diff --git a/.gitignore b/.gitignore index fa5e1d5..8538ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,8 @@ - -test2.lua -*.mp3 -*.exe -*.dll -lanestestclient.lua -lanestest.lua -sample-node.lua -sample-master.lua -Ayn Rand - The Virtue of Selfishness-Mg4QJheclsQ.m4a -Atlas Shrugged by Ayn Rand Audiobook-9s2qrEau63E.webm -test.lua -test.lua +*lua5.1 +*lua5.2 +*lua5.3 +*lua5.4 +*luajit *.code-workspace *.dat +*.zip \ No newline at end of file diff --git a/LICENSE b/LICENSE index 3b661ec..f0e3a38 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b0848c7..c1599be 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,75 @@ -# Multi Version: 15.1.0 Hold the thread +# Multi Version: 15.3.0 **Key Changes** -- thread.hold has been updated to allow all variants to work as well as some new features. Check the changelog or documentation for more info. -- multi:newProccesor() Creates a process that acts like the multi namespace that can be managed independently from the mainloop. -- Connections can be added together +- Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! -My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations and the love2d compat. +My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations. + +
+ +Progress is being made in [v15.3.0](https://github.com/rayaman/multi/tree/v15.3.0) +--- + +
INSTALLING ---------- -Links to dependencies: +Link to optional dependencies: [lanes](https://github.com/LuaLanes/lanes) +[love2d](https://love2d.org/) To install copy the multi folder into your environment and you are good to go
-If you want to use the system threads, then you'll need to install lanes! -**or** use luarocks `luarocks install multi` +If you want to use the system threads, then you'll need to install lanes or love2d game engine! -Going forward I will include a Release zip for love2d. -**The Network Manager rework is currently being worked on and the old version is not included in this version.** +``` +luarocks install multi +``` Discord ------- -Have a question? Or need realtime assistance? Feel free to join the discord!
-https://discord.gg/U8UspuA
+Have a question or need realtime assistance? Feel free to join the discord!
+https://discord.gg/U8UspuA Planned features/TODO --------------------- -- [x] ~~Finish Documentation~~ Finished -- [ ] Create test suite +- [ ] Create test suite (In progress, mostly done) - [ ] Network Parallelism rework -Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)
+Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md) ----- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi, thread = require("multi"):init() GLOBAL, THREAD = require("multi.integration.threading"):init() + multi:newSystemThread("System Thread",function() - while true do - THREAD.sleep(1) - print("World!") - end + while true do + THREAD.sleep(.1) + io.write(" World") + THREAD.kill() + end end) + multi:newThread("Coroutine Based Thread",function() - while true do - print("Hello") - thread.sleep(1) - end + while true do + io.write("Hello") + thread.sleep(.1) + thread.kill() + end end) + +multi:newTLoop(function(loop) + print("!") + loop:Destroy() + os.exit() +end,.3) + multi:mainloop() + --[[ while true do - multi:uManager() + multi:uManager() end ]] ``` diff --git a/changes.md b/changes.md index bc444c4..77368e5 100644 --- a/changes.md +++ b/changes.md @@ -1,15 +1,323 @@ # Changelog Table of contents --- -[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) +[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) + +# Update 15.2.0 - Upgrade Complete + +Full Update Showcase + +```lua +package.path = "./?/init.lua;"..package.path +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.threading"):init() + +-- Using a system thread, but both system and local threads support this! +-- Don't worry if you don't have lanes or love2d. PesudoThreading will kick in to emulate the threading features if you do not have access to system threading. +func = THREAD:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + THREAD.sleep(.1) + -- Push the status from the currently running threaded function to the main thread + THREAD.pushStatus(a,count) + if a == count then break end + end + return "Done" +end) + +thread:newThread("test",function() + local ret = func(10) + ret.OnStatus(function(part,whole) + print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + print("TEST",func(5).wait()) + -- The results from the OnReturn connection is passed by thread.hold + print("Status:",thread.hold(ret.OnReturn)) + print("Function Done!") +end).OnError(function(...) + print("Error:",...) +end) + +local ret = func(10) +local ret2 = func(15) +local ret3 = func(20) +local s1,s2,s3 = 0,0,0 +ret.OnError(function(...) + print("Error:",...) +end) +ret2.OnError(function(...) + print("Error:",...) +end) +ret3.OnError(function(...) + print("Error:",...) +end) +ret.OnStatus(function(part,whole) + s1 = math.ceil((part/whole)*1000)/10 + print(s1) +end) +ret2.OnStatus(function(part,whole) + s2 = math.ceil((part/whole)*1000)/10 + print(s2) +end) +ret3.OnStatus(function(part,whole) + s3 = math.ceil((part/whole)*1000)/10 + print(s3) +end) + +loop = multi:newTLoop() + +function loop:testing() + print("testing haha") +end + +loop:Set(1) +t = loop:OnLoop(function() + print("Looping...") +end):testing() + +local proc = multi:newProcessor("Test") +local proc2 = multi:newProcessor("Test2") +local proc3 = proc2:newProcessor("Test3") +proc.Start() +proc2.Start() +proc3.Start() +proc:newThread("TestThread_1",function() + while true do + thread.sleep(1) + end +end) +proc:newThread("TestThread_2",function() + while true do + thread.sleep(1) + end +end) +proc2:newThread("TestThread_3",function() + while true do + thread.sleep(1) + end +end) + +thread:newThread(function() + thread.sleep(1) + local tasks = multi:getStats() + + for i,v in pairs(tasks) do + print("Process: " ..i.. "\n\tTasks:") + for ii,vv in pairs(v.tasks) do + print("\t\t"..vv:getName()) + end + print("\tThreads:") + for ii,vv in pairs(v.threads) do + print("\t\t"..vv:getName()) + end + end + + thread.sleep(10) -- Wait 10 seconds then kill the process! + os.exit() +end) + +multi:mainloop() +``` + +Added: +--- +- `multi:getStats()` + - Returns a structured table where you can access data on processors there tasks and threads: + ```lua + -- Upon calling multi:getStats() the table below is returned + get_Stats_Table { + proc_1 -- table + proc_2 -- table + ... + proc_n -- table + } + proc_Table { + tasks = {alarms,steps,loops,etc} -- All multi objects + threads = {thread_1,thread_2,thread_3,etc} -- Thread objects + } + -- Refer to the objects documentation to see how you can interact with them + ``` + - Reference the Full update showcase for the method in action +- `multi:newProcessor(name, nothread)` + - If no thread is true auto sets the processor as Active, so proc.run() will start without the need for proc.Start() + +- `multi:getProcessors()` + - Returns a list of all processors + +- `multi:getName()` + - Returns the name of a processor + +- `multi:getFullName()` + - Returns the fullname/entire process tree of a process + +- Processors can be attached to processors + +- `multi:getTasks()` + - Returns a list of all non thread based objects (loops, alarms, steps, etc) + +- `multi:getThreads()` + - Returns a list of all threads on a process + +- `multi:newProcessor(name, nothread).run()` + - New function run to the processor object to + +- `multi:newProcessor(name, nothread):newFunction(func, holdme)` + - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor + +- `multi:newTLoop()` member functions + - `TLoop:Set(set)` - Sets the time to wait for the TLoop + +- `multi:newStep()` member functions + - `Step:Count(count)` - Sets the amount a step should count by + +- `multi:newTStep()` member functions + - `TStep:Set(set)` - Sets the time to wait for the TStep + + +Changed: +--- +- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: + ```lua + multi, thread = require("multi"):init() + + func = thread:newFunction(function(count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done", 1, 2, 3 + end) + + thread:newThread("test",function() + local ret = func(10) + ret.OnStatus(function(part,whole) + print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + print("Status:",thread.hold(ret.OnReturn)) + print("Function Done!") + os.exit() + end).OnError(function(...) + print("Error:",...) + end) + + multi:mainloop() + ``` + Output: + ``` + Ret1: 10% + Ret1: 20% + Ret1: 30% + Ret1: 40% + Ret1: 50% + Ret1: 60% + Ret1: 70% + Ret1: 80% + Ret1: 90% + Ret1: 100% + Status: Done 1 2 3 nil nil nil nil nil nil nil nil nil nil nil nil + Function Done! + ``` + +- Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. +- Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. + +- multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) + | Setting | Description | + ---|--- + print | When set to true parts of the library will print out updates otherwise no internal printing will be done + priority | When set to true, the library will prioritize different objects based on their priority +- `multi:newProcessor(name,nothread)` The new argument allows you to tell the system you won't be using the Start() and Stop() functions, rather you will handle the process yourself. Using the proc.run() function. This function needs to be called to pump the events. + - Processors now also use lManager instead of uManager. +- `multi.hold(n,opt)` now supports an option table like thread.hold does. +- Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update + + ```lua + multi,thread = require("multi"):init() + + loop = multi:newTLoop() + + function loop:testing() + print("testing haha") + end + + loop:Set(1) + t = loop:OnLoop(function() + print("Looping...") + end):testing() + + multi:mainloop() + + --[[Returns as expected: + + testing haha + Looping... + Looping... + Looping... + ... + Looping... + Looping... + Looping... + ]] + ``` + + While chaining on the OnSomeEventMethod() wasn't really a used feature, I still wanted to keep it just incase someone was relying on this working. And it does have it uses + +- All Multi Objects now use Connection objects + + `multiobj:OnSomeEvent(func)` or `multiobj.OnSomeEvent(func)` + +- Connection Objects no longer Fire with syntax sugar when attached to an object: + + `multiobj:OnSomeEvent(...)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! + +- multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) + +Removed: +--- +- `multi:getTasksDetails()` Remade completely and now called `multi:getStats()` +- `multi:getError()` Removed when setting protect was removed +- `multi:FreeMainEvent()` The new changes with connections make's this function unnecessary +- `multi:OnMainConnect(func)` See above +- `multi:connectFinal(func)` See above +- `multi:lightloop()` Cleaned up the mainloop/uManager method, actually faster than lightloop (Which should have been called liteloop) +- `multi:threadloop()` See above for reasons +- `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. +- `multi:GetParentProcess()` use `multi.getCurrentProcess()` instead +- priority scheme 2, 3 and auto-priority have been removed! Only priority scheme 1 actually performed in a reasonable fashion so that one remained. +- `multi:newFunction(func)` + - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did +- `multi.holdFor()` Now that multi.hold takes the option table that thread.hold has this feature can be emulated using that. + +- Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed + +Fixed: +--- + +- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seemed to be related to my filesystem, since remounting the drive caused the issue to stop. (Windows) + +- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not being up to date with threaded functions + +- Issue where gettasksdetails() would try to process a destroyed object causing it to crash + +- Issue with multi.hold() not pumping the mainloop and only the scheduler + +ToDo: +--- + +- Work on network parallelism + # Update 15.1.0 - Hold the thread! Full Update Showcase ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() func = thread:newFunction(function(count) local a = 0 @@ -98,17 +406,17 @@ multi:mainloop() Added: --- -## multi:newSystemThreadedJobQueue(n) isEmpty() - -- returns true if the queue is empty, false if there are items in the queue. +- multi:newSystemThreadedJobQueue(n) + + `queue:isEmpty()` + + Returns true if the queue is empty, false if there are items in the queue. **Note:** a queue might be empty, but the job may still be running and not finished yet! Also if a registered function is called directly instead of pushed, it will not reflect inside the queue until the next cycle! Example: ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -package.cpath = [[C:\Program Files (x86)\Lua\5.1\systree\lib\lua\5.1\?.dll;C:\Program Files (x86)\Lua\5.1\systree\lib\lua\5.1\?\core.dll;]] ..package.cpath -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads @@ -153,8 +461,7 @@ multi:mainloop() ## multi:newProcessor(name) ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() -- Create a processor object, it works a lot like the multi object sandbox = multi:newProcessor() @@ -227,8 +534,7 @@ Can be chained as long as you want! See example below Example: ```lua -package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() func = thread:newFunction(function(count) local a = 0 @@ -283,8 +589,7 @@ Changed: holdMe(set) | Sets the holdme argument that existed at function creation ```lua - package.path = "./?/init.lua;"..package.path - multi, thread = require("multi"):init() + local multi, thread = require("multi"):init() test = thread:newFunction(function(a,b) thread.sleep(1) @@ -378,8 +683,7 @@ ToDo Full Update Showcase --- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -multi,thread = require("multi"):init() +local multi,thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads @@ -413,6 +717,7 @@ multi:mainloop() Note: --- This was supposed to be released over a year ago, but work and other things got in my way. Pesudo Threading now works. The goal of this is so you can write modules that can be scaled up to utilize threading features when available. + Added: --- - multi:newISOThread(name,func,env) @@ -448,7 +753,6 @@ Todo: Full Update Showcase --- ```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi,thread = require("multi"):init() -- Testing destroying and fixed connections @@ -515,7 +819,6 @@ Fixed: - Issue with connections not returning a handle for managing a specific conn object. - Issue with connections where connection chaining wasn't working properly. This has been addressed. ```lua - package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path local multi,thread = require("multi"):init() test = multi:newConnection() test(function(hmm) @@ -568,7 +871,6 @@ Full Update Showcase --- Something I plan on doing each version going forward ```lua -package.path="?.lua;?/init.lua;?.lua;"..package.path local multi, thread = require("multi"):init() GLOBAL,THREAD = require("multi.integration.lanesManager"):init() serv = multi:newService(function(self,data) @@ -790,19 +1092,21 @@ Added: -- If the created function encounters an error, it will return nil, the error message! - special variable multi.NIL was added to allow error handling in threaded functions. -- multi.NIL can be used in to force a nil value when using thread.hold() -- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function +- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. + + **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function is converted into a threaded function + - lanes threads can now have their priority set using: sThread.priority = --- thread.Priority_Core --- thread.Priority_High --- thread.Priority_Above_Normal --- thread.Priority_Normal --- thread.Priority_Below_Normal --- thread.Priority_Low --- thread.Priority_Idle + - thread.Priority_Core + - thread.Priority_High + - thread.Priority_Above_Normal + - thread.Priority_Normal + - thread.Priority_Below_Normal + - thread.Priority_Low + - thread.Priority_Idle - thread.hold() and multi.hold() now accept connections as an argument. See example below ```lua -package.path = "./?/init.lua;"..package.path local multi, thread = require("multi"):init() conn = multi:newConnection() multi:newThread(function() @@ -833,7 +1137,6 @@ end) thread newFunction using auto convert ```lua -package.path = "./?/init.lua;" .. package.path multi, thread = require("multi").init() a=5 multi:newThread("Test",function() @@ -871,7 +1174,7 @@ Changed: --- - Connections connect function can now chain connections ```lua - package.path = "./?/init.lua;"..package.path + local multi, thread = require("multi").init() test = multi:newConnection() test(function(a) @@ -1069,7 +1372,6 @@ Added: - STC: FireTo(id,...) — Described above. ```lua -package.path="?/init.lua;?.lua;"..package.path local multi = require("multi") conn = multi:newConnector() conn.OnTest = multi:newConnection() @@ -1141,7 +1443,6 @@ Going forward: Example --- ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") GLOBAL, THREAD = require("multi.integration.lanesManager").init() jq = multi:newSystemThreadedJobQueue() @@ -1203,7 +1504,7 @@ L: 6543 I: 1635 ~n=n*4 ``` -P3 Ignores using a basic funceion and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! +P3 Ignores using a basic function and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! ``` C: 2120906 H: 2120906 @@ -1218,10 +1519,10 @@ Auto Priority works by seeing what should be set high or low. Due to lua not hav **Improved:** - Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. -Note: If you write slow code this library's improbements wont make much of a difference. +Note: If you write slow code this library's improvements wont make much of a difference. - Loops have been optimised as well! Being the most used objects I felt they needed to be made as fast as possible -I usually give an example of the changes made, but this time I have an explantion for multi.nextStep(). It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep addes a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. +I usually give an example of the changes made, but this time I have an explantion for `multi.nextStep()`. It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep adds a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. Note: The upper limit of this libraries performance on my machine is ~39mil. This is simply a while loop counting up from 0 and stops after 1 second. The 20mil that I am currently getting is probably as fast as it can get since its half of the max performance possible, and each layer I have noticed that it doubles complexity. Throughout the years with this library I have seen massive improvements in speed. In the beginning we had only ~2000 steps per second. Fast right? then after some tweaks we went to about 300000 steps per second, then 600000. Some more tweaks brought me to ~1mil steps per second, then to ~4 mil then ~9 mil and now finally ~20 mil... the doubling effect that i have now been seeing means that odds are I have reach the limit. I will aim to add more features and optimize individule objects. If its possible to make the library even faster then I will go for it. @@ -1235,10 +1536,9 @@ Fixed: Changed: --- - thread.hold() now returns the arguments that were pass by the event function -- event objexts now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object +- event objects now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local a = 0 multi:newThread("test",function() @@ -1335,7 +1635,6 @@ Now there is a little trick you can do. If you combine both networkmanager and s **NodeManager.lua** ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() @@ -1354,7 +1653,6 @@ Side note: I had a setting called cross talk that would allow nodes to talk to e **Node.lua** ```lua -package.path="?/init.lua;?.lua;"..package.path multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() @@ -1376,10 +1674,8 @@ multi:mainloop(settings) **Master.lua** ```lua --- set up the package -package.path="?/init.lua;?.lua;"..package.path -- Import the libraries -multi = require("multi") +local multi = require("multi") local GLOBAL, THREAD = require("multi.integration.lanesManager").init() nGLOBAL = require("multi.integration.networkManager").init() -- Act as a master node @@ -1549,7 +1845,6 @@ Added: Example of threaded connections ```lua -package.path="?/init.lua;?.lua;"..package.path local GLOBAL,THREAD=require("multi.integration.lanesManager").init() multi:newSystemThread("Test_Thread_1",function() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -1586,7 +1881,6 @@ Fixed: Example of threaded tables ```lua -package.path="?/init.lua;?.lua;"..package.path local GLOBAL,sThread=require("multi.integration.lanesManager").init() multi:newSystemThread("Test_Thread_1",function() require("multi") @@ -1912,7 +2206,6 @@ Added:
Using multi:systemThreadedBenchmark() --- ```lua -package.path="?/init.lua;"..package.path local GLOBAL,sThread=require("multi.integration.lanesManager").init() multi:systemThreadedBenchmark(3):OnBench(function(self,count) print("First Bench: "..count) @@ -1935,41 +2228,6 @@ GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2 -- Also, each thread has a .1 second delay! This is used to generate a random value for each thread! require("core.GuiManager") gui.ff.Color=Color.Black -function multi:newSystemThreadedQueue(name) -- in love2d this will spawn a channel on both ends - local c={} - c.name=name - if love then - if love.thread then - function c:init() - self.chan=love.thread.getChannel(self.name) - function self:push(v) - self.chan:push(v) - end - function self:pop() - return self.chan:pop() - end - GLOBAL[self.name]=self - return self - end - return c - else - error("Make sure you required the love.thread module!") - end - else - c.linda=lanes.linda() - function c:push(v) - self.linda:send("Q",v) - end - function c:pop() - return ({self.linda:receive(0,"Q")})[2] - end - function c:init() - return self - end - GLOBAL[name]=c - end - return c -end queue=multi:newSystemThreadedQueue("QUEUE"):init() queue:push("This is a test") queue:push("This is a test2") @@ -2251,7 +2509,7 @@ Change: Upcomming: --- -- Threaded objects wrapped in corutines, so you can hold/sleep without problems! +- Threaded objects wrapped in coroutines, so you can hold/sleep without problems! # Update: 1.4.0 (3/20/2017) Added: @@ -2325,8 +2583,10 @@ Added: # Update: 1.2.0 (12.31.2016) Added: --- -- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. +- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can organize data before hand you can route info to certain connections thus saving a lot of cpu time. + **NOTE:** Only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + Changed: --- - Started keeping track of dates @@ -2385,6 +2645,7 @@ Changed: Changed: --- - Everything, complete restructuring of the library from function based to object based. Resembles the modern version of the library + Added: --- - Love2d support basic diff --git a/makeENV.lua b/makeENV.lua new file mode 100644 index 0000000..acb6fc0 --- /dev/null +++ b/makeENV.lua @@ -0,0 +1,19 @@ +commands = [[ +mkdir luajit && python -m hererocks -j 2.1.0-beta3 -r latest --patch --compat all ./luajit && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +mkdir lua5.1 && python -m hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +mkdir lua5.2 && python -m hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +mkdir lua5.3 && python -m hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +mkdir lua5.4 && python -m hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 && set "PATH=G:\VSCWorkspace\multi\luajit\bin;%PATH%" && lua -v && luarocks install multi +]] +function string.split (inputstr, sep) + local sep = sep or "\n" + local t={} + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + table.insert(t, str) + end + return t +end +local run = commands:split() +for i=1,#run do + os.execute(run[i]) +end \ No newline at end of file diff --git a/makeENV.sh b/makeENV.sh new file mode 100755 index 0000000..2840d20 --- /dev/null +++ b/makeENV.sh @@ -0,0 +1,31 @@ +#!/bin/bash +mkdir luajit +hererocks -j 2.1.0-beta3 -r latest --compat all ./luajit +. luajit/bin/activate +echo | lua -v +luarocks install multi +deactivate-lua +mkdir lua5.1 +hererocks -l 5.1 -r latest --patch --compat all ./lua5.1 +. lua5.1/bin/activate +echo | lua -v +luarocks install multi +deactivate-lua +mkdir lua5.2 +hererocks -l 5.2 -r latest --patch --compat all ./lua5.2 +. lua5.2/bin/activate +echo | lua -v +luarocks install multi +deactivate-lua +mkdir lua5.3 +hererocks -l 5.3 -r latest --patch --compat all ./lua5.3 +. lua5.3/bin/activate +echo | lua -v +luarocks install multi +deactivate-lua +mkdir lua5.4 +hererocks -l 5.4 -r latest --patch --compat all ./lua5.4 +. lua5.4/bin/activate +echo | lua -v +luarocks install multi +deactivate-lua \ No newline at end of file diff --git a/multi/compat/love2d.lua b/multi/compat/love2d.lua deleted file mode 100644 index f86191a..0000000 --- a/multi/compat/love2d.lua +++ /dev/null @@ -1,273 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2020 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -if table.unpack then - unpack=table.unpack -end -function table.val_to_str ( v ) - if "string" == type( v ) then - v = string.gsub( v, "\n", "\\n" ) - if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then - return "'" .. v .. "'" - end - return '"' .. string.gsub(v,'"', '\\"' ) .. '"' - else - return "table" == type( v ) and table.tostring( v ) or - tostring( v ) - end -end - -function table.key_to_str ( k ) - if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then - return k - else - return "[" .. table.val_to_str( k ) .. "]" - end -end - -function table.tostring( tbl ) - local result, done = {}, {} - for k, v in ipairs( tbl ) do - table.insert( result, table.val_to_str( v ) ) - done[ k ] = true - end - for k, v in pairs( tbl ) do - if not done[ k ] then - table.insert( result, - table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) - end - end - return "{" .. table.concat( result, "," ) .. "}" -end -function table.merge(t1, t2) - t1,t2= t1 or {},t2 or {} - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -Library={} -function Library.inject(lib,dat,arg) - if type(lib)=="table" then - if type(dat)=="table" then - table.merge(lib,dat) - elseif type(dat)=="string" then - if lib.Version and dat:match("(%d-)%.(%d-)%.(%d-)") then - lib.Version={dat:match("(%d+)%.(%d+)%.(%d+)")} - elseif dat=="meta" and type(arg)=="table" then - local _mt=getmetatable(lib) or {} - local mt={} - table.merge(mt,arg) - table.merge(_mt,mt) - setmetatable(lib,_mt) - elseif dat=="compat" then - lib["getVersion"]=function(self) return self.Version[1].."."..self.Version[2].."."..self.Version[3] end - if not lib.Version then - lib.Version={1,0,0} - end - elseif dat=="inhert" then - if not(lib["!%"..arg.."%!"]) then print("Wrong Password!!") return end - lib["!%"..arg.."%!"].__index=lib["!!%"..arg.."%!!"] - end - elseif type(dat)=="function" then - for i,v in pairs(lib) do - dat(lib,i,v) - end - end - elseif type(lib)=="function" or type(lib)=="userdata" then - if lib==unpack then - print("function unpack cannot yet be injected!") - return unpack - elseif lib==pairs then - print("function pairs cannot yet be injected!") - return lib - elseif lib==ipairs then - print("function ipairs cannot yet be injected!") - return lib - elseif lib==type then - print("function type cannot yet be injected!") - return lib - end - temp={} - local mt={ - __call=function(t,...) - local consume,MainRet,init={},{},{...} - local tt={} - for i=1,#t.__Link do - tt={} - if t.__Link[i]==t.__Main then - if #consume~=0 then - MainRet={t.__Link[i](unpack(consume))} - else - MainRet={t.__Link[i](unpack(init))} - end - else - if i==1 then - consume=(t.__Link[i](unpack(init))) - else - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - consume={t.__Link[i](unpack(tt))} - end - if i==#t.__Link then - return unpack(consume) - end - if consume then if consume[0]=="\1\7\6\3\2\99\125" then consume[0]=nil return unpack(consume) end end - end - end - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - return unpack(tt) - end, - } - temp.__Link={lib} - temp.__Main=lib - temp.__self=temp - function temp:inject(func,i) - if i then - table.insert(self.__Link,i,func) - else - table.insert(self.__Link,func) - end - end - function temp:consume(func) - for i=1,#self.__Link do - if self.__Link[i]==self.__Main then - self.__Link[i]=func - self.__self.__Main=func - return true - end - end - return false - end - setmetatable(temp,mt) - return temp - else - return "arg1 must be a table or a function" - end -end -function Library.convert(...) - local temp,rets={...},{} - for i=1,#temp do - if type(temp[i])=="function" then - table.insert(rets,Library.inject(temp[i])) - else - error("Takes only functions and returns in order from functions given. arg # "..i.." is not a function!!! It is a "..type(temp[i])) - end - end - return unpack(rets) -end - -local link={MainLibrary=Library} -Library.inject(Library,"meta",{ - __Link=link, - __call=function(self,func) func(link) end, -}) -local multi, thread = require("multi").init() -os.sleep = love.timer.sleep -multi.drawF = {} -function multi:onDraw(func, i) - i = i or 1 - table.insert(self.drawF, i, func) -end -multi.OnKeyPressed = multi:newConnection() -multi.OnKeyReleased = multi:newConnection() -multi.OnMousePressed = multi:newConnection() -multi.OnMouseReleased = multi:newConnection() -multi.OnMouseWheelMoved = multi:newConnection() -multi.OnMouseMoved = multi:newConnection() -multi.OnDraw = multi:newConnection() -multi.OnTextInput = multi:newConnection() -multi.OnUpdate = multi:newConnection() -multi.OnQuit = multi:newConnection() -multi.OnPreLoad(function() - local function Hook(func, conn) - if love[func] ~= nil then - love[func] = Library.convert(love[func]) - love[func]:inject(function(...) - conn:Fire(...) - return {...} - end,1) - elseif love[func] == nil then - love[func] = function(...) - conn:Fire(...) - end - end - end - Hook("quit", multi.OnQuit) - Hook("keypressed", multi.OnKeyPressed) - Hook("keyreleased", multi.OnKeyReleased) - Hook("mousepressed", multi.OnMousePressed) - Hook("mousereleased", multi.OnMouseReleased) - Hook("wheelmoved", multi.OnMouseWheelMoved) - Hook("mousemoved", multi.OnMouseMoved) - Hook("draw", multi.OnDraw) - Hook("textinput", multi.OnTextInput) - Hook("update", multi.OnUpdate) - multi.OnDraw(function() - for i = 1, #multi.drawF do - love.graphics.setColor(255, 255, 255, 255) - multi.drawF[i]() - end - end) -end) - -function multi:loveloop(light) - local link - link = multi:newThread(function() - local mainloop = love.run() - while true do - thread.yield() - pcall(mainloop) - end - end).OnError(function(...) - print(...) - end) - if light==false then - multi:mainloop() - else - multi:lightloop() - end -end - -multi.OnQuit(function() - multi.Stop() - love.event.quit() -end) -return multi diff --git a/multi/compat/lovr.lua b/multi/compat/lovr.lua deleted file mode 100644 index c37906d..0000000 --- a/multi/compat/lovr.lua +++ /dev/null @@ -1,281 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2020 Ryan Ward - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sub-license, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]] -if table.unpack then - unpack=table.unpack -end -function table.val_to_str ( v ) - if "string" == type( v ) then - v = string.gsub( v, "\n", "\\n" ) - if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then - return "'" .. v .. "'" - end - return '"' .. string.gsub(v,'"', '\\"' ) .. '"' - else - return "table" == type( v ) and table.tostring( v ) or - tostring( v ) - end -end - -function table.key_to_str ( k ) - if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then - return k - else - return "[" .. table.val_to_str( k ) .. "]" - end -end - -function table.tostring( tbl ) - local result, done = {}, {} - for k, v in ipairs( tbl ) do - table.insert( result, table.val_to_str( v ) ) - done[ k ] = true - end - for k, v in pairs( tbl ) do - if not done[ k ] then - table.insert( result, - table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) - end - end - return "{" .. table.concat( result, "," ) .. "}" -end -function table.merge(t1, t2) - t1,t2= t1 or {},t2 or {} - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -Library={} -function Library.inject(lib,dat,arg) - if type(lib)=="table" then - if type(dat)=="table" then - table.merge(lib,dat) - elseif type(dat)=="string" then - if lib.Version and dat:match("(%d-)%.(%d-)%.(%d-)") then - lib.Version={dat:match("(%d+)%.(%d+)%.(%d+)")} - elseif dat=="meta" and type(arg)=="table" then - local _mt=getmetatable(lib) or {} - local mt={} - table.merge(mt,arg) - table.merge(_mt,mt) - setmetatable(lib,_mt) - elseif dat=="compat" then - lib["getVersion"]=function(self) return self.Version[1].."."..self.Version[2].."."..self.Version[3] end - if not lib.Version then - lib.Version={1,0,0} - end - elseif dat=="inhert" then - if not(lib["!%"..arg.."%!"]) then print("Wrong Password!!") return end - lib["!%"..arg.."%!"].__index=lib["!!%"..arg.."%!!"] - end - elseif type(dat)=="function" then - for i,v in pairs(lib) do - dat(lib,i,v) - end - end - elseif type(lib)=="function" or type(lib)=="userdata" then - if lib==unpack then - print("function unpack cannot yet be injected!") - return unpack - elseif lib==pairs then - print("function pairs cannot yet be injected!") - return lib - elseif lib==ipairs then - print("function ipairs cannot yet be injected!") - return lib - elseif lib==type then - print("function type cannot yet be injected!") - return lib - end - temp={} - local mt={ - __call=function(t,...) - local consume,MainRet,init={},{},{...} - local tt={} - for i=1,#t.__Link do - tt={} - if t.__Link[i]==t.__Main then - if #consume~=0 then - MainRet={t.__Link[i](unpack(consume))} - else - MainRet={t.__Link[i](unpack(init))} - end - else - if i==1 then - consume=(t.__Link[i](unpack(init))) - else - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - consume={t.__Link[i](unpack(tt))} - end - if i==#t.__Link then - return unpack(consume) - end - if consume then if consume[0]=="\1\7\6\3\2\99\125" then consume[0]=nil return unpack(consume) end end - end - end - if type(MainRet)=="table" then - table.merge(tt,MainRet) - end - if type(consume)=="table" then - table.merge(tt,consume) - end - return unpack(tt) - end, - } - temp.__Link={lib} - temp.__Main=lib - temp.__self=temp - function temp:inject(func,i) - if i then - table.insert(self.__Link,i,func) - else - table.insert(self.__Link,func) - end - end - function temp:consume(func) - for i=1,#self.__Link do - if self.__Link[i]==self.__Main then - self.__Link[i]=func - self.__self.__Main=func - return true - end - end - return false - end - setmetatable(temp,mt) - return temp - else - return "arg1 must be a table or a function" - end -end -function Library.convert(...) - local temp,rets={...},{} - for i=1,#temp do - if type(temp[i])=="function" then - table.insert(rets,Library.inject(temp[i])) - else - error("Takes only functions and returns in order from functions given. arg # "..i.." is not a function!!! It is a "..type(temp[i])) - end - end - return unpack(rets) -end - -local link={MainLibrary=Library} -Library.inject(Library,"meta",{ - __Link=link, - __call=function(self,func) func(link) end, -}) -local multi, thread = require("multi").init() -os.sleep = lovr.timer.sleep -multi.drawF = {} -function multi:onDraw(func, i) - i = i or 1 - table.insert(self.drawF, i, func) -end -multi.OnKeyPressed = multi:newConnection() -multi.OnKeyReleased = multi:newConnection() -multi.OnErrHand = multi:newConnection() -multi.OnFocus = multi:newConnection() -multi.OnLoad = multi:newConnection() -multi.OnLog = multi:newConnection() -multi.OnPermission = multi:newConnection() -multi.OnResize = multi:newConnection() -multi.OnRestart = multi:newConnection() -multi.OnThreadError = multi:newConnection() -multi.OnDraw = multi:newConnection() -multi.OnTextInput = multi:newConnection() -multi.OnUpdate = multi:newConnection() -multi.OnQuit = multi:newConnection() -multi.OnPreLoad(function() - local function Hook(func, conn) - if lovr[func] ~= nil then - lovr[func] = Library.convert(lovr[func]) - lovr[func]:inject(function(...) - conn:Fire(...) - return {...} - end,1) - elseif lovr[func] == nil then - lovr[func] = function(...) - conn:Fire(...) - end - end - end - Hook("quit", multi.OnQuit) - Hook("keypressed", multi.OnKeyPressed) - Hook("keyreleased", multi.OnKeyReleased) - Hook("focus", multi.OnFocus) - Hook("log", multi.OnLog) - Hook("errhand", multi.OnErrHand) - Hook("load", multi.OnLoad) - Hook("draw", multi.OnDraw) - Hook("textinput", multi.OnTextInput) - Hook("update", multi.OnUpdate) - Hook("permission", multi.OnPermission) - Hook("resize", multi.OnResize) - Hook("restart", multi.OnRestart) - Hook("threaderror", multi.OnThreadError) - multi.OnDraw(function() - for i = 1, #multi.drawF do - lovr.graphics.setColor(255, 255, 255, 255) - multi.drawF[i]() - end - end) -end) - -function multi:lovrloop(light) - local link - link = multi:newThread(function() - local mainloop = lovr.run() - while true do - thread.yield() - pcall(mainloop) - end - end).OnError(function(...) - print(...) - end) - if light==false then - multi:mainloop() - else - multi:lightloop() - end -end - -multi.OnQuit(function() - multi.Stop() - lovr.event.quit() -end) -return multi diff --git a/multi/init.lua b/multi/init.lua index d78cf6f..9625e4c 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,30 +27,25 @@ local mainloopActive = false local isRunning = false local clock = os.clock local thread = {} +local in_proc = false +local processes = {} if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "15.1.0" -multi.stage = "stable" -multi.Name = "multi.root" +multi.Version = "15.3.0" +multi.Name = "root" multi.NIL = {Type="NIL"} +local NIL = multi.NIL multi.Mainloop = {} -multi.Garbage = {} -multi.ender = {} multi.Children = {} multi.Active = true multi.Type = "rootprocess" -multi.Rest = 0 -multi._type = type -multi.queue = {} -multi.clock = os.clock -multi.time = os.time multi.LinkedPath = multi -multi.lastTime = clock() multi.TIMEOUT = "TIMEOUT" multi.TID = 0 +multi.defaultSettings = {} multi.Priority_Core = 1 multi.Priority_Very_High = 4 @@ -74,130 +69,264 @@ multi.PriorityResolve = { [65536]="Idle", } -multi.PStep = 1 -multi.PList = {multi.Priority_Core,multi.Priority_Very_High,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Very_Low,multi.Priority_Idle} +local PList = {multi.Priority_Core,multi.Priority_Very_High,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Very_Low,multi.Priority_Idle} multi.PriorityTick=1 multi.Priority=multi.Priority_High multi.threshold=256 multi.threstimed=.001 - -function multi.init() - return _G["$multi"].multi,_G["$multi"].thread -end -- System function multi.Stop() - mainloopActive=false + isRunning = false + mainloopActive = false end --Processor -local priorityTable = {[0]="Round-Robin",[1]="Balanced",[2]="Top-Down",[3]="Timed-Based-Balancer"} -local ProcessName = {[true]="SubProcessor",[false]="MainProcessor"} +local priorityTable = {[false]="Disabled",[true]="Enabled"} +local ProcessName = {"SubProcessor","MainProcessor"} local globalThreads = {} -function multi:getTasksDetails(t) - if not(t) then - str = { - {"Type ","Uptime","Priority","TID"} + +function multi:getProcessors() + return processes +end + +function multi:getStats() + local stats = { + [multi.Name] = { + threads = multi:getThreads(), + tasks = multi:getTasks() } - local count = 0 - for i,v in pairs(self.Mainloop) do - local name = v.Name or "" - if name~="" then - name = " <"..name..">" - end - count = count + 1 - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) - end - for v,i in pairs(self.PausedObjects) do - local name = v.Name or "" - if name~="" then - name = " <"..name..">" - end - count = count + 1 - table.insert(str,{v.Type:sub(1,1):upper()..v.Type:sub(2,-1)..name,multi.Round(os.clock()-v.creationTime,3),self.PriorityResolve[v.Priority],v.TID}) - end - if count == 0 then - table.insert(str,{"Currently no processes running!","","",""}) - end - local s = multi.AlignTable(str) - dat = "" - dat2 = "" - if multi.SystemThreads then - for i = 1,#multi.SystemThreads do - dat2 = dat2.."\n" - end - end - local load, steps = self:getLoad() - local thread_count = 0 - local process_count = 0 - if globalThreads then - local th_tab = { - {"Thread Name","Uptime","TID","Attached To"} - } - local proc_tab = { - {"Process Name", "Uptime", "PID", "Load", "Cycles per Second per task"} - } - for th,process in pairs(globalThreads) do - if tostring(th.isProcessThread) == "destroyed" then - globalThreads[th] = nil - elseif th.isProcessThread then - local load, steps = process:getLoad() - process_count = process_count + 1 - table.insert(proc_tab,{th.Name,os.clock()-th.creationTime,(th.PID or "-1"),load,steps}) - else - thread_count = thread_count + 1 - table.insert(th_tab,{th.Name,os.clock()-th.creationTime,(th.TID or "-1"),process.Name}) - end - end - dat = multi.AlignTable(proc_tab).. "\n" - dat = dat .. "\n" .. multi.AlignTable(th_tab) - return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: "..thread_count.."\nSystemThreads Running: "..#(multi.SystemThreads or {}).."\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat..dat2.."\n\n"..s - else - return "Load on "..ProcessName[self.Type=="process"].."<"..(self.Name or "Unnamed")..">"..": "..multi.Round(load,2).."%\nCycles Per Second Per Task: "..steps.."\n\nMemory Usage: "..math.ceil(collectgarbage("count")).." KB\nProcesses Running: "..process_count.."\nThreads Running: 0\nPriority Scheme: "..priorityTable[multi.defaultSettings.priority or 0].."\n\n"..dat2.."\n\n"..s - end - else - local load,steps = self:getLoad() - str = { - ProcessName = (self.Name or "Unnamed"), - MemoryUsage = math.ceil(collectgarbage("count")), - PriorityScheme = priorityTable[multi.defaultSettings.priority or 0], - SystemLoad = multi.Round(load,2), - CyclesPerSecondPerTask = steps, - SystemThreadCount = multi.SystemThreads and #multi.SystemThreads or 0 + } + local procs = multi:getProcessors() + for i = 1, #procs do + local proc = procs[i] + stats[proc:getFullName()] = { + threads = proc:getThreads(), + tasks = proc:getTasks() } - str.Tasks = {} - str.PausedTasks = {} - str.Threads = {} - str.Processes = {} - str.Systemthreads = {} - for i,v in pairs(self.Mainloop) do - table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) - end - for v,i in pairs(multi.PausedObjects) do - table.insert(str.Tasks,{Link = v, Type=v.Type,Name=v.Name,Uptime=os.clock()-v.creationTime,Priority=self.PriorityResolve[v.Priority],TID = v.TID}) - end - for th,process in pairs(globalThreads) do - if tostring(th.isProcessThread) == "destroyed" then - globalThreads[th] = nil - elseif th.isProcessThread then - local load, steps = process:getLoad() - table.insert(str.Processes,{Uptime = os.clock()-th.creationTime, Name = th.Name, Link = th, TID = th.TID,Load = load,Steps = steps}) - else - table.insert(str.Threads,{Uptime = os.clock()-th.creationTime,Name = th.Name,Link = th,TID = th.TID,Attached_to = process}) - end - end - str.ThreadCount = #str.Threads - str.ProcessCount = #str.Processes - if multi.SystemThreads then - for i=1,#multi.SystemThreads do - table.insert(str.Systemthreads,{Uptime = os.clock()-multi.SystemThreads[i].creationTime,Name = multi.SystemThreads[i].Name,Link = multi.SystemThreads[i],TID = multi.SystemThreads[i].count}) - end - end - return str end + return stats end --Helpers +local ignoreconn = true +function multi:newConnection(protect,func,kill) + local c={} + local call_funcs = {} + local lock = false + c.callback = func + c.Parent=self + setmetatable(c,{__call=function(self,...) + local t = ... + if type(t)=="table" then + for i,v in pairs(t) do + if v==self then + local ref = self:connect(select(2,...)) + ref.root_link = select(1,...) + return ref + end + end + return self:connect(...) + else + return self:connect(...) + end + end, + __add = function(c1,c2) + cn = multi:newConnection() + if not c1.__hasInstances then + cn.__hasInstances = 2 + cn.__count = 0 + else + cn.__hasInstances = c1.__hasInstances + 1 + cn.__count = c1.__count + end + c1(function(...) + cn.__count = cn.__count + 1 + if cn.__count == cn.__hasInstances then + cn:Fire(...) + end + end) + c2(function(...) + cn.__count = cn.__count + 1 + if cn.__count == cn.__hasInstances then + cn:Fire(...) + end + end) + return cn + end}) + c.Type='connector' + c.func={} + c.ID=0 + local protect=protect or false + local connections={} + c.FC=0 + function c:hasConnections() + return #call_funcs~=0 + end + function c:holdUT(n) + local n=n or 0 + self.waiting=true + local count=0 + local id=self:connect(function() + count = count + 1 + if n<=count then + self.waiting=false + end + end) + repeat + self.Parent:uManager() + until self.waiting==false + id:Destroy() + return self + end + c.HoldUT=c.holdUT + function c:getConnection(name,ignore) + if ignore then + return connections[name] or CRef + else + return connections[name] or self + end + end + function c:Lock() + lock = true + return self + end + function c:Unlock() + lock = false + return self + end + if protect then + function c:Fire(...) + if lock then return end + for i=#call_funcs,1,-1 do + if not call_funcs[i] then return end + pcall(call_funcs[i],...) + if kill then + table.remove(call_funcs,i) + end + end + end + else + function c:Fire(...) + for i=#call_funcs,1,-1 do + call_funcs[i](...) + if kill then + table.remove(call_funcs,i) + end + end + end + end + local fast = {} + function c:getConnections() + return call_funcs + end + function c:fastMode() + function self:Fire(...) + for i=1,#fast do + fast[i](...) + end + end + function self:connect(func) + table.insert(fast,func) + end + end + function c:Bind(t) + local temp = call_funcs + call_funcs=t + return temp + end + function c:Remove() + local temp = call_funcs + call_funcs={} + return temp + end + local function conn_helper(self,func,name,num) + self.ID=self.ID+1 + if num then + table.insert(call_funcs,num,func) + else + table.insert(call_funcs,1,func) + end + local temp = { + func=func, + Type="connector_link", + Parent=self, + connect = function(s,...) + return self:connect(...) + end + } + setmetatable(temp,{ + __call=function(s,...) + return self:connect(...) + end, + __index = function(t,k) + if rawget(t,"root_link") then + return t["root_link"][k] + end + return nil + end, + __newindex = function(t,k,v) + if rawget(t,"root_link") then + t["root_link"][k] = v + end + rawset(t,k,v) + end, + }) + function temp:Fire(...) + if lock then return end + if protect then + local t=pcall(call_funcs,...) + if t then + return t + end + else + return call_funcs(...) + end + end + function temp:Destroy() + for i=#call_funcs,1,-1 do + if call_funcs[i]~=nil then + if call_funcs[i]==self.func then + table.remove(call_funcs,i) + self.remove=function() end + multi.setType(temp,multi.DestroyedObj) + end + end + end + end + if name then + connections[name]=temp + end + if self.callback then + self.callback(temp) + end + return temp + end + function c:connect(...)--func,name,num + local tab = {...} + local funcs={} + for i=1,#tab do + if type(tab[i])=="function" then + funcs[#funcs+1] = tab[i] + end + end + if #funcs>1 then + local ret = {} + for i=1,#funcs do + table.insert(ret,conn_helper(self,funcs[i])) + end + return ret + else + return conn_helper(self,tab[1],tab[2],tab[3]) + end + end + c.Connect=c.connect + c.GetConnection=c.getConnection + if not(ignoreconn) then + multi:create(c) + end + return c +end -- Used with ISO Threads local function isolateFunction(func,env) @@ -215,15 +344,7 @@ end function multi:Break() self:Pause() self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end - -function multi:OnBreak(func) - table.insert(self.ender,func) + self.OnBreak:Fire(self) end function multi:isPaused() @@ -247,14 +368,13 @@ function multi:SetTime(n) c.timer:Start() c.set=n c.link=self + c.OnTimedOut = multi:newConnection() + c.OnTimerResolved = multi:newConnection() self._timer=c.timer function c:Act() if self.timer:Get()>=self.set then self.link:Pause() - for i=1,#self.link.funcTM do - self.link.funcTM[i](self.link) - end - + self.OnTimedOut:Fire(self.link) self:Destroy() end end @@ -263,23 +383,11 @@ end function multi:ResolveTimer(...) self._timer:Pause() - for i=1,#self.funcTMR do - self.funcTMR[i](self,...) - end + self.OnTimerResolved:Fire(self,...) self:Pause() return self end -function multi:OnTimedOut(func) - self.funcTM[#self.funcTM+1]=func - return self -end - -function multi:OnTimerResolved(func) - self.funcTMR[#self.funcTMR+1]=func - return self -end - -- Timer stuff done multi.PausedObjects = {} function multi:Pause() @@ -335,7 +443,7 @@ function multi:Destroy() globalThreads = new multi.setType(self,multi.DestroyedObj) else - for i=1,#self.Parent.Mainloop do + for i=#self.Parent.Mainloop,1,-1 do if self.Parent.Mainloop[i]==self then self.Parent.OnObjectDestroyed:Fire(self) table.remove(self.Parent.Mainloop,i) @@ -343,7 +451,7 @@ function multi:Destroy() break end end - multi.setType(self,multi.DestroyedObj) + self.Act = function() end end return self end @@ -358,7 +466,7 @@ function multi:isDone() end function multi:create(ref) - multi.OnObjectCreated:Fire(ref,self) + self.OnObjectCreated:Fire(ref,self) return self end @@ -370,9 +478,9 @@ end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) - if not(self.Type=='rootprocess' or self.Type=='process' or self.Type=='queue' or self.Type == 'sandbox') then error('Can only create an object on multi or an interface obj') return false end + if not(self.Type=='rootprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end local c = {} - if self.Type=='process' or self.Type=='queue' or self.Type=='sandbox' then + if self.Type=='process' then setmetatable(c, {__index = multi}) else setmetatable(c, {__index = multi}) @@ -381,7 +489,7 @@ function multi:newBase(ins) c.func={} c.funcTM={} c.funcTMR={} - c.ender={} + c.OnBreak = multi:newConnection() c.TID = _tid c.Act=function() end c.Parent=self @@ -394,204 +502,15 @@ function multi:newBase(ins) _tid = _tid + 1 return c end + function multi:newConnector() local c = {Type = "connector"} return c end + local CRef = { Fire = function() end } -local ignoreconn = true -function multi:newConnection(protect,func,kill) - local c={} - c.callback = func - c.Parent=self - c.lock = false - setmetatable(c,{__call=function(self,...) - local t = ... - if type(t)=="table" then - for i,v in pairs(t) do - if v==self then - return self:Fire(select(2,...)) - end - end - return self:connect(...) - else - return self:connect(...) - end - end, - __add = function(c1,c2) - cn = multi:newConnection() - if not c1.__hasInstances then - cn.__hasInstances = 2 - cn.__count = 0 - else - cn.__hasInstances = c1.__hasInstances + 1 - cn.__count = c1.__count - end - c1(function(...) - cn.__count = cn.__count + 1 - if cn.__count == cn.__hasInstances then - cn:Fire(...) - end - end) - c2(function(...) - cn.__count = cn.__count + 1 - if cn.__count == cn.__hasInstances then - cn:Fire(...) - end - end) - return cn - end}) - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - c.connections={} - c.FC=0 - function c:holdUT(n) - local n=n or 0 - self.waiting=true - local count=0 - local id=self:connect(function() - count = count + 1 - if n<=count then - self.waiting=false - end - end) - repeat - self.Parent:uManager(multi.defaultSettings) - until self.waiting==false - id:Destroy() - return self - end - c.HoldUT=c.holdUT - function c:getConnection(name,ignore) - if ignore then - return self.connections[name] or CRef - else - return self.connections[name] or self - end - end - function c:Lock() - c.lock = true - return self - end - function c:Unlock() - c.lock = false - return self - end - function c:Fire(...) - local ret={} - if self.lock then return end - for i=#self.func,1,-1 do - if self.protect then - if not self.func[i] then return end - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - multi.print(temp[2]) - end - else - if not self.func[i] then return end - table.insert(ret,{self.func[i][1](...)}) - end - if kill then - table.remove(self.func,i) - end - end - return ret - end - function c:Bind(t) - local temp = self.func - self.func=t - return temp - end - function c:Remove() - local temp = self.func - self.func={} - return temp - end - local function conn_helper(self,func,name,num) - self.ID=self.ID+1 - if num then - table.insert(self.func,num,{func,self.ID}) - else - table.insert(self.func,1,{func,self.ID}) - end - local temp = { - Link=self.func, - func=func, - Type="connector_link", - ID=self.ID, - Parent=self, - connect = function(s,...) - return self:connect(...) - end - } - setmetatable(temp,{__call=function(s,...) - return self:connect(...) - end}) - function temp:Fire(...) - if self.Parent.lock then return end - if self.Parent.protect then - local t=pcall(self.func,...) - if t then - return t - end - else - return self.func(...) - end - end - function temp:Destroy() - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - multi.setType(temp,multi.DestroyedObj) - end - end - end - end - if name then - self.connections[name]=temp - end - if self.callback then - self.callback(temp) - end - return temp - end - function c:connect(...)--func,name,num - local tab = {...} - local funcs={} - for i=1,#tab do - if type(tab[i])=="function" then - funcs[#funcs+1] = tab[i] - end - end - if #funcs>1 then - local ret = {} - for i=1,#funcs do - table.insert(ret,conn_helper(self,funcs[i])) - end - return ret - else - return conn_helper(self,tab[1],tab[2],tab[3]) - end - - end - c.Connect=c.connect - c.GetConnection=c.getConnection - if not(ignoreconn) then - multi:create(c) - end - return c -end multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() @@ -633,51 +552,48 @@ end function multi:newEvent(task) local c=self:newBase() c.Type='event' - c.Task=task or function() end + local task = task or function() end function c:Act() - local t = {self.Task(self)} - if t[1] then + local t = task(self) + if t then self:Pause() self.returns = t - for _E=1,#self.func do - self.func[_E](self) - end + c.OnEvent:Fire(self) end end function c:SetTask(func) - self.Task=func - return self - end - function c:OnEvent(func) - table.insert(self.func,func) + task=func return self end + c.OnEvent = self:newConnection() self:setPriority("core") + c:SetName(c.Type) multi:create(c) return c end + function multi:newUpdater(skip) local c=self:newBase() c.Type='updater' - c.pos=1 - c.skip=skip or 1 + local pos = 1 + local skip = skip or 1 function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end + if pos >= skip then + pos = 0 + self.OnUpdate:Fire(self) end - self.pos=self.pos+1 + pos = pos+1 end function c:SetSkip(n) - self.skip=n + skip=n return self end - c.OnUpdate=self.OnMainConnect + c.OnUpdate = self:newConnection() + c:SetName(c.Type) multi:create(c) return c end + function multi:newAlarm(set) local c=self:newBase() c.Type='alarm' @@ -689,9 +605,7 @@ function multi:newAlarm(set) if clock()-t>=self.set then self:Pause() self.Active=false - for i=1,#self.func do - self.func[i](self) - end + self.OnRing:Fire(self) t = clock() end end @@ -706,64 +620,44 @@ function multi:newAlarm(set) t = clock() return self end - function c:OnRing(func) - table.insert(self.func,func) - return self - end + c.OnRing = self:newConnection() function c:Pause() count = clock() self.Parent.Pause(self) return self end + c:SetName(c.Type) multi:create(c) return c end -function multi:newLoop(func) + +function multi:newLoop(func,notime) local c=self:newBase() c.Type='loop' local start=clock() - local funcs = {} + if notime then + function c:Act() + self.OnLoop:Fire(self) + end + else + function c:Act() + self.OnLoop:Fire(self,clock()-start) + end + end + c.OnLoop = self:newConnection() + function c:fastMode() + self.OnLoop:fastMode() + end + if func then - funcs={func} - end - function c:Act() - for i=1,#funcs do - funcs[i](self,clock()-start) - end - end - function c:OnLoop(func) - table.insert(funcs,func) - return self + c.OnLoop(func) end + multi:create(c) + c:SetName(c.Type) return c end -function multi:newFunction(func) - local c={} - c.func=func - c.Type = "mfunc" - mt={ - __index=multi, - __call=function(self,...) - if self.Active then - return self:func(...) - end - return nil,true - end - } - c.Parent=self - function c:Pause() - self.Active=false - return self - end - function c:Resume() - self.Active=true - return self - end - setmetatable(c,mt) - multi:create(c) - return c -end + function multi:newStep(start,reset,count,skip) local c=self:newBase() think=1 @@ -773,8 +667,6 @@ function multi:newStep(start,reset,count,skip) c.skip=skip or 0 c.spos=0 c.count=count or 1*think - c.funcE={} - c.funcS={} c.start=start or 1 if start~=nil and reset~=nil then if start>reset then @@ -785,19 +677,13 @@ function multi:newStep(start,reset,count,skip) if self~=nil then if self.spos==0 then if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self,self.pos) + self.OnStart:Fire(self) end + self.OnStep:Fire(self,self.pos) self.pos=self.pos+self.count if self.pos-self.count==self.endAt then self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end + self.OnEnd:Fire(self) self.pos=self.start end end @@ -808,22 +694,16 @@ function multi:newStep(start,reset,count,skip) end end c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - return self - end - function c:OnStep(func) - table.insert(self.func,1,func) - return self - end - function c:OnEnd(func) - table.insert(self.funcE,func) - return self - end + c.OnStart = self:newConnection() + c.OnStep = self:newConnection() + c.OnEnd = self:newConnection() function c:Break() self.Active=nil return self end + function c:Count(count) + self.count = count + end function c:Update(start,reset,count,skip) self.start=start or self.start self.endAt=reset or self.endAt @@ -832,9 +712,11 @@ function multi:newStep(start,reset,count,skip) self:Resume() return self end + c:SetName(c.Type) multi:create(c) return c end + function multi:newTLoop(func,set) local c=self:newBase() c.Type='tloop' @@ -842,19 +724,16 @@ function multi:newTLoop(func,set) c.timer=self:newTimer() c.life=0 c:setPriority("Low") - if func then - c.func={func} - end function c:Act() if self.timer:Get()>=self.set then - print("Acting...") self.life=self.life+1 self.timer:Reset() - for i=1,#self.func do - self.func[i](self,self.life) - end + self.OnLoop:Fire(self,self.life) end end + function c:Set(set) + self.set = set + end function c:Resume() self.Parent.Resume(self) self.timer:Resume() @@ -865,31 +744,26 @@ function multi:newTLoop(func,set) self.Parent.Pause(self) return self end - function c:OnLoop(func) - table.insert(self.func,func) - return self + c.OnLoop = self:newConnection() + if func then + c.OnLoop(func) end + c:SetName(c.Type) multi:create(c) return c end + function multi:setTimeout(func,t) - multi:newThread(function() thread.sleep(t) func() end) + thread:newThread(function() thread.sleep(t) func() end) end + function multi:newTStep(start,reset,count,set) - local c=self:newBase() - think=1 + local c=self:newStep(start,reset,count) c.Type='tstep' c:setPriority("Low") - c.start=start or 1 local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} c.timer=clock() c.set=set or 1 - c.funcS={} function c:Update(start,reset,count,set) self.start=start or self.start self.pos=self.start @@ -904,38 +778,19 @@ function multi:newTStep(start,reset,count,set) if clock()-self.timer>=self.set then self:Reset() if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self,self.pos) + self.OnStart:Fire(self) end + self.OnStep:Fire(self,self.pos) self.pos=self.pos+self.count if self.pos-self.count==self.endAt then self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end + self.OnEnd:Fire(self) self.pos=self.start end end end - function c:OnStart(func) - table.insert(self.funcS,func) - return self - end - function c:OnStep(func) - table.insert(self.func,func) - return self - end - function c:OnEnd(func) - table.insert(self.funcE,func) - return self - end - function c:Break() - self.Active=nil - return self + function c:Set(set) + self.set = set end function c:Reset(n) if n then self.set=n end @@ -943,14 +798,17 @@ function multi:newTStep(start,reset,count,set) self:Resume() return self end + c:SetName(c.Type) multi:create(c) return c end + local scheduledjobs = {} local sthread + function multi:scheduleJob(time,func) if not sthread then - sthread = multi:newThread("JobScheduler",function() + sthread = thread:newThread("JobScheduler",function() local time = os.date("*t", os.time()) local ready = false while true do @@ -977,222 +835,294 @@ function multi:scheduleJob(time,func) end local __CurrentProcess = multi +local __CurrentTask + function multi.getCurrentProcess() return __CurrentProcess end +function multi.getCurrentTask() + return __CurrentTask +end + +function multi:getName() + return self.Name +end + +function multi:getFullName() + return self.Name +end + local sandcount = 1 -function multi:newProcessor(name) + +function multi:newProcessor(name,nothread) local c = {} - setmetatable(c,{__index = self}) - local multi,thread = require("multi"):init() -- We need to capture the t in thread + setmetatable(c,{__index = multi}) local name = name or "Processor_"..sandcount sandcount = sandcount + 1 c.Mainloop = {} c.Type = "process" - c.Active = false + local Active = nothread or false c.Name = name or "" - c.process = self:newThread(c.Name,function() - while true do - thread.hold(function() - return c.Active - end) - __CurrentProcess = c + c.pump = false + c.threads = {} + c.startme = {} + c.parent = self + + local handler = c:createHandler(c.threads,c.startme) + + c.process = self:newLoop(function() + if Active then c:uManager() - __CurrentProcess = self + handler() end end) + + c.process.__ignore = true + c.process.isProcessThread = true c.process.PID = sandcount c.OnError = c.process.OnError + + function c:getThreads() + return c.threads + end + + function c:getFullName() + return c.parent:getFullName() .. "." .. c.Name + end + + function c:getName() + return self.Name + end + + function c:newThread(name,func,...) + in_proc = c + local t = thread.newThread(c,name,func,...) + in_proc = false + return t + end + + function c:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return c:newThread("TempThread",func,...) + end,holdme)() + end + + function c.run() + if not Active then return end + c.pump = true + c:uManager() + handler() + c.pump = false + return c + end + + function c.isActive() + return Active + end + function c.Start() - c.Active = true - return self + Active = true + return c end + function c.Stop() - c.Active = false - return self + Active = false + return c end + function c:Destroy() - self.OnObjectDestroyed:Fire(c) + Active = false + c.process:Destroy() end - c:attachScheduler() - c.initThreads() + + table.insert(processes,c) return c end --- Threading stuff -local initT = false -local threadCount = 0 -local threadid = 0 -thread.__threads = {} -local threads = thread.__threads -local Gref = _G -multi.GlobalVariables={} -local dFunc = function() return true end -local dRef = {nil,nil,nil,nil,nil} -thread.requests = {} -function thread.request(t,cmd,...) - thread.requests[t.thread] = {cmd,{...}} -end -function thread.getRunningThread() - local threads = globalThreads - local t = coroutine.running() - if t then - for i,v in pairs(threads) do - if t==i.thread then - return v - end - end - end -end -function thread._Requests() - local t = thread.requests[coroutine.running()] - if t then - thread.requests[coroutine.running()] = nil - local cmd,args = t[1],t[2] - thread[cmd](unpack(args)) - end -end -function thread.sleep(n) - thread._Requests() - thread.getRunningThread().lastSleep = clock() - dRef[1] = "_sleep_" - dRef[2] = n or 0 - return coroutine.yield(dRef) -end --- function thread.hold(n) --- thread._Requests() --- dRef[1] = "_hold_" --- dRef[2] = n or dFunc --- return coroutine.yield(dRef) --- end -function thread.hold(n,opt) - thread._Requests() - if opt and type(opt)=="table" then - if opt.interval then - dRef[4] = opt.interval - end - if opt.cycles then - dRef[1] = "_holdW_" - dRef[2] = opt.cycles or 1 - dRef[3] = n or dFunc - return coroutine.yield(dRef) - elseif opt.sleep then - dRef[1] = "_holdF_" - dRef[2] = opt.sleep - dRef[3] = n or dFunc - return coroutine.yield(dRef) - elseif opt.skip then - dRef[1] = "_skip_" - dRef[2] = opt.skip or 1 - return coroutine.yield(dRef) - end - end - if type(n) == "number" then - thread.getRunningThread().lastSleep = clock() - dRef[1] = "_sleep_" - dRef[2] = n or 0 - return coroutine.yield(dRef) - else - dRef[1] = "_hold_" - dRef[2] = n or dFunc - return coroutine.yield(dRef) - end -end -function thread.holdFor(sec,n) - thread._Requests() - dRef[1] = "_holdF_" - dRef[2] = sec - dRef[3] = n or dFunc - return coroutine.yield(dRef) -end -function thread.holdWithin(skip,n) - thread._Requests() - dRef[1] = "_holdW_" - dRef[2] = skip or 1 - dRef[3] = n or dFunc - return coroutine.yield(dRef) -end -function thread.skip(n) - thread._Requests() - dRef[1] = "_skip_" - dRef[2] = n or 1 - return coroutine.yield(dRef) -end -function thread.kill() - dRef[1] = "_kill_" - dRef[2] = "T_T" - return coroutine.yield(dRef) -end -function thread.yield() - thread._Requests() - return thread.sleep(0) -end -function thread.isThread() - if _VERSION~="Lua 5.1" then - local a,b = coroutine.running() - return not(b) - else - return coroutine.running()~=nil - end -end -function thread.getCores() - return thread.__CORES -end -function thread.set(name,val) - multi.GlobalVariables[name]=val - return true -end -function thread.get(name) - return multi.GlobalVariables[name] -end -function thread.waitFor(name) - thread.hold(function() return thread.get(name)~=nil end) - return thread.get(name) -end -function multi.hold(func,no) - if thread.isThread() and not(no) then +function multi.hold(func,opt) + if thread.isThread() then if type(func) == "function" or type(func) == "table" then - return thread.hold(func) + return thread.hold(func,opt) end return thread.sleep(func) end local death = false + local proc = multi.getCurrentTask() + proc:Pause() if type(func)=="number" then - self:newThread("Hold_func",function() - thread.sleep(func) + thread:newThread("Hold_func",function() + thread.hold(func) death = true end) while not death do - multi.scheduler:Act() + multi:uManager() end + proc:Resume() else local rets - self:newThread("Hold_func",function() - rets = {thread.hold(func)} + thread:newThread("Hold_func",function() + rets = {thread.hold(func,opt)} death = true end) while not death do - multi.scheduler:Act() + multi:uManager() end + proc:Resume() return unpack(rets) end end -function multi.holdFor(n,func) - local temp - multi.getCurrentProcess():newThread(function() - thread.sleep(n) - temp = true - end) - return multi.getCurrentProcess().hold(function() - if func() then - return func() - elseif temp then - return multi.NIL, multi.TIMEOUT - end - end) + +-- Threading stuff +local threadCount = 0 +local threadid = 0 +thread.__threads = {} +local threads = thread.__threads +multi.GlobalVariables={} +local dFunc = function() return true end +thread.requests = {} +local CMD = {} -- We will compare this special local +local interval +local resume, status, create, yield, running = coroutine.resume, coroutine.status, coroutine.create, coroutine.yield, coroutine.running + +local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 + +function multi:getThreads() + return threads end + +function multi:getTasks() + local tasks = {} + for i,v in pairs(self.Mainloop) do + if not v.__ignore then + tasks[#tasks+1] = v + end + end + return tasks +end + +function thread.request(t,cmd,...) + thread.requests[t.thread] = {cmd,{...}} +end + +function thread.getRunningThread() + local threads = globalThreads + local t = coroutine.running() + if t then + for th,process in pairs(threads) do + if t==th.thread then + return th + end + end + end +end + +function thread._Requests() + local t = thread.requests[running()] + if t then + thread.requests[running()] = nil + local cmd,args = t[1],t[2] + thread[cmd](unpack(args)) + end +end + +function thread.sleep(n) + thread._Requests() + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 1) +end + +function thread.hold(n,opt) + thread._Requests() + local opt = opt or {} + if type(opt)=="table" then + interval = opt.interval + if opt.cycles then + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == "connector" then + local rdy = function() + return false + end + n(function(a1,a2,a3,a4,a5,a6) + rdy = function() + if a1==nil then + return NIL,a2,a3,a4,a5,a6 + end + return a1,a2,a3,a4,a5,a6 + end + end) + return yield(CMD, t_hold, function() + return rdy() + end, nil, interval) + elseif type(n) == "function" then + return yield(CMD, t_hold, n or dFunc, nil, interval) + else + error("Invalid argument passed to thread.hold(...)!") + end +end + +function thread.holdFor(sec,n) + thread._Requests() + return yield(CMD, t_holdF, sec, n or dFunc) +end + +function thread.holdWithin(skip,n) + thread._Requests() + return yield(CMD, t_holdW, skip or 1, n or dFunc) +end + +function thread.skip(n) + thread._Requests() + return yield(CMD, t_skip, n or 1) +end + +function thread.kill() + error("thread killed!") +end + +function thread.yield() + thread._Requests() + return yield(CMD, t_yield) +end + +function thread.isThread() + if _VERSION~="Lua 5.1" then + local a,b = running() + return not(b) + else + return running()~=nil + end +end + +function thread.getCores() + return thread.__CORES +end + +function thread.set(name,val) + multi.GlobalVariables[name]=val + return true +end + +function thread.get(name) + return multi.GlobalVariables[name] +end + +function thread.waitFor(name) + thread.hold(function() return thread.get(name)~=nil end) + return thread.get(name) +end + local function cleanReturns(...) local returns = {...} local rets = {} @@ -1205,85 +1135,100 @@ local function cleanReturns(...) end return unpack(returns,1,ind) end + function thread.pushStatus(...) local t = thread.getRunningThread() t.statusconnector:Fire(...) end -function thread:newFunction(func,holdme) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - return multi.hold(function() - if err then - return multi.NIL, err - elseif rets then - return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) - end - end) - else - while not rets and not err do - multi.scheduler:Act() - end - if err then - return nil,err - end - return cleanReturns(rets[1],rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) + +local handler + +function thread:newFunctionBase(generator,holdme) + return function() + local tfunc = {} + tfunc.Active = true + function tfunc:Pause() + self.Active = false end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" + function tfunc:Resume() + self.Active = true + end + function tfunc:holdMe(b) + holdme = b + end + local function noWait() + return nil, "Function is paused" + end + local rets, err + local function wait() + if thread.isThread() then + return thread.hold(function() + if err then + return multi.NIL, err + elseif rets then + return cleanReturns((rets[1] or multi.NIL),rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) + end + end) + else + while not rets and not err do + handler() + end + if err then + return nil,err + end + return cleanReturns(rets[1],rets[2],rets[3],rets[4],rets[5],rets[6],rets[7],rets[8],rets[9],rets[10],rets[11],rets[12],rets[13],rets[14],rets[15],rets[16]) end - return { + end + tfunc.__call = function(t,...) + if not t.Active then + if holdme then + return nil, "Function is paused" + end + return { + isTFunc = true, + wait = noWait, + connect = function(f) + f(nil,"Function is paused") + end + } + end + local t = generator(...) + t.OnDeath(function(...) rets = {...} end) + t.OnError(function(self,e) err = e end) + if holdme then + return wait() + end + local temp = { + OnStatus = multi:newConnection(true), + OnError = multi:newConnection(true), + OnReturn = multi:newConnection(true), isTFunc = true, - wait = noWait, + wait = wait, + getReturns = function() + return unpack(rets) + end, connect = function(f) - f(nil,"Function is paused") + local tempConn = multi:newConnection(true) + t.OnDeath(function(...) if f then f(...) else tempConn:Fire(...) end end) + t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) + return tempConn end } - end - local t = multi.getCurrentProcess():newThread("TempThread",func,...) - t.OnDeath(function(self,status,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() + t.OnDeath(function(...) temp.OnReturn:Fire(...) end) + t.OnError(function(self,err) temp.OnError:Fire(err) end) + t.linkedFunction = temp + t.statusconnector = temp.OnStatus + return temp end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,status,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,status,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp + setmetatable(tfunc,tfunc) + return tfunc end - setmetatable(tfunc,tfunc) - return tfunc +end + +function thread:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return thread:newThread("TempThread",func,...) + end,holdme)() end -- A cross version way to set enviroments, not the same as fenv though @@ -1293,308 +1238,318 @@ function multi.setEnv(func,env) return chunk end -function multi:attachScheduler() - local threads = {} - self.threadsRef = threads - function self:newThread(name,func,...) - self.OnLoad:Fire() - local func = func or name - if type(name) == "function" then - name = "Thread#"..threadCount - end - local c={} - local env = {self=c} - c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} - c.startArgs = {...} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.Type="thread" - c.TID = threadid - c.firstRunDone=false - c.timer=self:newTimer() - c._isPaused = false - c.returns = {} - c.isError = false - c.OnError = self:newConnection(true,nil,true) - c.OnDeath = self:newConnection(true,nil,true) - function c:isPaused() - return self._isPaused - end - local resumed = false - function c:Pause() - if not self._isPaused then - thread.request(self,"exec",function() - thread.hold(function() - return resumed - end) - resumed = false - self._isPaused = false +local threads = {} +local startme = {} +local startme_len = 0 +function thread:newThread(name,func,...) + multi.OnLoad:Fire() -- This was done incase a threaded function was called before mainloop/uManager was called + local func = func or name + + if type(name) == "function" then + name = "Thread#"..threadCount + end + local c={nil,nil,nil,nil,nil,nil,nil} + local env = {self=c} + c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} + c.startArgs = {...} + c.ref={} + c.Name=name + c.thread=create(func) + c.sleep=1 + c.Type="thread" + c.TID = threadid + c.firstRunDone=false + c._isPaused = false + c.returns = {} + c.isError = false + c.OnError = multi:newConnection(true,nil,true) + c.OnDeath = multi:newConnection(true,nil,true) + + function c:getName() + return c.Name + end + + function c:isPaused() + return self._isPaused + end + + local resumed = false + function c:Pause() + if not self._isPaused then + thread.request(self,"exec",function() + thread.hold(function() + return resumed end) - self._isPaused = true - end - return self + resumed = false + self._isPaused = false + end) + self._isPaused = true end - function c:Resume() - resumed = true - return self - end - function c:Kill() - thread.request(self,"kill") - return self - end - c.Destroy = c.Kill - c.kill = c.Kill - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - dRef[1] = "_kill_" - dRef[2] = "I Was killed by You!" - err = coroutine.yield(dRef) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - if type(n)=="function" then - ret=thread.hold(n) - elseif type(n)=="number" then - ret=thread.sleep(tonumber(n) or 0) - else - error("Invalid Type for sleep!") - end - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(threads,c) - globalThreads[c] = self - if initT==false then - self.initThreads() - end - c.creationTime = os.clock() - threadid = threadid + 1 - multi:create(c) - return c + return self end - function self:newISOThread(name,func,_env,...) - self.OnLoad:Fire() - local func = func or name - local env = _env or {} - if not env.thread then - env.thread = thread - end - if not env.multi then - env.multi = self - end - if type(name) == "function" then - name = "Thread#"..threadCount - end - local func = isolateFunction(func,env) - return self:newThread(name,func) + + function c:Resume() + resumed = true + return self end - function self.initThreads(justThreads) - initT = true - self.scheduler=self:newLoop():setName("multi.thread") - self.scheduler.Type="scheduler" - function self.scheduler:setStep(n) - self.skip=tonumber(n) or 24 - end - self.scheduler.skip=0 - local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 - local r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 - local ret,_ - local function CheckRets(i) - if threads[i] and not(threads[i].isError) then - if not _ then - threads[i].isError = true - threads[i].TempRets[1] = ret - return - end - if ret or r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 or r9 or r10 or r11 or r12 or r13 or r14 or r15 or r16 then - threads[i].TempRets[1] = ret - threads[i].TempRets[2] = r1 - threads[i].TempRets[3] = r2 - threads[i].TempRets[4] = r3 - threads[i].TempRets[5] = r4 - threads[i].TempRets[6] = r5 - threads[i].TempRets[7] = r6 - threads[i].TempRets[8] = r7 - threads[i].TempRets[9] = r8 - threads[i].TempRets[10] = r9 - threads[i].TempRets[11] = r10 - threads[i].TempRets[12] = r11 - threads[i].TempRets[13] = r12 - threads[i].TempRets[14] = r13 - threads[i].TempRets[15] = r14 - threads[i].TempRets[16] = r15 - threads[i].TempRets[17] = r16 - end - end - end - local function holdconn(n) - if type(ret[n])=="table" and ret[n].Type=='connector' then - local letsgo - ret[n](function(...) letsgo = {...} end) - ret[n] = function() - if letsgo then - return unpack(letsgo) - end - end - end - end - local function helper(i) - if type(ret)=="table" then - if ret[1]=="_kill_" then - threads[i].OnDeath:Fire(threads[i],"killed",ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - ret = nil - elseif ret[1]=="_sleep_" then - threads[i].sec = ret[2] - threads[i].time = clock() - threads[i].task = "sleep" - threads[i].__ready = false - ret = nil - elseif ret[1]=="_skip_" then - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].task = "skip" - threads[i].__ready = false - ret = nil - elseif ret[1]=="_hold_" then - holdconn(2) - threads[i].func = ret[2] - threads[i].task = "hold" - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - elseif ret[1]=="_holdF_" then - holdconn(3) - threads[i].sec = ret[2] - threads[i].func = ret[3] - threads[i].task = "holdF" - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - elseif ret[1]=="_holdW_" then - holdconn(3) - threads[i].count = ret[2] - threads[i].pos = 0 - threads[i].func = ret[3] - threads[i].task = "holdW" - threads[i].time = clock() - threads[i].__ready = false - threads[i].interval = ret[4] or 0 - threads[i].intervalR = clock() - ret = nil - end - end - CheckRets(i) - end - self.scheduler:OnLoop(function(self) - for i=#threads,1,-1 do - if threads[i].isError then - if coroutine.status(threads[i].thread)=="dead" then - threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - end - end - if threads[i] and not threads[i].__started then - if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,unpack(threads[i].startArgs)) - end - threads[i].__started = true - helper(i) - end - if threads[i] and not _ then - threads[i].OnError:Fire(threads[i],unpack(threads[i].TempRets)) - threads[i].isError = true - end - if threads[i] and coroutine.status(threads[i].thread)=="dead" then - local t = threads[i].TempRets or {} - threads[i].OnDeath:Fire(threads[i],"ended",t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10],t[11],t[12],t[13],t[14],t[15],t[16]) - self.setType(threads[i],self.DestroyedObj) - table.remove(threads,i) - elseif threads[i] and threads[i].task == "skip" then - threads[i].pos = threads[i].pos + 1 - if threads[i].count==threads[i].pos then - threads[i].task = "" - threads[i].__ready = true - end - elseif threads[i] and threads[i].task == "hold" then - if clock() - threads[i].intervalR>=threads[i].interval then - t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = threads[i].func() - if t0 then - if t0==self.NIL then - t0 = nil - end - threads[i].task = "" - threads[i].__ready = true - end - threads[i].intervalR = clock() - end - elseif threads[i] and threads[i].task == "sleep" then - if clock() - threads[i].time>=threads[i].sec then - threads[i].task = "" - threads[i].__ready = true - end - elseif threads[i] and threads[i].task == "holdF" then - if clock() - threads[i].intervalR>=threads[i].interval then - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = threads[i].func() - if t0 then - threads[i].task = "" - threads[i].__ready = true - elseif clock() - threads[i].time>=threads[i].sec then - threads[i].task = "" - threads[i].__ready = true - t0 = nil - t1 = multi.TIMEOUT - end - threads[i].intervalR = clock() - end - elseif threads[i] and threads[i].task == "holdW" then - if clock() - threads[i].intervalR>=threads[i].interval then - threads[i].pos = threads[i].pos + 1 - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = threads[i].func() - if t0 then - threads[i].task = "" - threads[i].__ready = true - elseif threads[i].count==threads[i].pos then - threads[i].task = "" - threads[i].__ready = true - t0 = nil - t1 = multi.TIMEOUT - end - threads[i].intervalR = clock() - end - end - if threads[i] and threads[i].__ready then - threads[i].__ready = false - if coroutine.running() ~= threads[i].thread then - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=coroutine.resume(threads[i].thread,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - CheckRets(i) - end - end - helper(i) - end + + function c:Kill() + thread.request(self,"kill") + return self + end + + function c:Sleep(n) + thread.request(self,"exec",function() + thread.sleep(n) + resumed = false end) - if justThreads then - while true do - self.scheduler:Act() + return self + end + + function c:Hold(n,opt) + thread.request(self,"exec",function() + thread.hold(n,opt) + resumed = false + end) + return self + end + + c.Destroy = c.Kill + + if self.Type=="process" then + table.insert(self.startme,c) + else + table.insert(startme,c) + end + + startme_len = #startme + globalThreads[c] = multi + threadid = threadid + 1 + multi:create(c) + c.creationTime = os.clock() + return c +end + +function thread:newISOThread(name,func,_env,...) + local func = func or name + local env = _env or {} + if not env.thread then + env.thread = thread + end + if not env.multi then + env.multi = multi + end + if type(name) == "function" then + name = "Thread#"..threadCount + end + local func = isolateFunction(func,env) + return thread:newThread(name,func,...) +end + +multi.newThread = thread.newThread +multi.newISOThread = thread.newISOThread + +local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 +local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 +local ret,_ +local task, thd, ref, ready +local switch = { + function(th,co)--hold + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--sleep + if clock() - th.time>=th.sec then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdf + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif clock() - th.time>=th.sec then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--skip + th.pos = th.pos + 1 + if th.count==th.pos then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdw + if clock() - th.intervalR>=th.interval then + th.pos = th.pos + 1 + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = "" + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif th.count==th.pos then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--yield + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end, + function() end--none +} + +setmetatable(switch,{__index=function() return function() end end}) + +local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + function(th,arg1,arg2,arg3) + th.func = arg1 + th.task = t_hold + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.time = clock() + th.task = t_sleep + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.func = arg2 + th.task = t_holdF + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.task = t_skip + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.func = arg2 + th.task = t_holdW + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.task = t_yield + end, + function() end +} +setmetatable(cmds,{__index=function() return function() end end}) +local co_status +co_status = { + ["suspended"] = function(thd,ref,task,i,th) + switch[task](ref,thd) + cmds[r1](ref,r2,r3,r4,r5) + if ret ~= CMD and _ ~= nil then -- The rework makes this necessary + co_status["dead"](thd,ref,task,i,th) + end + r1=nil r2=nil r3=nil r4=nil r5=nil + end, + ["normal"] = function(thd,ref) end, + ["running"] = function(thd,ref) end, + ["dead"] = function(thd,ref,task,i,th) + if ref.__processed then return end + if _ then + ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + else + ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + end + if i then + table.remove(th,i) + else + for i,v in pairs(th) do + if v.thread==thd then + table.remove(th,i) + break + end end end + _=nil r1=nil r2=nil r3=nil r4=nil r5=nil + ref.__processed = true + end, +} +handler = coroutine.wrap(function(self) + local temp_start + while true do + for start = #startme, 1, -1 do + temp_start = startme[start] + table.remove(startme) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread,temp_start,t_none,nil,threads) -- Make sure there was no error + table.insert(threads,temp_start) + yield() + end + for i=#threads,1,-1 do + ref = threads[i] + if ref then + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd,ref,task,i,threads) + end + yield() + end + yield() end +end) + +function multi:createHandler(threads,startme) + return coroutine.wrap(function(self) + local temp_start + while true do + for start = #startme, 1, -1 do + temp_start = startme[start] + table.remove(startme) + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = resume(temp_start.thread,unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread,temp_start,t_none,nil,threads) -- Make sure there was no error + table.insert(threads,temp_start) + yield() + end + for i=#threads,1,-1 do + ref = threads[i] + if ref then + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd,ref,task,i,threads) + end + yield() + end + yield() + end + end) end + function multi:newService(func) -- Priority managed threads local c = {} c.Type = "service" @@ -1607,6 +1562,7 @@ function multi:newService(func) -- Priority managed threads local ap local task = thread.sleep local scheme = 1 + function c.Start() if not active then time = self:newTimer() @@ -1615,7 +1571,8 @@ function multi:newService(func) -- Priority managed threads c:OnStarted(c,Service_Data) end return c - end + end + local function process() thread.hold(function() return active @@ -1624,18 +1581,22 @@ function multi:newService(func) -- Priority managed threads task(ap) return c end - local th = self:newThread(function() + + local th = thread:newThread(function() while true do process() end end) + th.OnError = c.OnError -- use the threads onerror as our own + function c.Destroy() th:kill() c.Stop() multi.setType(c,multi.DestroyedObj) return c end + function c:SetScheme(n) if type(self)=="number" then n = self end scheme = n @@ -1651,6 +1612,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Stop() if active then c:OnStopped(c) @@ -1662,6 +1624,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Pause() if active then time:Pause() @@ -1669,6 +1632,7 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.Resume() if not active then time:Resume() @@ -1676,462 +1640,138 @@ function multi:newService(func) -- Priority managed threads end return c end + function c.GetUpTime() return time:Get() end + function c:SetPriority(pri) if type(self)=="number" then pri = self end p = pri c.SetScheme(scheme) return c end + multi.create(multi,c) + return c end + -- Multi runners -function multi:threadloop() - multi.initThreads(true) -end -function multi:lightloop(settings) - multi.defaultSettings = settings or multi.defaultSettings - multi.OnPreLoad:Fire() - if not isRunning then - local Loop=self.Mainloop - while true do - for _D=#Loop,1,-1 do - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - end - end - end - end - end -end -function multi:mainloop(settings) +local function mainloop(self) __CurrentProcess = self multi.OnPreLoad:Fire() - multi.defaultSettings = settings or multi.defaultSettings - self.uManager=self.uManagerRef - local p_c,p_h,p_an,p_n,p_bn,p_l,p_i = self.Priority_Core,self.Priority_High,self.Priority_Above_Normal,self.Priority_Normal,self.Priority_Below_Normal,self.Priority_Low,self.Priority_Idle - local P_LB = p_i + self.uManager = self.uManagerRef if not isRunning then - local protect = false - local priority = false - local stopOnError = true - local delay = 3 - if settings then - priority = settings.priority - if settings.auto_priority then - priority = -1 - end - if settings.preLoop then - settings.preLoop(self) - end - if settings.stopOnError then - stopOnError = settings.stopOnError - end - if settings.auto_stretch then - p_i = p_i * settings.auto_stretch - end - if settings.auto_delay then - delay = settings.auto_delay - end - if settings.auto_lowerbound then - P_LB = settings.auto_lowerbound - end - protect = settings.protect - end - local t,tt = clock(),0 - isRunning=true - local lastTime = clock() - rawset(self,'Start',clock()) + isRunning = true mainloopActive = true local Loop=self.Mainloop - local PS=self - local PStep = 1 - local autoP = 0 - local solid,sRef - local cc=0 + local ctask multi.OnLoad:Fire() while mainloopActive do - if priority == 1 then - for _D=#Loop,1,-1 do - for P=1,7 do - if Loop[_D] then - if (PS.PList[P])%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - end - elseif priority == 2 then - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - PStep=PStep+1 - if PStep==p_i then - PStep=0 - end - elseif priority == 3 then - cc=cc+1 - if cc == 1000 then - tt = clock()-t - t = clock() - cc=0 - end - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Priority == p_c or (Loop[_D].Priority == p_h and tt<.5) or (Loop[_D].Priority == p_an and tt<.125) or (Loop[_D].Priority == p_n and tt<.063) or (Loop[_D].Priority == p_bn and tt<.016) or (Loop[_D].Priority == p_l and tt<.003) or (Loop[_D].Priority == p_i and tt<.001) then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - elseif priority == -1 then - for _D=#Loop,1,-1 do - sRef = Loop[_D] - if Loop[_D] then - if (sRef.Priority == p_c) or PStep==0 then - if sRef.Active then - self.CID=_D - if not protect then - if sRef.solid then - sRef:Act() - __CurrentProcess = self - solid = true - else - time = multi.timer(sRef.Act,sRef) - sRef.solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = p_c - else - Loop[_D].Priority = P_LB - end - end - else - if Loop[_D].solid then - Loop[_D]:Act() - __CurrentProcess = self - solid = true - else - time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - Loop[_D].solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = p_c - else - Loop[_D].Priority = P_LB - end - end - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - PStep=PStep+1 - if PStep>p_i then - PStep=0 - if clock()-lastTime>delay then - lastTime = clock() - for i = 1,#Loop do - Loop[i]:ResetPriority() - end - end - end - else - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + ctask:Act() + __CurrentProcess = self end + handler() end else - return "Already Running!" + return nil, "Already Running!" end end -function multi:uManager(settings) + +multi.mainloop = mainloop + +local function p_mainloop(self) __CurrentProcess = self multi.OnPreLoad:Fire() - multi.defaultSettings = settings or multi.defaultSettings - self.t,self.tt = clock(),0 - if settings then - priority = settings.priority - if settings.auto_priority then - priority = -1 + self.uManager = self.uManagerRefP1 + if not isRunning then + isRunning=true + mainloopActive = true + local Loop = self.Mainloop + local ctask + multi.OnLoad:Fire() + while mainloopActive do + for task=#Loop,1,-1 do + __CurrentTask = Loop[task] + ctask = __CurrentTask + for i=1,9 do + if PList[i]%ctask.Priority == 0 then + ctask:Act() + __CurrentProcess = self + end + end + end + handler() end - if settings.preLoop then - settings.preLoop(self) - end - if settings.stopOnError then - stopOnError = settings.stopOnError - end - multi.defaultSettings.p_i = self.Priority_Idle - if settings.auto_stretch then - multi.defaultSettings.p_i = settings.auto_stretch*self.Priority_Idle - end - multi.defaultSettings.delay = settings.auto_delay or 3 - multi.defaultSettings.auto_lowerbound = settings.auto_lowerbound or self.Priority_Idle - protect = settings.protect + else + return nil, "Already Running!" end - multi.OnLoad:Fire() - self.uManager=self.uManagerRef end -function multi:uManagerRef(settings) - if self.Active then - local Loop=self.Mainloop - local PS=self - if multi.defaultSettings.priority==1 then - for _D=#Loop,1,-1 do - for P=1,7 do - if Loop[_D] then - if (PS.PList[P])%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D - if not multi.defaultSettings.protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - end - elseif multi.defaultSettings.priority==2 then - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - self.CID=_D - if not multi.defaultSettings.protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=0 - end - elseif priority == 3 then - self.tt = clock()-self.t - self.t = clock() - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Priority == self.Priority_Core or (Loop[_D].Priority == self.Priority_High and tt<.5) or (Loop[_D].Priority == self.Priority_Above_Normal and tt<.125) or (Loop[_D].Priority == self.Priority_Normal and tt<.063) or (Loop[_D].Priority == self.Priority_Below_Normal and tt<.016) or (Loop[_D].Priority == self.Priority_Low and tt<.003) or (Loop[_D].Priority == self.Priority_Idle and tt<.001) then - if Loop[_D].Active then - self.CID=_D - if not protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - elseif priority == -1 then - for _D=#Loop,1,-1 do - local sRef = Loop[_D] - if Loop[_D] then - if (sRef.Priority == self.Priority_Core) or PStep==0 then - if sRef.Active then - self.CID=_D - if not protect then - if sRef.solid then - sRef:Act() - __CurrentProcess = self - solid = true - else - time = multi.timer(sRef.Act,sRef) - sRef.solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = self.Priority_Core - else - Loop[_D].Priority = multi.defaultSettings.auto_lowerbound - end - end - else - if Loop[_D].solid then - Loop[_D]:Act() - __CurrentProcess = self - solid = true - else - time, status, err=multi.timer(pcall,Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - Loop[_D].solid = true - solid = false - end - if Loop[_D] and not solid then - if time == 0 then - Loop[_D].Priority = self.Priority_Core - else - Loop[_D].Priority = multi.defaultSettings.auto_lowerbound - end - end - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - if multi.defaultSettings.stopOnError then - Loop[_D]:Destroy() - end - end - end - end - end - end - end - self.PStep=self.PStep+1 - if self.PStep>multi.defaultSettings.p_i then - self.PStep=0 - if clock()-self.lastTime>multi.defaultSettings.delay then - self.lastTime = clock() - for i = 1,#Loop do - Loop[i]:ResetPriority() - end - end - end + +local init = false +function multi.init(settings, realsettings) + if settings == multi then settings = realsettings end + if init then return _G["$multi"].multi,_G["$multi"].thread end + init = true + if type(settings)=="table" then + multi.defaultSettings = settings + if settings.priority then + multi.mainloop = p_mainloop else - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - self.CID=_D - if not multi.defaultSettings.protect then - Loop[_D]:Act() - __CurrentProcess = self - else - local status, err=pcall(Loop[_D].Act,Loop[_D]) - __CurrentProcess = self - if err then - Loop[_D].error=err - self.OnError:Fire(Loop[_D],err) - end - end - end + multi.mainloop = mainloop + end + end + return _G["$multi"].multi,_G["$multi"].thread +end + +function multi:uManager() + if self.Active then + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager=self.uManagerRef + multi.OnLoad:Fire() + handler() + end +end + +function multi:uManagerRefP1() + if self.Active then + __CurrentProcess = self + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + for P=1,9 do + if PList[P]%__CurrentTask.Priority==0 then + __CurrentTask:Act() + __CurrentProcess = self end end end + handler() end end + +function multi:uManagerRef() + if self.Active then + __CurrentProcess = self + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + __CurrentTask:Act() + __CurrentProcess = self + end + handler() + end +end + -------- -- UTILS -------- + function table.merge(t1, t2) for k,v in pairs(t2) do if type(v) == 'table' then @@ -2146,9 +1786,11 @@ function table.merge(t1, t2) end return t1 end + if table.unpack and not unpack then unpack=table.unpack end + multi.DestroyedObj = { Type = "destroyed", } @@ -2176,16 +1818,11 @@ setmetatable(multi.DestroyedObj, { end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni }) math.randomseed(os.time()) -multi.defaultSettings = { - priority = 0, - protect = false, -} function multi:enableLoadDetection() if multi.maxSpd then return end -- here we are going to run a quick benchMark solo local temp = self:newProcessor() - temp:Start() local t = os.clock() local stop = false temp:benchMark(.01):OnBench(function(time,steps) @@ -2198,40 +1835,27 @@ function multi:enableLoadDetection() multi.maxSpd = stop end -local busy = false local lastVal = 0 local last_step = 0 -local bb = 0 function multi:getLoad() - if not multi.maxSpd then self:enableLoadDetection() end - if busy then return lastVal,last_step end + if not multi.maxSpd then multi:enableLoadDetection() end local val = nil - if thread.isThread() then - local bench - self:benchMark(.01):OnBench(function(time,steps) - bench = steps - bb = steps - end) - thread.hold(function() - return bench - end) - bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) - else - busy = true - local bench - self:benchMark(.01):OnBench(function(time,steps) - bench = steps - bb = steps - end) - while not bench do - self:uManager() - end - bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) - busy = false + local bench + local bb + self:benchMark(.01).OnBench(function(time,steps) + bench = steps + bb = steps + end) + _,timeout = multi.hold(function() + return bench + end,{sleep=.012}) + if timeout or not bench then + bench = 0 + bb = 0 end + bench = bench^1.5 + val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) if val<0 then val = 0 end if val > 100 then val = 100 end lastVal = val @@ -2240,7 +1864,7 @@ function multi:getLoad() end function multi:setPriority(s) - if type(s)==number then + if type(s)=="number" then self.Priority=s elseif type(s)=='string' then if s:lower()=='core' or s:lower()=='c' then @@ -2262,7 +1886,6 @@ function multi:setPriority(s) elseif s:lower()=='idle' or s:lower()=='i' then self.Priority=self.Priority_Idle end - self.solid = true end if not self.PrioritySet then self.defPriority = self.Priority @@ -2303,10 +1926,6 @@ function multi.randomString(n) return str end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end - function multi:getChildren() return self.Mainloop end @@ -2329,12 +1948,6 @@ function multi:canSystemThread() return false end -function multi:getError() - if self.error then - return self.error - end -end - function multi:benchMark(sec,p,pt) local c = 0 local temp=self:newLoop(function(self,t) @@ -2342,17 +1955,14 @@ function multi:benchMark(sec,p,pt) if pt then multi.print(pt.." "..c.." Steps in "..sec.." second(s)!") end - self.tt(sec,c) + self.OnBench:Fire(sec,c) self:Destroy() else c=c+1 end end) + temp.OnBench = multi:newConnection() temp:setPriority(p or 1) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end return temp end @@ -2391,7 +2001,12 @@ function multi.AlignTable(tab) end function multi:endTask(TID) - self.Mainloop[TID]:Destroy() + for i=#self.Mainloop,1,-1 do + if self.Mainloop[i].TID == TID then + self.Mainloop[TID]:Destroy() + return self + end + end return self end @@ -2418,30 +2033,6 @@ function multi.timer(func,...) return t,unpack(args) end -function multi:OnMainConnect(func) - table.insert(self.func,func) - return self -end - -function multi:FreeMainEvent() - self.func={} - return self -end - -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - multi.print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(func) to trigger it's final event!") - self:OnBreak(func) - end - return self -end - if os.getOS()=="windows" then thread.__CORES=tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else @@ -2458,7 +2049,6 @@ multi.GetType=multi.getType multi.IsPaused=multi.isPaused multi.IsActive=multi.isActive multi.Reallocate=multi.Reallocate -multi.GetParentProcess=multi.getParentProcess multi.ConnectFinal=multi.connectFinal multi.ResetTime=multi.SetTime multi.IsDone=multi.isDone @@ -2466,19 +2056,22 @@ multi.SetName = multi.setName -- Special Events local _os = os.exit + function os.exit(n) multi.OnExit:Fire(n or 0) _os(n) end + multi.OnError=multi:newConnection() multi.OnPreLoad = multi:newConnection() multi.OnExit = multi:newConnection(nil,nil,true) multi.m = {onexit = function() multi.OnExit:Fire() end} -if _VERSION >= "Lua 5.2" then + +if _VERSION >= "Lua 5.2" or jit then setmetatable(multi.m, {__gc = multi.m.onexit}) else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end -multi:attachScheduler() -return multi + +return multi \ No newline at end of file diff --git a/multi/integration/lanesManager/extensions.lua b/multi/integration/lanesManager/extensions.lua index d4fd404..198e551 100644 --- a/multi/integration/lanesManager/extensions.lua +++ b/multi/integration/lanesManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -113,7 +113,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("JobQueueManager",function() + thread:newThread("JobQueueManager",function() while true do local job = thread.hold(function() return queueReturn:pop() @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) local clock = os.clock local ref = 0 setmetatable(_G,{__index = funcs}) - multi:newThread("JobHandler",function() + thread:newThread("JobHandler",function() while true do local dat = thread.hold(function() return queueJob:pop() @@ -141,7 +141,7 @@ function multi:newSystemThreadedJobQueue(n) queueReturn:push{jid, funcs[name](unpack(args)),queue} end end) - multi:newThread("DoAllHandler",function() + thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() return doAll:peek() @@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("IdleHandler",function() + thread:newThread("IdleHandler",function() while true do thread.hold(function() return clock()-idle>3 diff --git a/multi/integration/lanesManager/init.lua b/multi/integration/lanesManager/init.lua index 16bdcf4..4bada5a 100644 --- a/multi/integration/lanesManager/init.lua +++ b/multi/integration/lanesManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,8 +21,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] + package.path = "?/init.lua;?.lua;" .. package.path -multi, thread = require("multi").init() -- get it all and have it on all lanes +multi, thread = require("multi"):init() -- get it all and have it on all lanes if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle return { init = function() @@ -34,138 +35,68 @@ end lanes = require("lanes").configure() multi.SystemThreads = {} multi.isMainThread = true + function multi:canSystemThread() return true end + function multi:getPlatform() return "lanes" end + -- Step 2 set up the Linda objects local __GlobalLinda = lanes.linda() -- handles global stuff local __SleepingLinda = lanes.linda() -- handles sleeping stuff local __ConsoleLinda = lanes.linda() -- handles console stuff -multi:newLoop(function() - local _,data = __ConsoleLinda:receive(0, "Q") - if data then - print(unpack(data)) - end -end) -local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda) -local threads = {} +local __StatusLinda = lanes.linda() -- handles pushstatus for stfunctions + +local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda, __SleepingLinda, __StatusLinda) local count = 1 local started = false local livingThreads = {} function THREAD:newFunction(func,holdme) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - return multi.hold(function() - if err then - return nil, err - elseif rets then - return unpack(rets) - end - end) - else - while not rets and not err do - multi.scheduler:Act() - end - if err then - return nil,err - end - return unpack(rets) - end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" - end - return { - isTFunc = true, - wait = noWait, - connect = function(f) - f(nil,"Function is paused") - end - } - end - local t = multi:newSystemThread("SystemThreadedFunction",func,...) - t.OnDeath(function(self,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() - end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp - end - setmetatable(tfunc,tfunc) - return tfunc + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end,holdme)() end function multi:newSystemThread(name, func, ...) multi.InitSystemThreadErrorHandler() - rand = math.random(1, 10000000) - local c = {} - local __self = c + local rand = math.random(1, 10000000) + local return_linda = lanes.linda() + c = {} c.name = name c.Name = name c.Id = count c.loadString = {"base","package","os","io","math","table","string","coroutine"} livingThreads[count] = {true, name} + c.returns = return_linda c.Type = "sthread" c.creationTime = os.clock() c.alive = true c.priority = THREAD.Priority_Normal - local args = {...} - multi:newThread(function() - c.thread = lanes.gen(table.concat(c.loadString,","), - { - globals={ -- Set up some globals - THREAD_NAME=name, - THREAD_ID=count, - THREAD = THREAD, - GLOBAL = GLOBAL, - _Console = __ConsoleLinda - }, - priority=c.priority - },func)(unpack(args)) - thread.kill() - end) + c.thread = lanes.gen("*", + { + globals={ -- Set up some globals + THREAD_NAME = name, + THREAD_ID = count, + THREAD = THREAD, + GLOBAL = GLOBAL, + _Console = __ConsoleLinda + }, + priority=c.priority + },function(...) + local has_error = true + return_linda:set("returns",{func(...)}) + has_error = false + end)(...) count = count + 1 + function c:getName() + return c.Name + end function c:kill() self.thread:cancel() - multi.print("Thread: '" .. self.name .. "' has been stopped!") self.alive = false end table.insert(multi.SystemThreads, c) @@ -175,59 +106,60 @@ function multi:newSystemThread(name, func, ...) return c end -local function detectLuaError(str) - return type(str)=="string" and str:match("%.lua:%d*:") -end - -local function tableLen(tab) - local len = 0 - for i,v in pairs(tab) do - len = len + 1 - end - return len -end +THREAD.newSystemThread = multi.newSystemThread function multi.InitSystemThreadErrorHandler() if started == true then return end started = true - multi:newThread("ThreadErrorHandler",function() + thread:newThread("SystemThreadScheduler",function() local threads = multi.SystemThreads + local _,data,status,push,temp while true do - thread.sleep(.1) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough. + thread.yield() + _,data = __ConsoleLinda:receive(0, "Q") for i = #threads, 1, -1 do - local _,data = pcall(function() - return {threads[i].thread:join(1)} - end) - local v, err, t = data[1],data[2],data[3] - if detectLuaError(err) then - if err:find("Thread was killed!\1") then - livingThreads[threads[i].Id] = {false, threads[i].Name} - threads[i].alive = false - threads[i].OnDeath:Fire(threads[i],nil,"Thread was killed!") - GLOBAL["__THREADS__"] = livingThreads - table.remove(threads, i) - else - threads[i].OnError:Fire(threads[i], err, "Error in systemThread: '" .. threads[i].name .. "' <" .. err .. ">") - threads[i].alive = false - livingThreads[threads[i].Id] = {false, threads[i].Name} - GLOBAL["__THREADS__"] = livingThreads - table.remove(threads, i) - end - elseif tableLen(data)>0 then - livingThreads[threads[i].Id] = {false, threads[i].Name} - threads[i].alive = false - threads[i].OnDeath:Fire(threads[i],unpack(data)) + temp = threads[i] + status = temp.thread.status + push = __StatusLinda:get(temp.Id) + if push then + temp.statusconnector:Fire(unpack(({__StatusLinda:receive(nil, temp.Id)})[2])) + end + if status == "done" or temp.returns:get("returns") then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnDeath:Fire(unpack(({temp.returns:receive(0, "returns")})[2])) + GLOBAL["__THREADS__"] = livingThreads + table.remove(threads, i) + elseif status == "running" then + -- + elseif status == "waiting" then + -- + elseif status == "error" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,unpack(temp.returns:receive(0,"returns") or {"Thread Killed!"})) + GLOBAL["__THREADS__"] = livingThreads + table.remove(threads, i) + elseif status == "cancelled" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,"thread_cancelled") + GLOBAL["__THREADS__"] = livingThreads + table.remove(threads, i) + elseif status == "killed" then + livingThreads[temp.Id] = {false, temp.Name} + temp.alive = false + temp.OnError:Fire(temp,"thread_killed") GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) end end end - end).OnError(function(...) - print("Error!",...) end) end + multi.print("Integrated Lanes!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL diff --git a/multi/integration/lanesManager/threads.lua b/multi/integration/lanesManager/threads.lua index 6d9d13d..68cb733 100644 --- a/multi/integration/lanesManager/threads.lua +++ b/multi/integration/lanesManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,7 +28,8 @@ local function getOS() return "unix" end end -local function INIT(__GlobalLinda,__SleepingLinda) + +local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda) local THREAD = {} THREAD.Priority_Core = 3 THREAD.Priority_High = 2 @@ -37,12 +38,15 @@ local function INIT(__GlobalLinda,__SleepingLinda) THREAD.Priority_Below_Normal = -1 THREAD.Priority_Low = -2 THREAD.Priority_Idle = -3 + function THREAD.set(name, val) __GlobalLinda:set(name, val) end + function THREAD.get(name) return __GlobalLinda:get(name) end + function THREAD.waitFor(name) local function wait() math.randomseed(os.time()) @@ -53,14 +57,17 @@ local function INIT(__GlobalLinda,__SleepingLinda) until __GlobalLinda:get(name) return __GlobalLinda:get(name) end + if getOS() == "windows" then THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) end + function THREAD.getCores() return THREAD.__CORES end + function THREAD.getConsole() local c = {} c.queue = _Console @@ -73,28 +80,41 @@ local function INIT(__GlobalLinda,__SleepingLinda) end return c end + function THREAD.getThreads() return GLOBAL.__THREADS__ end + if os.getOS() == "windows" then THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) end + function THREAD.kill() -- trigger the lane destruction error("Thread was killed!\1") end + function THREAD.getName() return THREAD_NAME end + function THREAD.getID() return THREAD_ID end + + function THREAD.pushStatus(...) + local args = {...} + __StatusLinda:send(nil,THREAD_ID, args) + end + _G.THREAD_ID = 0 + function THREAD.sleep(n) math.randomseed(os.time()) __SleepingLinda:receive(n, "__non_existing_variable") end + function THREAD.hold(n) local function wait() math.randomseed(os.time()) @@ -104,6 +124,7 @@ local function INIT(__GlobalLinda,__SleepingLinda) wait() until n() end + local GLOBAL = {} setmetatable(GLOBAL, { __index = function(t, k) @@ -115,6 +136,7 @@ local function INIT(__GlobalLinda,__SleepingLinda) }) return GLOBAL, THREAD end -return {init = function(g,s) - return INIT(g,s) + +return {init = function(g,s,st) + return INIT(g,s,st) end} \ No newline at end of file diff --git a/multi/integration/loveManager/extensions.lua b/multi/integration/loveManager/extensions.lua index 1673611..cf6beb2 100644 --- a/multi/integration/loveManager/extensions.lua +++ b/multi/integration/loveManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -131,7 +131,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("jobManager",function() + thread:newThread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -155,7 +155,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - multi:newThread("startUp",function() + thread:newThread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -165,7 +165,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("runner",function() + thread:newThread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -187,7 +187,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - multi:newThread("Idler",function() + thread:newThread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/loveManager/init.lua b/multi/integration/loveManager/init.lua index 16f9b07..93a5a2c 100644 --- a/multi/integration/loveManager/init.lua +++ b/multi/integration/loveManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -37,7 +37,7 @@ GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] -local multi, thread = require("multi.compat.love2d"):init() +local multi, thread = require("multi"):init() local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" @@ -48,70 +48,7 @@ local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 local OBJECT_ID = 0 local stf = 0 -function THREAD:newFunction(func,holup) - local tfunc = {} - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait(no) - if thread.isThread() and not (no) then - -- In a thread - else - -- Not in a thread - end - end - tfunc.__call = function(t,...) - if not t.Active then - if holdme then - return nil, "Function is paused" - end - return { - isTFunc = true, - wait = noWait, - connect = function(f) - f(nil,"Function is paused") - end - } - end - local t = multi:newSystemThread("SystemThreadedFunction",func,...) - t.OnDeath(function(self,...) rets = {...} end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() - end - local temp = { - OnStatus = multi:newConnection(), - OnError = multi:newConnection(), - OnReturn = multi:newConnection(), - isTFunc = true, - wait = wait, - connect = function(f) - local tempConn = multi:newConnection() - t.OnDeath(function(self,...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(self,...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp - end - setmetatable(tfunc,tfunc) - return tfunc -end + function multi:newSystemThread(name,func,...) local c = {} c.name = name @@ -119,38 +56,60 @@ function multi:newSystemThread(name,func,...) c.thread=love.thread.newThread(ThreadFileData) c.thread:start(THREAD.dump(func),c.ID,c.name,...) c.stab = THREAD.createStaticTable(name) - GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} + c.OnDeath = multi:newConnection() + c.OnError = multi:newConnection() + GLOBAL["__THREAD_"..c.ID] = {ID=c.ID, Name=c.name, Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID - THREAD_ID=THREAD_ID+1 - multi:newThread(function() - while true do - thread.yield() - if c.stab["returns"] then - c.OnDeath:Fire(c,unpack(t.stab.returns)) - t.stab.returns = nil - thread.kill() - end - local error = c.thread:getError() - if error then - if error:find("Thread Killed!\1") then - c.OnDeath:Fire(c,"Thread Killed!") - thread.kill() - else - c.OnError:Fire(c,error) - thread.kill() + THREAD_ID=THREAD_ID + 1 + function c:getName() + return c.name + end + thread:newThread(function() + if name:find("TempSystemThread") then + local status_channel = love.thread.getChannel("__"..c.ID.."__MULTI__STATUS_CHANNEL__") + thread.hold(function() + -- While the thread is running we might as well do something in the loop + local status = status_channel + if status:peek()~=nil then + c.statusconnector:Fire(unpack(status:pop())) end - end + return not c.thread:isRunning() + end) + else + thread.hold(function() + return not c.thread:isRunning() + end) + end + -- If the thread is not running let's handle that. + local thread_err = c.thread:getError() + if thread_err == "Thread Killed!\1" then + c.OnDeath:Fire("Thread Killed!") + elseif thread_err then + c.OnError:Fire(c,thread_err) + elseif c.stab.returns then + c.OnDeath:Fire(unpack(c.stab.returns)) + c.stab.returns = nil end end) return c end -function love.threaderror(thread, errorstr) - print("Thread error!\n"..errorstr) + +function THREAD:newFunction(func) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread"..THREAD_ID,func,...) + end)() end + +THREAD.newSystemThread = multi.newSystemThread + +function love.threaderror(thread, errorstr) + mulit.print("Thread error!\n"..errorstr) +end + multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.loveManager.extensions") -print("Integrated Love Threading!") +mulit.print("Integrated Love Threading!") return {init=function() return GLOBAL,THREAD end} \ No newline at end of file diff --git a/multi/integration/loveManager/threads.lua b/multi/integration/loveManager/threads.lua index a4ee947..c66e7dc 100644 --- a/multi/integration/loveManager/threads.lua +++ b/multi/integration/loveManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,15 +24,19 @@ SOFTWARE. require("love.timer") require("love.system") require("love.data") +require("love.thread") local socket = require("socket") local multi, thread = require("multi").init() local threads = {} + function threads.loadDump(d) return loadstring(d:getString()) end + function threads.dump(func) return love.data.newByteData(string.dump(func)) end + local fRef = {"func",nil} local function manage(channel, value) channel:clear() @@ -44,6 +48,7 @@ local function manage(channel, value) channel:push(value) end end + local function RandomVariable(length) local res = {} math.randomseed(socket.gettime()*10000) @@ -52,12 +57,14 @@ local function RandomVariable(length) end return table.concat(res) end + local GNAME = "__GLOBAL_" local proxy = {} function threads.set(name,val) if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end proxy[name]:performAtomic(manage, val) end + function threads.get(name) if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end local dat = proxy[name]:peek() @@ -67,6 +74,7 @@ function threads.get(name) return dat end end + function threads.waitFor(name) if thread.isThread() then return thread.hold(function() @@ -82,18 +90,28 @@ function threads.waitFor(name) end return dat end + function threads.package(name,val) local init = val.init val.init=threads.dump(val.init) GLOBAL[name]=val val.init=init end + function threads.getCores() return love.system.getProcessorCount() end + function threads.kill() error("Thread Killed!\1") end + +function THREAD.pushStatus(...) + local status_channel = love.thread.getChannel("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__") + local args = {...} + status_channel:push(__THREADID__, args) +end + function threads.getThreads() local t = {} for i=1,GLOBAL["__THREAD_COUNT"] do @@ -101,18 +119,23 @@ function threads.getThreads() end return t end + function threads.getThread(n) return GLOBAL["__THREAD_"..n] end + function threads.getName() return __THREADNAME__ end + function threads.getID() return __THREADID__ end + function threads.sleep(n) love.timer.sleep(n) end + function threads.getGlobal() return setmetatable({}, { @@ -125,6 +148,7 @@ function threads.getGlobal() } ) end + function threads.createTable(n) local _proxy = {} local function set(name,val) @@ -151,6 +175,7 @@ function threads.createTable(n) } ) end + function threads.getConsole() local c = {} c.queue = love.thread.getChannel("__CONSOLE__") @@ -163,11 +188,12 @@ function threads.getConsole() end return c end + if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = love.thread.getChannel("__CONSOLE__") - multi:newThread("consoleManager",function() + thread:newThread("consoleManager",function() while true do thread.yield() dat = queue:pop() @@ -181,6 +207,7 @@ if not ISTHREAD then end end) end + function threads.createStaticTable(n) local __proxy = {} local function set(name,val) @@ -212,10 +239,12 @@ function threads.createStaticTable(n) } ) end + function threads.hold(n) local dat while not(dat) do dat = n() end end + return threads \ No newline at end of file diff --git a/multi/integration/lovrManager/extensions.lua b/multi/integration/lovrManager/extensions.lua index d2d5143..7032b1d 100644 --- a/multi/integration/lovrManager/extensions.lua +++ b/multi/integration/lovrManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) end) end,holup),name end - multi:newThread("jobManager",function() + thread:newThread("jobManager",function() while true do thread.yield() local dat = c.queueReturn:pop() @@ -153,7 +153,7 @@ function multi:newSystemThreadedJobQueue(n) local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} setmetatable(_G,{__index = funcs}) - multi:newThread("startUp",function() + thread:newThread("startUp",function() while true do thread.yield() local all = queueAll:peek() @@ -163,7 +163,7 @@ function multi:newSystemThreadedJobQueue(n) end end end) - multi:newThread("runner",function() + thread:newThread("runner",function() thread.sleep(.1) while true do thread.yield() @@ -185,7 +185,7 @@ function multi:newSystemThreadedJobQueue(n) end):OnError(function(...) error(...) end) - multi:newThread("Idler",function() + thread:newThread("Idler",function() while true do thread.yield() if clock()-lastProc> 2 then diff --git a/multi/integration/lovrManager/init.lua b/multi/integration/lovrManager/init.lua index 523382a..53cafa3 100644 --- a/multi/integration/lovrManager/init.lua +++ b/multi/integration/lovrManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -76,6 +76,7 @@ function multi:newSystemThread(name,func,...) THREAD_ID=THREAD_ID+1 return c end +THREAD.newSystemThread = multi.newSystemThread function lovr.threaderror(thread, errorstr) print("Thread error!\n"..errorstr) end diff --git a/multi/integration/lovrManager/threads.lua b/multi/integration/lovrManager/threads.lua index b373136..6a95a1e 100644 --- a/multi/integration/lovrManager/threads.lua +++ b/multi/integration/lovrManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -168,7 +168,7 @@ if not ISTHREAD then local clock = os.clock local lastproc = clock() local queue = lovr.thread.getChannel("__CONSOLE__") - multi:newThread("consoleManager",function() + thread:newThread("consoleManager",function() while true do thread.yield() dat = queue:pop() diff --git a/multi/integration/luvitManager.lua b/multi/integration/luvitManager.lua index 73ee666..0f53cb9 100644 --- a/multi/integration/luvitManager.lua +++ b/multi/integration/luvitManager.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -116,6 +116,7 @@ local function _INIT(luvitThread, timer) luvitThread.start(entry, package.path, name, c.func, ...) return c end + THREAD.newSystemThread = multi.newSystemThread multi.print("Integrated Luvit!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL diff --git a/multi/integration/networkManager/channel.lua b/multi/integration/networkManager/channel.lua index 2fc61b3..3083241 100644 --- a/multi/integration/networkManager/channel.lua +++ b/multi/integration/networkManager/channel.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/childNode.lua b/multi/integration/networkManager/childNode.lua index 24945fa..43837d5 100644 --- a/multi/integration/networkManager/childNode.lua +++ b/multi/integration/networkManager/childNode.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/clientSide.lua b/multi/integration/networkManager/clientSide.lua index b640568..9210840 100644 --- a/multi/integration/networkManager/clientSide.lua +++ b/multi/integration/networkManager/clientSide.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/cmds.lua b/multi/integration/networkManager/cmds.lua index a6097ae..ccb3c2f 100644 --- a/multi/integration/networkManager/cmds.lua +++ b/multi/integration/networkManager/cmds.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/extensions.lua b/multi/integration/networkManager/extensions.lua index 0752381..c4cac79 100644 --- a/multi/integration/networkManager/extensions.lua +++ b/multi/integration/networkManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/init.lua b/multi/integration/networkManager/init.lua index f6778d8..78e10e2 100644 --- a/multi/integration/networkManager/init.lua +++ b/multi/integration/networkManager/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/masterNode.lua b/multi/integration/networkManager/masterNode.lua index 2ea9b1e..25c2b07 100644 --- a/multi/integration/networkManager/masterNode.lua +++ b/multi/integration/networkManager/masterNode.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -72,7 +72,7 @@ function master:newNetworkThread(nodeName,func,...) local ret local nID = netID local conn = multi:newConnection() - multi:newThread(function() + thread:newthread(function() dat:addBlock{ args = args, func = func, @@ -143,7 +143,7 @@ function multi:newMasterNode(cd) else c:getNodesFromBroadcast() end - multi:newThread("CMDQueueProcessor",function() + thread:newthread("CMDQueueProcessor",function() while true do thread.skip(128) local data = table.remove(c._queue,1) diff --git a/multi/integration/networkManager/node.lua b/multi/integration/networkManager/node.lua index 4723725..8151b57 100644 --- a/multi/integration/networkManager/node.lua +++ b/multi/integration/networkManager/node.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/nodeManager.lua b/multi/integration/networkManager/nodeManager.lua index 73cf06a..840bc60 100644 --- a/multi/integration/networkManager/nodeManager.lua +++ b/multi/integration/networkManager/nodeManager.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/serverSide.lua b/multi/integration/networkManager/serverSide.lua index 2c2291f..4d8b771 100644 --- a/multi/integration/networkManager/serverSide.lua +++ b/multi/integration/networkManager/serverSide.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/threads.lua b/multi/integration/networkManager/threads.lua index 0752381..c4cac79 100644 --- a/multi/integration/networkManager/threads.lua +++ b/multi/integration/networkManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/networkManager/utils.lua b/multi/integration/networkManager/utils.lua index 0aeac3c..9ebdb1b 100644 --- a/multi/integration/networkManager/utils.lua +++ b/multi/integration/networkManager/utils.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/multi/integration/pesudoManager/extensions.lua b/multi/integration/pesudoManager/extensions.lua index deae13d..3881f3f 100644 --- a/multi/integration/pesudoManager/extensions.lua +++ b/multi/integration/pesudoManager/extensions.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -125,7 +125,7 @@ function multi:newSystemThreadedJobQueue(n) end,holup),name end for i=1,c.cores do - multi:newThread("PesudoThreadedJobQueue_"..i,function() + thread:newthread("PesudoThreadedJobQueue_"..i,function() while true do thread.yield() if #jobs>0 then diff --git a/multi/integration/pesudoManager/init.lua b/multi/integration/pesudoManager/init.lua index 0cafe79..84bdca3 100644 --- a/multi/integration/pesudoManager/init.lua +++ b/multi/integration/pesudoManager/init.lua @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] package.path = "?/init.lua;?.lua;" .. package.path -local multi, thread = require("multi").init() +local multi, thread = require("multi"):init() if multi.integration then return { @@ -32,7 +32,7 @@ if multi.integration then } end -local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads"):init() +local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) function multi:canSystemThread() -- We are emulating system threading return true @@ -41,6 +41,7 @@ end function multi:getPlatform() return "pesudo" end + local function split(str) local tab = {} for word in string.gmatch(str, '([^,]+)') do @@ -48,13 +49,16 @@ local function split(str) end return tab end -THREAD.newFunction=thread.newFunction + +local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,xpcall,math,coroutine,string,table]] +tab = split(tab) + local id = 0 function multi:newSystemThread(name,func,...) GLOBAL["$THREAD_NAME"] = name GLOBAL["$__THREADNAME__"] = name GLOBAL["$THREAD_ID"] = id - --GLOBAL["$thread"] = thread + GLOBAL["$thread"] = thread local env = { GLOBAL = GLOBAL, THREAD = THREAD, @@ -64,21 +68,28 @@ function multi:newSystemThread(name,func,...) thread = thread } - local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,xpcall,math,coroutine,string,table]] - tab = split(tab) for i = 1,#tab do env[tab[i]] = _G[tab[i]] end - --setmetatable(env,{__index=env}) - multi:newISOThread(name,func,env,...).OnError(function(self,msg) - print("ERROR:",msg) - end) + + local th = thread:newISOThread(name,func,env,...) + id = id + 1 + + return th end + +THREAD.newSystemThread = multi.newSystemThread -- System threads as implemented here cannot share memory, but use a message passing system. -- An isolated thread allows us to mimic that behavior so if access data from the "main" thread happens things will not work. This behavior is in line with how the system threading works -print("Integrated Pesudo Threading!") +function THREAD:newFunction(func,holdme) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end,holdme)() +end + +multi.print("Integrated Pesudo Threading!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD diff --git a/multi/integration/pesudoManager/threads.lua b/multi/integration/pesudoManager/threads.lua index febac47..b8ee6aa 100644 --- a/multi/integration/pesudoManager/threads.lua +++ b/multi/integration/pesudoManager/threads.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2020 Ryan Ward +Copyright (c) 2022 Ryan Ward Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] + local function getOS() if package.config:sub(1, 1) == "\\" then return "windows" @@ -28,7 +29,8 @@ local function getOS() return "unix" end end -local function INIT(env) + +local function INIT(thread) local THREAD = {} local GLOBAL = {} THREAD.Priority_Core = 3 @@ -38,24 +40,29 @@ local function INIT(env) THREAD.Priority_Below_Normal = -1 THREAD.Priority_Low = -2 THREAD.Priority_Idle = -3 + function THREAD.set(name, val) GLOBAL[name] = val end + function THREAD.get(name) return GLOBAL[name] end + function THREAD.waitFor(name) - print("Waiting",thread) return thread.hold(function() return GLOBAL[name] end) end + if getOS() == "windows" then THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) end + function THREAD.getCores() return THREAD.__CORES end + function THREAD.getConsole() local c = {} function c.print(...) @@ -66,31 +73,38 @@ local function INIT(env) end return c end + function THREAD.getThreads() return {}--GLOBAL.__THREADS__ end + + THREAD.pushStatus = thread.pushStatus + if os.getOS() == "windows" then THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS")) else THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n")) end + function THREAD.kill() error("Thread was killed!") end + function THREAD.getName() return GLOBAL["$THREAD_NAME"] end + function THREAD.getID() return GLOBAL["$THREAD_ID"] end - function THREAD.sleep(n) - thread.sleep(n) - end - function THREAD.hold(n) - return thread.hold(n) - end + + THREAD.sleep = thread.sleep + + THREAD.hold = thread.hold + return GLOBAL, THREAD end -return {init = function() - return INIT() + +return {init = function(thread) + return INIT(thread) end} \ No newline at end of file diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec new file mode 100644 index 0000000..ac62aa9 --- /dev/null +++ b/rockspecs/multi-15.2-0.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "15.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } +} \ No newline at end of file diff --git a/rockspecs/multi-15.2-1.rockspec b/rockspecs/multi-15.2-1.rockspec new file mode 100644 index 0000000..12c0c0c --- /dev/null +++ b/rockspecs/multi-15.2-1.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "15.2-1" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.1", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } +} \ No newline at end of file diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec new file mode 100644 index 0000000..d96f6f4 --- /dev/null +++ b/rockspecs/multi-15.3-0.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "15.3-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.3.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } +} \ No newline at end of file diff --git a/test.lua b/test.lua index 21fd9e2..fc484f9 100644 --- a/test.lua +++ b/test.lua @@ -1,113 +1,20 @@ ---package.path = "./?/init.lua;"..package.path -multi,thread = require("multi"):init() +package.path = "./?/init.lua;?.lua;lua5.4/share/lua/?/init.lua;lua5.4/share/lua/?.lua;"..package.path +package.cpath = "lua5.4/lib/lua/?/core.dll;"..package.cpath +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -func = thread:newFunction(function(count) - local a = 0 - while true do - a = a + 1 - thread.sleep(.5) - thread.pushStatus(a,count) - if a == count then break end - end - return "Done" -end) - -multi:newThread("test",function() - local ret = func(10) - local ret2 = func(15) - local ret3 = func(20) - ret.OnStatus(function(part,whole) - print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - ret2.OnStatus(function(part,whole) - print("Ret2: ",math.ceil((part/whole)*1000)/10 .."%") - end) - ret3.OnStatus(function(part,whole) - print("Ret3: ",math.ceil((part/whole)*1000)/10 .."%") - end) - thread.hold(ret2.OnReturn + ret.OnReturn + ret3.OnReturn) - print("Function Done!") - os.exit() -end) - ---GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available - -func2 = thread:newFunction(function() - thread.sleep(3) - print("Hello World!") - return true -end,true) -- set holdme to true - -func2:holdMe(false) -- reset holdme to false -print("Calling func...") -print(func2()) - -test = thread:newFunction(function(a,b) - thread.sleep(1) - return a,b -end) -print(test(1,2).connect(function(...) - print(...) -end)) -test:Pause() -print(test(1,2).connect(function(...) - print(...) -end)) -test:Resume() -print(test(1,2).connect(function(...) - print(...) -end)) - -test = thread:newFunction(function() - return 1,2,nil,3,4,5,6,7,8,9 +test = THREAD:newFunction(function() + PNT() + return 1,2 end,true) +multi:newThread(function() + while true do + print("...") + thread.sleep(1) + end +end) +multi:newAlarm(.1):OnRing(function() os.exit() end) print(test()) -multi:newThread("testing",function() - print("#Test = ",test()) - print(thread.hold(function() - print("Hello!") - return false - end,{ - interval = 2, - cycles = 3 - })) -- End result, 3 attempts within 6 seconds. If still false then timeout - print("held") -end).OnError(function(...) - print(...) -end) - -sandbox = multi:newProcessor("Test Processor") -sandbox:newTLoop(function() - print("testing...") -end,1) - -test2 = multi:newTLoop(function() - print("testing2...") -end,1) - -sandbox:newThread("Test Thread",function() - local a = 0 - while true do - thread.sleep(1) - a = a + 1 - print("Thread Test: ".. multi.getCurrentProcess().Name) - if a == 10 then - sandbox.Stop() - end - end -end).OnError(function(...) - print(...) -end) - -multi:newThread("Test Thread",function() - while true do - thread.sleep(1) - print("Thread Test: ".. multi.getCurrentProcess().Name) - end -end).OnError(function(...) - print(...) -end) - -sandbox.Start() +print("Hi!") multi:mainloop() \ No newline at end of file diff --git a/tests/objectTests.lua b/tests/objectTests.lua deleted file mode 100644 index 9296a48..0000000 --- a/tests/objectTests.lua +++ /dev/null @@ -1,3 +0,0 @@ -return function objectTests(multi,thread) - print("Testing Alarms!") -end \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index e0faa88..07d6e85 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,4 +1,9 @@ -package.path="../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path +if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then + package.path="multi/?.lua;multi/?/init.lua;multi/?.lua;multi/?/?/init.lua;"..package.path + require("lldebugger").start() +else + package.path="./?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path +end --[[ This file runs all tests. Format: @@ -15,7 +20,157 @@ package.path="../?.lua;../?/init.lua;../?.lua;../?/?/init.lua;"..package.path 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() -function runTest(path) - +local multi, thread = require("multi"):init{print=true}--{priority=true} +local good = false +local proc = multi:newProcessor("Test",true) +proc:newAlarm(3):OnRing(function() + good = true +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!") + proc:newAlarm(2):OnRing(function(a) + alarms = true + a:Destroy() + end) + proc:newTStep(1,10,1,.1):OnStep(function(t) + tsteps = tsteps + 1 + end).OnEnd(function(step) + step:Destroy() + end) + proc:newStep(1,10):OnStep(function(s) + steps = steps + 1 + end).OnEnd(function(step) + step:Destroy() + end) + local loop = proc:newLoop(function(l) + loops = loops + 1 + end) + proc:newTLoop(function(t) + tloops = tloops + 1 + end,.1) + local updater = proc:newUpdater(1):OnUpdate(function() + updaters = updaters + 1 + end) + local event = proc:newEvent(function() + return alarms + end) + 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 + end) + thread.hold(event.OnEvent) + print("Starting Connection and Thread tests!") + func = thread:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done" + end) + local ret = func(10) + local ret2 = func(15) + local ret3 = func(20) + local s1,s2,s3 = 0,0,0 + ret.OnError(function(...) + print("Func 1:",...) + end) + ret2.OnError(function(...) + print("Func 2:",...) + end) + ret3.OnError(function(...) + print("Func 3:",...) + end) + ret.OnStatus(function(part,whole) + s1 = math.ceil((part/whole)*1000)/10 + end) + ret2.OnStatus(function(part,whole) + s2 = math.ceil((part/whole)*1000)/10 + end) + ret3.OnStatus(function(part,whole) + s3 = math.ceil((part/whole)*1000)/10 + end) + ret.OnReturn(function() + print("Done 1") + end) + ret2.OnReturn(function() + print("Done 2") + end) + ret3.OnReturn(function() + print("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: Ok") + else + print("Threads OnStatus or thread.hold(conn) Error!") + end + if timeout then + print("Threads or Connection Error!") + else + print("Connection Test 1: Ok") + end + conn1 = proc:newConnection() + conn2 = proc:newConnection() + conn3 = proc:newConnection() + local c1,c2,c3,c4 = false,false,false,false + + local a = conn1(function() + c1 = true + end) + + local b = conn2(function() + c2 = true + end) + + local c = conn3(function() + c3 = true + end) + + local d = conn3(function() + c4 = true + end) + + conn1:Fire() + conn2:Fire() + conn3:Fire() + + if c1 and c2 and c3 and c4 then + print("Connection Test 2: Ok") + else + print("Connection Test 2: Error") + end + c3 = false + c4 = false + d:Destroy() + conn3:Fire() + if c3 and not(c4) then + print("Connection Test 3: Ok") + else + print("Connection Test 3: Error removing connection") + end + os.exit() -- End of tests +end) + +runTest().OnError(function(...) + print("Error: Something went wrong with the test!") + print(...) + os.exit(1) +end) + +print("Pumping proc") +while true do + proc.run() end \ No newline at end of file