diff --git a/docs/changes.md b/docs/changes.md index 9fabcec..b78ff3f 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -59,14 +59,19 @@ Table of contents [Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) # Update 16.0.0 - Getting the priorities straight -Added ---- -### New Integration - priorityManager + +## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities. Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed these features will still work! +- Allows the creation of custom priorityManagers for example: + +Added +--- +- multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. - multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. - multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object +- multi.success(...) -- Sends a success. Green `SUCCESS` mainly used for tests - multi.warn(...) -- Sends a warning. Yellow `WARNING` - multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR` @@ -145,7 +150,7 @@ Allows the user to have multi auto set priorities. Also adds the functionality t end) function test(a,b,c) - print("I run before all and control if things go!") + print("I run before all and control if execution should continue!") return a>b end @@ -246,6 +251,7 @@ Removed Fixed --- +- connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! - multi:reallocate(processor, index) has been fixed to work with the current changes of the library. - Issue with lanes not handling errors properly. This is now resolved - Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now! @@ -292,7 +298,7 @@ conn2:Fire() -- Looks like this is triggering a response. It shouldn't. We need to account for this conn1:Fire() conn1:Fire() --- Triggering conn1 twice counted as a valid way to trigger the phantom connection (conn1 * conn2) +-- Triggering conn1 twice counted as a valid way to trigger the virtual connection (conn1 * conn2) -- Now in 15.3.1, this works properly and the above doesn't do anything. Internally connections are locked until the conditions are met. conn2:Fire() diff --git a/init.lua b/init.lua index ea4c5c0..b19e453 100644 --- a/init.lua +++ b/init.lua @@ -138,6 +138,10 @@ end --Helpers +function multi.setClock(c) + clock = c +end + function multi.ForEach(tab,func) for i=1,#tab do func(tab[i]) end end @@ -156,11 +160,11 @@ local ignoreconn = true local empty_func = function() end function multi:newConnection(protect,func,kill) local c={} - local call_funcs = {} local lock = false - local locked = {} local fast = {} - c.Parent=self + c.__connectionAdded = function() end + c.rawadd = false + c.Parent = self setmetatable(c,{__call=function(self,...) local t = ... @@ -180,6 +184,49 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, + __mod = function(obj1, obj2) -- % + local cn = multi:newConnection() + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + else + error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end, + __concat = function(obj1, obj2) -- .. + local cn = multi:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) then + obj2:Fire(...) + end + end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + obj1:Fire(...) + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + else + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + end + return cn + end, __add = function(c1,c2) -- Or local cn = multi:newConnection() c1(function(...) @@ -192,6 +239,7 @@ function multi:newConnection(protect,func,kill) end, __mul = function(c1,c2) -- And local cn = multi:newConnection() + local ref1, ref2 if c1.__hasInstances == nil then cn.__hasInstances = {2} cn.__count = {0} @@ -201,25 +249,25 @@ function multi:newConnection(protect,func,kill) cn.__count = c1.__count end - c1(function(...) + ref1 = c1(function(...) cn.__count[1] = cn.__count[1] + 1 - c1:Lock() + c1:Lock(ref1) if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 - c1:Unlock() - c2:Unlock() + c1:Unlock(ref1) + c2:Unlock(ref2) end end) - c2(function(...) + ref2 = c2(function(...) cn.__count[1] = cn.__count[1] + 1 - c2:Lock() + c2:Lock(ref2) if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 - c1:Unlock() - c2:Unlock() + c1:Unlock(ref1) + c2:Unlock(ref2) end end) return cn @@ -330,6 +378,17 @@ function multi:newConnection(protect,func,kill) end function c:Connect(func, name) + local th + if thread.getRunningThread then + th = thread.getRunningThread() + end + if th then + local fref = func + func = function(...) + __CurrentConnectionThread = th + fref(...) + end + end table.insert(fast, func) if name then fast[name] = func @@ -356,6 +415,11 @@ function multi:newConnection(protect,func,kill) }) temp.ref = func temp.name = name + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(temp, func) + end return temp end @@ -376,7 +440,12 @@ function multi:newConnection(protect,func,kill) c.HasConnections = c.hasConnections c.GetConnection = c.getConnection + if func then + c = c .. func + end + if not(ignoreconn) then + if not self then return c end self:create(c) end @@ -567,7 +636,7 @@ function multi:newBase(ins) c.TID = _tid c.Act=function() end c.Parent=self - c.creationTime = os.clock() + c.creationTime = clock() if ins then table.insert(self.Mainloop,ins,c) else @@ -593,7 +662,7 @@ function multi:newTimer() local count=0 local paused=false function c:Start() - time=os.clock() + time=clock() return self end function c:Get() @@ -611,7 +680,7 @@ function multi:newTimer() end function c:Resume() paused=false - time=os.clock()-time + time=clock()-time return self end self:create(c) @@ -1443,7 +1512,7 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 multi:getCurrentProcess():create(c) - c.creationTime = os.clock() + c.creationTime = clock() return c end @@ -1970,7 +2039,7 @@ function multi:enableLoadDetection() if multi.maxSpd then return end -- here we are going to run a quick benchMark solo local temp = self:newProcessor() - local t = os.clock() + local t = clock() local stop = false temp:benchMark(.01):OnBench(function(time,steps) stop = steps @@ -2207,6 +2276,12 @@ function multi.error(self, err) end end +function multi.success(...) + local t = {} + for i,v in pairs({...}) do t[#t+1] = tostring(v) end + io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") +end + multi.GetType = multi.getType multi.IsPaused = multi.isPaused multi.IsActive = multi.isActive diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index cf8cd99..95ed193 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -133,11 +133,11 @@ local function init() end function multi:isRegistredScheme(scheme) - -- + return registry[name] ~= nil end function multi:getRegisteredScheme(scheme) - -- + return registry[name].mainloop, registry[name].umanager, registry[name].condition end local empty_func = function() return true end @@ -163,6 +163,9 @@ local function init() condition = condition, static = options.static or false } + + multi.priorityScheme[name] = name + return true end @@ -200,6 +203,8 @@ local function init() end local function init_chronos() + -- Let's implement a higher precision clock + multi.setClock(chronos.nanotime) -- This is also in .000 format. So a plug and play works. thread:newThread("System Priority Manager", function() while true do thread.yield() diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 4ae0d72..bd59f10 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -139,7 +139,7 @@ function multi:newSystemThreadedJobQueue(n) end function multi:newSystemThreadedConnection(name) - local conn = multi.newConnection() + local conn = multi:newConnection() conn.init = function(self) return self end GLOBAL[name or "_"] = conn return conn diff --git a/tests/runtests.lua b/tests/runtests.lua index ff59c52..049c26b 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -20,7 +20,7 @@ end The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init{print=true}--{priority=true} +local multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} local good = false local proc = multi:newProcessor("Test") @@ -32,7 +32,7 @@ end) runTest = thread:newFunction(function() local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false - print("Testing Basic Features. If this fails most other features will probably not work!") + multi.print("Testing Basic Features. If this fails most other features will probably not work!") proc:newAlarm(2):OnRing(function(a) alarms = true a:Destroy() @@ -62,18 +62,18 @@ runTest = thread:newFunction(function() event.OnEvent(function(evnt) evnt:Destroy() events = true - print("Alarms: Ok") - print("Events: Ok") - if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end - if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end - if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end - if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end - if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end + multi.success("Alarms: Ok") + multi.success("Events: Ok") + if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end + if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end + if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end + if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end + if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end end) thread.hold(event.OnEvent) - print("Starting Connection and Thread tests!") + multi.print("Starting Connection and Thread tests!") func = thread:newFunction(function(count) - print("Starting Status test: ",count) + multi.print("Starting Status test: ",count) local a = 0 while true do a = a + 1 @@ -88,13 +88,13 @@ runTest = thread:newFunction(function() local ret3 = func(20) local s1,s2,s3 = 0,0,0 ret.OnError(function(...) - print("Func 1:",...) + multi.error("Func 1:",...) end) ret2.OnError(function(...) - print("Func 2:",...) + multi.error("Func 2:",...) end) ret3.OnError(function(...) - print("Func 3:",...) + multi.error("Func 3:",...) end) ret.OnStatus(function(part,whole) s1 = math.ceil((part/whole)*1000)/10 @@ -107,31 +107,31 @@ runTest = thread:newFunction(function() end) ret.OnReturn(function(...) - print("Done 1",...) + multi.success("Done 1",...) end) ret2.OnReturn(function(...) - print("Done 2",...) + multi.success("Done 2",...) end) ret3.OnReturn(function(...) - print("Done 3",...) + multi.success("Done 3",...) end) local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) if s1 == 100 and s2 == 100 and s3 == 100 then - print("Threads: All tests Ok") + multi.success("Threads: All tests Ok") else if s1>0 and s2>0 and s3 > 0 then - print("Thread OnStatus: Ok") + multi.success("Thread OnStatus: Ok") else - print("Threads OnStatus or thread.hold(conn) Error!") + multi.error("Threads OnStatus or thread.hold(conn) Error!") end if timeout then - print("Connection Error!") + multi.error("Connection Error!") else - print("Connection Test 1: Ok") + multi.success("Connection Test 1: Ok") end - print("Connection holding Error!") + multi.error("Connection holding Error!") end conn1 = proc:newConnection() @@ -160,30 +160,30 @@ runTest = thread:newFunction(function() conn3:Fire() if c1 and c2 and c3 and c4 then - print("Connection Test 2: Ok") + multi.success("Connection Test 2: Ok") else - print("Connection Test 2: Error") + multi.error("Connection Test 2: Error") end c3 = false c4 = false conn3:Unconnect(d) conn3:Fire() if c3 and not(c4) then - print("Connection Test 3: Ok") + multi.success("Connection Test 3: Ok") else - print("Connection Test 3: Error removing connection") + multi.error("Connection Test 3: Error removing connection") end if not love then - print("Testing pseudo threading") + multi.print("Testing pseudo threading") os.execute("lua tests/threadtests.lua p") - print("Testing lanes threading") + multi.print("Testing lanes threading") os.execute("lua tests/threadtests.lua l") os.exit() end end) runTest().OnError(function(...) - print("Error: Something went wrong with the test!") + multi.error("Something went wrong with the test!") print(...) end) diff --git a/tests/threadtests.lua b/tests/threadtests.lua index fd2e735..4d382d9 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,8 +1,8 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{}--{priority=true} +multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 -local env +local env, we_good if love then GLOBAL, THREAD = require("multi.integration.loveManager"):init() @@ -27,11 +27,11 @@ else end end -print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") +multi.print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") THREAD.setENV({ multi_assert = function(expected, actual, s) if expected ~= actual then - error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") + multi.error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") end end }) @@ -47,7 +47,7 @@ multi:newThread("Scheduler Thread",function() multi_assert("table", type(f), "Argument f is not a table!") queue:push("done") end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) - print("Error:", err) + multi.error(err) os.exit() end) @@ -57,7 +57,7 @@ multi:newThread("Scheduler Thread",function() thread.kill() end - print("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") + multi.success("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") func = THREAD:newFunction(function(a,b,c) assert(a == 3, "First argument expected '3' got '".. a .."'!") @@ -73,7 +73,7 @@ multi:newThread("Scheduler Thread",function() assert(c == 3, "Third return was not '3'!") assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") - print("Threaded Functions, arg passing, return passing, holding: Ok") + multi.success("Threaded Functions, arg passing, return passing, holding: Ok") test=multi:newSystemThreadedTable("YO"):init() test["test1"]="tabletest" @@ -84,7 +84,7 @@ multi:newThread("Scheduler Thread",function() THREAD.hold(function() return tab["test1"] end) THREAD.sleep(.1) tab["test2"] = "Whats so funny?" - end).OnError(print) + end).OnError(multi.error) multi:newThread("test2",function() thread.hold(function() return test["test2"] end) @@ -96,11 +96,11 @@ multi:newThread("Scheduler Thread",function() end,{sleep=1}) if val == multi.TIMEOUT then - print("SystemThreadedTables: Failed") + multi.error("SystemThreadedTables: Failed") os.exit() end - print("SystemThreadedTables: Ok") + multi.success("SystemThreadedTables: Ok") local ready = false @@ -123,11 +123,11 @@ multi:newThread("Scheduler Thread",function() end,{sleep=2}) if val == multi.TIMEOUT then - print("SystemThreadedJobQueues: Failed") + multi.error("SystemThreadedJobQueues: Failed") os.exit() end - print("SystemThreadedJobQueues: Ok") + multi.success("SystemThreadedJobQueues: Ok") queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() multi:newSystemThread("Test_Thread_2",function() @@ -137,7 +137,7 @@ multi:newThread("Scheduler Thread",function() queue2:push("Test_Thread_2") end) multi:mainloop() - end).OnError(print) + end).OnError(multi.error) multi:newSystemThread("Test_Thread_3",function() queue2 = THREAD.waitFor("Test_Queue2"):init() @@ -146,7 +146,7 @@ multi:newThread("Scheduler Thread",function() queue2:push("Test_Thread_3") end) multi:mainloop() - end).OnError(print) + end).OnError(multi.error) connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() a=0 connOut(function(arg) @@ -164,21 +164,26 @@ multi:newThread("Scheduler Thread",function() count = count + 1 end end - end).OnError(print) + end).OnError(multi.error) _, err = thread.hold(function() return count == 9 end,{sleep=.3}) if err == multi.TIMEOUT then - print("SystemThreadedConnections: Failed") - os.exit() + multi.error("SystemThreadedConnections: Failed") end - print("SystemThreadedConnections: Ok") + multi.success("SystemThreadedConnections: Ok") - print("Tests complete!") + we_good = true os.exit() -end).OnError(print) - +end).OnError(multi.error) +multi.OnExit(function(err) + if not we_good then + multi.error("There was an error running some tests!") + else + multi.success("Tests complete!") + end +end) multi:mainloop() \ No newline at end of file