diff --git a/Documentation.md b/Documentation.md index 6d9b438..6eba7d6 100644 --- a/Documentation.md +++ b/Documentation.md @@ -316,6 +316,7 @@ All of these functions are found on actors `self = multiObj:OnTimedOut(func)` -- If ResolveTimer was not called in time this event will be triggered. The function connected to it get a refrence of the original object that the timer was created on as the first argument. `self = multiObj:OnTimerResolved(func)` -- This event is triggered when the timer gets resolved. Same argument as above is passed, but the variable arguments that are accepted in resolvetimer are also passed as well. `self = multiObj:Reset(n)` -- In the cases where it isn't obvious what it does, it acts as Resume() +`self = multiObj:SetName(STRING name)` Actor: Events ------ @@ -822,6 +823,7 @@ ST - THREAD namespace `THREAD.getName()` -- Returns the name of the working thread `THREAD.sleep(NUMBER n)` -- Sleeps for an amount of time stopping the current thread `THREAD.hold(FUNCTION func)` -- Holds the current thread until a condition is met +`THREAD.getID()` -- returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id ST - GLOBAL namespace --------------------- @@ -835,6 +837,7 @@ ST - System Threads ------------------- `systemThread = multi:newSystemThread(STRING thread_name,FUNCTION spawned_function,ARGUMENTS ...)` -- Spawns a thread with a certain name. `systemThread:kill()` -- kills a thread; can only be called in the main thread! +`systemThread.OnError(FUNCTION(systemthread,errMsg,errorMsgWithThreadName))` System Threads are the feature that allows a user to interact with systen threads. It differs from regular coroutine based thread in how it can interact with variables. When using system threads the GLOBAL table is the "only way"* to send data. Spawning a System thread is really simple once all the required libraries are in place. See example below: diff --git a/changes.md b/changes.md index 6c69832..26fa215 100644 --- a/changes.md +++ b/changes.md @@ -1,6 +1,6 @@ #Changes [TOC] -Update 13.0.0 So you documented it, finally! If I had a dollar for each time I found a bug working on 13.0.0 I'd be quite wealthy by now. How much lag could one expect when I've been coding with my own library wrong this entire time? +Update 13.0.0 So you documented it, finally! If I had a dollar for each time I found a bug working on 13.0.0 I'd be quite wealthy by now. ------------- **Quick note** on the 13.0.0 update: This update I went all in finding bugs and improving proformance within the library. I added some new features and the new task manager, which I used as a way to debug the library was a great help, so much so thats it is now a permanent feature. It's been about half a year since my last update, but so much work needed to be done. I hope you can find a use in your code to use my library. I am extremely proud of my work; 7 years of development, I learned so much about lua and programming through the creation of this library. It was fun, but there will always be more to add and bugs crawling there way in. I can't wait to see where this library goes in the future! @@ -10,6 +10,7 @@ Changed: - A few things, to make concepts in the library more clear. - The way functions returned paused status. Before it would return "PAUSED" now it returns nil, true if paused - Modified the connection object to allow for some more syntaxial suger! +- System threads now trigger an OnError connection that is a member of the object itself. multi.OnError() is no longer triggered for a system thread that crashes! Connection Example: ```lua @@ -65,6 +66,7 @@ Added: - multi:getTasksDetails(STRING format) -- Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format - multi:endTask(TID) -- Use multi:getTasksDetails("t") to get the tid of a task - multi:enableLoadDetection() -- Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it +- THREAD.getID() -- returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface ```lua package.path="?/init.lua;?.lua;"..package.path @@ -109,7 +111,6 @@ Table format for getTasksDetails(STRING format) **Going forward:** - Add something - Update 12.2.2 Time for some more bug fixes! ------------- Fixed: multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes. diff --git a/multi/init.lua b/multi/init.lua index 323e9c7..c811d25 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -28,6 +28,7 @@ multi.Version = "13.0.0" multi._VERSION = "13.0.0" multi.stage = "stable" multi.__index = multi +multi.Name = "multi.Root" multi.Mainloop = {} multi.Garbage = {} multi.ender = {} @@ -159,7 +160,7 @@ function multi:getLoad() return bench end) bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/1.5)))*100) + val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) else busy = true local bench @@ -170,7 +171,7 @@ function multi:getLoad() multi:uManager() end bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/1.5)))*100) + val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) busy = false end if val<0 then val = 0 end @@ -330,11 +331,11 @@ function multi.AlignTable(tab) return table.concat(str,"\n") end local priorityTable = {[0]="Round-Robin",[1]="Just-Right",[2]="Top-heavy",[3]="Timed-Based-Balancer"} -local ProcessName = {[true]="SubProcess",[false]="MainProcess"} +local ProcessName = {[true]="SubProcessor",[false]="MainProcessor"} function multi:getTasksDetails(t) if t == "string" or not t then str = { - {"Type","Uptime","Priority","TID"} + {"Type \n" + end + end if multi.scheduler then for i=1,#multi.scheduler.Threads do dat = dat .. "\n" end - return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(multi:getLoad(),2).."%\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nThreads Running: "..#multi.scheduler.Threads.."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s.."\n\n"..dat + return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(multi:getLoad(),2).."%\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nThreads Running: "..#multi.scheduler.Threads.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s.."\n\n"..dat..dat2 else - return "Load on "..({[true]="SubProcess<"..(self.Name or "Unnamed")..">",[false]="MainProcess"})[self.Type=="process"]..": "..multi.Round(multi:getLoad(),2).."%\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s + return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(multi:getLoad(),2).."%\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..s..dat2 end elseif t == "t" or t == "table" then str = { @@ -366,12 +373,18 @@ function multi:getTasksDetails(t) SystemLoad = multi.Round(multi:getLoad(),2) } str.threads = {} + str.systemthreads = {} for i,v in pairs(self.Mainloop) do str[#str+1]={Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = i} end for i=1,#multi.scheduler.Threads do str.threads[multi.scheduler.Threads[i].Name]={Uptime = os.clock()-multi.scheduler.Threads[i].creationTime} end + if multi.canSystemThread then + for i=1,#multi.SystemThreads do + str.systemthreads[multi.SystemThreads[i].Name]={Uptime = os.clock()-multi.SystemThreads[i].creationTime} + end + end return str end end @@ -1576,7 +1589,7 @@ function multi.initThreads() event:OnEvent(function(evnt) evnt.link.sleep=0 evnt:Destroy() - end) + end):setName("multi.thread.skip") elseif ret[1]=="_hold_" then self.Threads[i].timer:Reset() self.Threads[i].sleep=math.huge @@ -1589,7 +1602,7 @@ function multi.initThreads() multi.nextStep(function() evnt:Destroy() end) - end) + end):setName("multi.thread.hold") elseif ret.Name then self.Globals[ret.Name]=ret.Value end diff --git a/multi/integration/lanesManager.lua b/multi/integration/lanesManager.lua index ea201f4..5a97347 100644 --- a/multi/integration/lanesManager.lua +++ b/multi/integration/lanesManager.lua @@ -32,6 +32,8 @@ end -- Step 1 get lanes lanes=require("lanes").configure() local multi = require("multi") -- get it all and have it on all lanes +multi.SystemThreads = {} +local thread = thread multi.isMainThread=true function multi:canSystemThread() return true @@ -93,6 +95,9 @@ end function THREAD.getName() return THREAD_NAME end +function THREAD.getID() + return THREAD_ID +end --[[ Step 4 We need to get sleeping working to handle timing... We want idle wait, not busy wait Idle wait keeps the CPU running better where busy wait wastes CPU cycles... Lanes does not have a sleep method however, a linda recieve will in fact be a idle wait! So we use that and wrap it in a nice package]] @@ -109,17 +114,32 @@ function THREAD.hold(n) end local rand = math.random(1,10000000) -- Step 5 Basic Threads! +-- local threads = {} +local count = 0 +local started function multi:newSystemThread(name,func,...) + multi.InitSystemThreadErrorHandler() rand = math.random(1,10000000) local c={} local __self=c c.name=name + c.Name = name + c.Id = count + local THREAD_ID = count + count = count + 1 c.Type="sthread" + c.creationTime = os.clock() local THREAD_NAME=name local function func2(...) + local multi = require("multi") _G["THREAD_NAME"]=THREAD_NAME + _G["THREAD_ID"]=THREAD_ID math.randomseed(rand) func(...) + if _G.__Needs_Multi then + multi:mainloop() + end + THREAD.kill() end c.thread=lanes.gen("*", func2)(...) function c:kill() @@ -127,16 +147,32 @@ function multi:newSystemThread(name,func,...) self.thread:cancel() print("Thread: '"..self.name.."' has been stopped!") end - c.status=multi:newUpdater(multi.Priority_IDLE) - c.status.link=c - c.status:OnUpdate(function(self) - local v,err,t=self.link.thread:join(.001) - if err then - multi.OnError:Fire(self.link,err,"Error in systemThread: '"..self.link.name.."' <"..err..">") - self:Destroy() + table.insert(multi.SystemThreads,c) + c.OnError = multi:newConnection() + return c +end +function multi.InitSystemThreadErrorHandler() + if started then return end + multi:newThread("ThreadErrorHandler",function() + local deadThreads = {} + local threads = multi.SystemThreads + while true do + thread.sleep(.5) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. + for i=#threads,1,-1 do + local v,err,t=threads[i].thread:join(.001) + if err then + if err:find("Thread was killed!") then + table.remove(threads,i) + else + threads[i].OnError:Fire(threads[i],err,"Error in systemThread: '"..threads[i].name.."' <"..err..">") + table.remove(threads,i) + table.insert(deadThreads,threads[i].Id) + GLOBAL["__DEAD_THREADS__"]=deadThreads + end + end + end end end) - return c end print("Integrated Lanes!") multi.integration={} -- for module creators diff --git a/multi/integration/loveManager.lua b/multi/integration/loveManager.lua index bc886eb..50c4e6c 100644 --- a/multi/integration/loveManager.lua +++ b/multi/integration/loveManager.lua @@ -34,6 +34,7 @@ multi.integration.love2d.ThreadBase=[[ tab={...} __THREADID__=table.remove(tab,1) __THREADNAME__=table.remove(tab,1) +THREAD_ID=table.remove(tab,1) require("love.filesystem") require("love.system") require("love.timer") @@ -217,6 +218,9 @@ isMainThread=true function THREAD.getName() return __THREADNAME__ end +function THREAD.getID() + return THREAD_ID +end function ToStr(val, name, skipnewlines, depth) skipnewlines = skipnewlines or false depth = depth or 0 @@ -295,12 +299,16 @@ local function randomString(n) end return str end +local count = 0 function multi:newSystemThread(name,func,...) -- the main method local c={} c.name=name + c.Name = name c.ID=c.name.."" + c.Id=count + count = count + 1 c.thread=love.thread.newThread(multi.integration.love2d.ThreadBase:gsub("INSERT_USER_CODE",dump(func))) - c.thread:start(c.ID,c.name,...) + c.thread:start(c.ID,c.name,,...) function c:kill() multi.integration.GLOBAL["__DIEPLZ"..self.ID.."__"]="__DIEPLZ"..self.ID.."__" end diff --git a/multi/integration/shared.lua b/multi/integration/shared.lua index 217326f..2149456 100644 --- a/multi/integration/shared.lua +++ b/multi/integration/shared.lua @@ -123,6 +123,7 @@ function multi:newSystemThreadedConnection(name,protect) local qsm = multi:newSystemThreadedQueue(name.."THREADED_CALLSYNCM"):init() local qs = multi:newSystemThreadedQueue(name.."THREADED_CALLSYNC"):init() function c:init() + _G.__Needs_Multi = true local multi = require("multi") if multi:getPlatform()=="love2d" then GLOBAL=_G.GLOBAL @@ -241,6 +242,7 @@ function multi:newSystemThreadedConsole(name) local sThread=multi.integration.THREAD local GLOBAL=multi.integration.GLOBAL function c:init() + _G.__Needs_Multi = true local multi = require("multi") if multi:getPlatform()=="love2d" then GLOBAL=_G.GLOBAL @@ -288,6 +290,7 @@ function multi:newSystemThreadedTable(name) local sThread=multi.integration.THREAD local GLOBAL=multi.integration.GLOBAL function c:init() -- create an init function so we can mimic on both love2d and lanes + _G.__Needs_Multi = true local multi = require("multi") if multi:getPlatform()=="love2d" then GLOBAL=_G.GLOBAL diff --git a/test.lua b/test.lua index 081b606..9a0de0a 100644 --- a/test.lua +++ b/test.lua @@ -5,12 +5,12 @@ nGLOBAL = require("multi.integration.networkManager").init() local a local clock = os.clock function sleep(n) -- seconds - local t0 = clock() - while clock() - t0 <= n do end + local t0 = clock() + while clock() - t0 <= n do end end master = multi:newMaster{ name = "Main", -- the name of the master - --noBroadCast = true, -- if using the node manager, set this to true to avoid double connections +--~ --noBroadCast = true, -- if using the node manager, set this to true to avoid double connections managerDetails = {"192.168.1.4",12345}, -- the details to connect to the node manager (ip,port) } master.OnError(function(name,err) @@ -28,9 +28,20 @@ end) master.OnNodeConnected(function(name) table.insert(connlist,name) end) -multi.OnError(function(...) - print(...) +--~ multi:newThread("TaskView",function() +--~ while true do +--~ thread.sleep(1) +--~ print(multi:getTasksDetails()) +--~ end +--~ end) +multi:newSystemThread("SystemThread",function() + local multi = require("multi") + print(THREAD.getName(),THREAD.getID()) + THREAD.sleep(8) +end).OnError(function(a,b,c) + print("ERROR:",b) end) +--~ print(multi:getTasksDetails()) multi:mainloop{ protect = false }