From 5137bb9483984d5e23eda80ccc8e911e6e48f19f Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 31 Dec 2022 17:06:04 -0500 Subject: [PATCH 001/117] Fixed spelling, started ideaing for 16.0.0 --- docs/changes.md | 132 +++++++++--------- init.lua | 2 +- .../extensions.lua | 0 .../{pesudoManager => pseudoManager}/init.lua | 0 .../threads.lua | 0 rockspecs/multi-16.0-0.rockspec | 39 ++++++ 6 files changed, 106 insertions(+), 67 deletions(-) rename integration/{pesudoManager => pseudoManager}/extensions.lua (100%) rename integration/{pesudoManager => pseudoManager}/init.lua (100%) rename integration/{pesudoManager => pseudoManager}/threads.lua (100%) create mode 100644 rockspecs/multi-16.0-0.rockspec diff --git a/docs/changes.md b/docs/changes.md index 314a545..0975926 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -179,7 +179,7 @@ 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. +-- Don't worry if you don't have lanes or love2d. PseudoThreading 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 @@ -344,7 +344,7 @@ Added: Changed: --- -- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: +- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Example below: ```lua multi, thread = require("multi"):init() @@ -583,7 +583,7 @@ Added: Example: ```lua local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available +GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads func = jq:newFunction("test",function(a,b) @@ -612,7 +612,7 @@ multi:mainloop() ## multi.TIMEOUT -`multi.TIMEOUT` is equal to "TIMEOUT", it is reccomended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. +`multi.TIMEOUT` is equal to "TIMEOUT", it is recommended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. ## new connections on threaded functions @@ -850,7 +850,7 @@ Full Update Showcase --- ```lua local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available +GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads func = jq:newFunction("test",function(a,b) @@ -882,20 +882,20 @@ 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. +This was supposed to be released over a year ago, but work and other things got in my way. Pseudo-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) - Creates an isolated thread that prevents both locals and globals from being accessed. - - Was designed for the pesudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intened for what I wanted them to do! -- Added new integration: pesudoManager, functions just like lanesManager and loveManager, but it's actually single threaded - - This was implemented because, you may want to build your code around being multi threaded, but some systems/implemetations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatcally are threaded if built correctly. I am planning on adding more threadedOjbects. -- In addition to adding pesudo Threading `multi.integration.threading` can now be used to autodetect which enviroment you are on and use the threading features. + - Was designed for the pseudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intended for what I wanted them to do! +- Added new integration: pseudoManager, functions just like lanesManager and loveManager, but it's actually single threaded + - This was implemented because, you may want to build your code around being multi threaded, but some systems/implementations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatically are threaded if built correctly. I am planning on adding more threadedOjbects. +- In addition to adding pseudo Threading `multi.integration.threading` can now be used to autodetect which environment you are on and use the threading features. ``` GLOBAL,THREAD = require("multi.integration.threading"):init() ``` - If you are using love2d it will use that, if you have lanes avaialble then it will use lanes. Otherwise it will use pesudo threading. This allows module creators to implement scalable features without having to worry about which enviroment they are in. Can now require a consistant module: `require("multi.integration.threading"):init()` + If you are using love2d it will use that, if you have lanes available then it will use lanes. Otherwise it will use pseudo-threading. This allows module creators to implement scalable features without having to worry about which environment they are in. Can now require a consistent module: `require("multi.integration.threading"):init()` Changed: --- @@ -903,7 +903,7 @@ Changed: Removed: --- -- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatiblity with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how enviroments worked which means that you can no longer modify an enviroment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded enviroment into a threadedfunction. This in hindsight wasn't the best pratice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyones code in a massive way. +- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatibility with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how environments worked which means that you can no longer modify an environment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded environment into a threadedfunction. This in hindsight wasn't the best practice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyone's code in a massive way. Fixed: --- @@ -1024,9 +1024,9 @@ Removed: (Cleaning up a lot of old features) - multi:setDomainName(name)* - multi:linkDomain(name)* - multi:_Pause()* — Use multi:Stop() instead! -- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for chacking. +- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for checking. - multi.executeFunction(name,...)* -- multi:getError()* — Errors are nolonger gotten like that, multi.OnError(func) is the way to go +- multi:getError()* — Errors are no longer received like that, multi.OnError(func) is the way to go - multi.startFPSMonitior()* - multi.doFPS(s)* @@ -1048,7 +1048,7 @@ end) serv.OnStarted(function(self,data) print("Started!",self.Type,data) data.test = "Testing..." - -- self as reference to the object and data is a reference to the datatable that the service has access to + -- self as reference to the object and data is a reference to the data table that the service has access to end) serv:Start() serv:SetPriority(multi.Priority_Idle) @@ -1089,7 +1089,7 @@ setmetatable(example,{ print("We did it!",a,b) rawset(t,k,v) -- This means by using a threaded function we can get around the yielding across metamethods. - -- This is useful if you aren't using luajit, or if you using lua in an enviroment that is on version 5.1 + -- This is useful if you aren't using luajit, or if you using lua in an environment that is on version 5.1 -- There is a gotcha however, if using code that was meant to work with another coroutine based scheduler this may not work end, __index = thread:newFunction(function(t,k,v) -- Using a threaded function as the metamethod @@ -1101,7 +1101,7 @@ setmetatable(example,{ example["test"] = "We set a variable!" print(example["test"]) print(example.hi) --- When not in a threaded enviroment at root level we need to tell the code that we are waiting! Alternitavely after the function argument we can pass true to force a wait +-- When not in a threaded environment at root level we need to tell the code that we are waiting! Alternatively after the function argument we can pass true to force a wait c,d = test().wait() print(c,d) a,b = 6,7 @@ -1204,10 +1204,10 @@ Added: Changed: --- - threaded functions no longer auto detect the presence of arguments when within a threaded function. However, you can use the holup method to produce the same effect. If you plan on using a function in different ways then you can use .wait() and .connect() without setting the holup argument -- thread:newFunction(func,holup) — Added an argument holup to always force the threaded funcion to wait. Meaning you don't need to tell it to func().wait() or func().connect() +- thread:newFunction(func,holup) — Added an argument holup to always force the threaded function to wait. Meaning you don't need to tell it to func().wait() or func().connect() - multi:newConnection(protect,callback,kill) — Added the kill argument. Makes connections work sort of like a stack. Pop off the connections as they get called. So a one time connection handler. - - I'm not sure callback has been documented in any form. callback gets called each and everytime conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. -- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both enviroments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistant. looping through upvalues and sterlizing them and sending them are very complex and slow. + - I'm not sure callback has been documented in any form. callback gets called each and every time conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. +- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both environments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistent. looping through upvalues and sterilizing them and sending them are very complex and slow. Removed: --- @@ -1216,7 +1216,7 @@ Removed: Fixed: --- - Issue where setting the priority of lanes Threads were not working since we were using the data before one could have a chance to set it. This has been resolved! -- Issue where connections object:conn() was firing based on the existance of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing +- Issue where connections object:conn() was firing based on the existence of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing - Issue where async functions connect wasn't properly triggering when a function returned - Issue where async functions were not passing arguments properly. - Issue where async functions were not handling errors properly @@ -1239,12 +1239,12 @@ Fixed: Added: --- -- multi.init() — Initlizes the library! Must be called for multiple files to have the same handle. Example below -- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but timesout when a certain amount of time has passed! +- multi.init() — Initializes the library! Must be called for multiple files to have the same handle. Example below +- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but times out when a certain amount of time has passed! - multi.hold(function or number) — It's back and better than ever! Normal multi objs without threading will all be halted where threads will still run. If within a thread continue using thread.hold() and thread.sleep() - thread.holdWithin(NUMBER; cycles,FUNCTION; condition) — Holds until the condition is met! If the number of cycles passed is equal to cycles, hold will return a timeout error -- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicing the functionality of thread.holdWithin -**Note:** when hold has a timeout the first argument will return nil and the second atgument will be TIMEOUT, if not timed out hold will return the values from the conditions +- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicking the functionality of thread.holdWithin +**Note:** when hold has a timeout the first argument will return nil and the second argument will be TIMEOUT, if not timed out hold will return the values from the conditions - thread objects now have hooks that allow you to interact with it in more refined ways! -- tObj.OnDeath(self,status,returns[...]) — This is a connection that passes a reference to the self, the status, whether or not the thread ended or was killed, and the returns of the thread. -- tObj.OnError(self,error) — returns a reference to self and the error as a string @@ -1254,7 +1254,7 @@ Added: -- returns a function that gives you the option to wait or connect to the returns of the function. -- func().wait() — waits for the function to return works both within a thread and outside of one -- func().connect() — connects to the function finishing --- func() — If your function does not return anything you dont have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing +-- func() — If your function does not return anything you don't have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing -- 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() @@ -1327,7 +1327,7 @@ multi:mainloop() Fixed: --- -- Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label. +- Connections had a performance issue where they would create a non function when using connection.getConnection() of a non existing label. - An internal mismanagement of the threads scheduler was fixed. Now it should be quicker and free of bugs - Thread error management is the integrations was not properly implemented. This is now fixed @@ -1358,18 +1358,18 @@ Changed: ```lua local multi, thread = require("multi").init() -- The require multi function still returns the multi object like before ``` -- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistant code as well +- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistent code as well Note: Using init allows you to get access to the thread handle. This was done because thread was modifying the global space as well as multi. I wanted to not modify the global space anymore. internally most of your code can stay the same, you only need to change how the library is required. I do toy a bit with the global space, buy I use a variable name that is invalid as a variable name. The variable name is $multi. This is used internally to keep some records and maintain a clean space -Also when using intergrations things now look more consistant. +Also when using integrations things now look more consistent. ```lua local multi, thread = require("multi").init() local GLOBSL, THREAD = require("multi.integration.lanesManager").init() -- or whichever manager you are using local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti() ``` -Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome. +Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create coroutine based threads within both network and system threads. This gives you quite a bit of flexibility to create something awesome. Going forward: --- @@ -1382,7 +1382,7 @@ Added: --- - Connections:Lock() — Prevents a connection object form being fired - Connections:Unlock() — Removes the restriction imposed by conn:Lock() -- new fucntions added to the thread namespace +- new functions added to the thread namespace -- thread.request(THREAD handle,STRING cmd,VARARGS args) — allows you to push thread requests from outside the running thread! Extremely powerful. -- thread.exec(FUNCTION func) — Allows you to push code to run within the thread execution block! - handle = multi:newThread() — now returns a thread handle to interact with the object outside fo the thread @@ -1394,18 +1394,18 @@ Fixed: --- - Minor bug with multi:newThread() in how names and functions were managed - Major bug with the system thread handler. Saw healthy threads as dead ones -- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler opperates. +- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler operates. - newSystemThread()'s returned object now matches both the lanes and love2d in terms of methods that are usable. Error handling of System threads now behave the same across both love and lanes implementations. -- looks like I found a typo, thread.yeild -> thread.yield +- looks like I found a typo, thread.yield -> thread.yield Changed: --- -- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details +- getTasksDetails("t"), the table variant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details - TID has been added to multi objects. They count up from 0 and no 2 objects will have the same number - thread.hold() — As part of the memory leaks that I had to fix thread.hold() is slightly different. This change shouldn't impact previous code at all, but thread.hold() can not only return at most 7 arguments! -- You should notice some faster code execution from threads, the changes improve preformance of threads greatly. They are now much faster than before! -- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be seperated from the thread objects! -- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! +- You should notice some faster code execution from threads, the changes improve performance of threads greatly. They are now much faster than before! +- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be separated from the thread objects! +- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requiring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! Tasks Details Table format @@ -1466,7 +1466,7 @@ Changed: --- - A few things, to make concepts in the library more clear. - The way functions returned paused status. Before it would return "PAUSED" now it returns nil, true if paused -- Modified the connection object to allow for some more syntaxial suger! +- Modified the connection object to allow for some more syntactical sugar! - System threads now trigger an OnError connection that is a member of the object itself. multi.OnError() is no longer triggered for a system thread that crashes! Connection Example: @@ -1493,7 +1493,7 @@ print(func()) -- nil, true Removed: --- -- Ranges and conditions — corutine based threads can emulate what these objects did and much better! +- Ranges and conditions — coroutine based threads can emulate what these objects did and much better! - Due to the creation of hyper threaded processes the following objects are no more! - ~~multi:newThreadedEvent()~~ - ~~multi:newThreadedLoop()~~ @@ -1508,33 +1508,33 @@ These didn't have much use in their previous form, but with the addition of hype Fixed: --- -- There were some bugs in the networkmanager.lua file. Desrtoy -> Destroy some misspellings. +- There were some bugs in the networkmanager.lua file. Destroy -> Destroy some misspellings. - Massive object management bugs which caused performance to drop like a rock. - Found a bug with processors not having the Destroy() function implemented properly. -- Found an issue with the rockspec which is due to the networkManager additon. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately -- Insane proformance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) -- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increading the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? +- Found an issue with the rockspec which is due to the networkManager addition. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately +- Insane performance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) +- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increasing the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? - Fixed an issue with processors not properly destroying objects within them and not being destroyable themselves - Fixed a bug where pause and resume would duplicate objects! Not good -- Noticed that the switching of lua states, corutine based threading, is slower than multi-objs (Not by much though). +- Noticed that the switching of lua states, coroutine based threading, is slower than multi-objs (Not by much though). - multi:newSystemThreadedConnection(name,protect) — I did it! It works and I believe all the gotchas are fixed as well. - Issue one, if a thread died that was connected to that connection all connections would stop since the queue would get clogged! FIXED - There is one thing, the connection does have some handshakes that need to be done before it functions as normal! Added: --- -- Documentation, the purpose of 13.0.0, orginally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. +- Documentation, the purpose of 13.0.0, originally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. - multi:newHyperThreadedProcess(STRING name) — This is a version of the threaded process that gives each object created its own coroutine based thread which means you can use thread.* without affecting other objects created within the hyper threaded processes. Though, creating a self contained single thread is a better idea which when I eventually create the wiki page I'll discuss - multi:newConnector() — A simple object that allows you to use the new connection Fire syntax without using a multi obj or the standard object format that I follow. - multi:purge() — Removes all references to objects that are contained withing the processes list of tasks to do. Doing this will stop all objects from functioning. Calling Resume on an object should make it work again. - multi:getTasksDetails(STRING format) — Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format - multi:endTask(TID) — Use multi:getTasksDetails("t") to get the tid of a task - multi:enableLoadDetection() — Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it -- THREAD.getID() — returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! +- THREAD.getID() — returns a unique ID for the current thread. This variable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! - multi.print(...) works like normal print, but only prints if the setting print is set to true - setting: `print` enables multi.print() to work - STC: IgnoreSelf defaults to false, if true a Fire command will not be sent to the self -- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are genereated for you. +- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are generated for you. - STC: FireTo(id,...) — Described above. ```lua @@ -1574,19 +1574,19 @@ Table format for getTasksDetails(STRING format) } } ``` -**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance. +**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a significant increase in performance. **Going forward:** - Work on system threaded functions - work on the node manager - patch up bugs -- finish documentstion +- finish documentation # Update 12.2.2 - Time for some more bug fixes! Fixed: --- -- multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes. +- multi.Stop() not actually stopping due to the new priority management scheme and performance boost changes. # Update 12.2.1 - Time for some bug fixes! @@ -1597,13 +1597,13 @@ Fixed: SystemThreadedJobQueues - No longer need to use jobqueue.OnReady() The code is smarter and will send the pushed jobs automatically when the threads are ready Fixed: SystemThreadedConnection -- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that ecist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. +- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that exist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. Removed: multi:newQueuer -- This feature has no real use after corutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. +- This feature has no real use after coroutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. Going forward: -- Will I ever finish steralization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. +- Will I ever finish sterilization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. - I need to make better documentation for this library as well. In its current state, all I have are examples and not a list of what is what. Example @@ -1631,8 +1631,8 @@ multi:mainloop() - Priority 3 has been added! - ResetPriority() — This will set a flag for a process to be re evaluated for how much of an impact it is having on the performance of the system. - setting: auto_priority added! — If only lua os.clock was more fine tuned... milliseconds are not enough for this to work -- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lowbound for pirority. The defualt is a hyrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that -- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priotity +- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lower bound for priority. The default is a hybrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that +- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priority - setting: auto_delay added! — sets the time in seconds that the system will recheck for low performing processes and manage them. Will also upgrade a process if it starts to run better. ```lua -- All methods that did not return before now return a copy of itself. Thus allowing chaining. Most if not all mutators returned nil, so chaining can now be done. I will eventually write up a full documentation of everything which will show this. @@ -1648,7 +1648,7 @@ multi:mainloop{ ``` Priority 3 works a bit differently than the other 2. -P1 follows a forumla that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. +P1 follows a formula that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. ``` C: 3322269 ~I*7 H: 2847660 ~I*6 @@ -1681,17 +1681,17 @@ L: 2120906 I: 2120506 ``` -Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. +Auto Priority works by seeing what should be set high or low. Due to lua not having more precision than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the default is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. **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 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 +- Loops have been optimized 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 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. +I usually give an example of the changes made, but this time I have an explanation 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. +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 individual objects. If its possible to make the library even faster then I will go for it. # Update 12.1.0 - Threads just can't hold on anymore @@ -1726,16 +1726,16 @@ multi:mainloop() Going forward: --- -Contunue to make small changes as I come about them. This change was inspired when working of the net library. I was addind simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been recieved. While upvalues did work, i felt returning data was cleaner and added this feature. +Continue to make small changes as I come about them. This change was inspired when working of the net library. I was adding simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been received. While upvalues did work, i felt returning data was cleaner and added this feature. # Update: 12.0.0 - Big update (Lots of additions some changes) **Note:** ~~After doing some testing, I have noticed that using multi-objects are slightly, quite a bit, faster than using (coroutines)multi:newthread(). Only create a thread if there is no other possibility! System threads are different and will improve performance if you know what you are doing. Using a (coroutine)thread as a loop with a -is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with seperate objects. And finally, reduced function overhead help keeps the threads running better. +is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with separate objects. And finally, reduced function overhead help keeps the threads running better. **Note:** The nodeManager is being reworked! This will take some time before it is in a stable state. The old version had some major issues that caused it to perform poorly. -**Note:** Version names were brought back to reality this update. When transistioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library +**Note:** Version names were brought back to reality this update. When transitioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library Added: --- @@ -1745,11 +1745,11 @@ Added: - `multi:nodeManager(port)` - `thread.isThread()` — for coroutine based threads - New setting to the main loop, stopOnError which defaults to true. This will cause the objects that crash, when under protect, to be destroyed. So the error does not keep happening. -- multi:threadloop(settings) works just like mainloop, but prioritizes (corutine based) threads. Regular multi-objects will still work. This improves the preformance of (coroutine based) threads greatly. +- multi:threadloop(settings) works just like mainloop, but prioritizes (coroutine based) threads. Regular multi-objects will still work. This improves the performance of (coroutine based) threads greatly. - multi.OnPreLoad — an event that is triggered right before the mainloop starts Changed: -- When a (corutine based)thread errors it does not print anymore! Conect to multi.OnError() to get errors when they happen! +- When a (coroutine based)thread errors it does not print anymore! Connect to multi.OnError() to get errors when they happen! - Connections get yet another update. Connect takes an additional argument now which is the position in the table that the func should be called. Note: Fire calls methods backwards so 1 is the back and the # of connections (the default value) is the beginning of the call table - The love2d compat layer has now been revamped allowing module creators to connect to events without the user having to add likes of code for those events. Its all done automagically. - This library is about 8 years old and using 2.0.0 makes it seem young. I changed it to 12.0.0 since it has some huge changes and there were indeed 12 major releases that added some cool things. Going forward I'll use major.minor.bugfix @@ -2499,7 +2499,7 @@ function comma_value(amount) end return formatted end -multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded enviroment as well as a call to multi:mainloop() +multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded environment as well as a call to multi:mainloop() multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 1"):OnBench(function(self,c) GLOBAL["T1"]=c multi:Stop() end) end) multi:newSystemThread("test2",function() -- spawns a thread in another lua process diff --git a/init.lua b/init.lua index bf65b70..dc4f8ca 100644 --- a/init.lua +++ b/init.lua @@ -35,7 +35,7 @@ if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "15.3.0" +multi.Version = "16.0.0" multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL diff --git a/integration/pesudoManager/extensions.lua b/integration/pseudoManager/extensions.lua similarity index 100% rename from integration/pesudoManager/extensions.lua rename to integration/pseudoManager/extensions.lua diff --git a/integration/pesudoManager/init.lua b/integration/pseudoManager/init.lua similarity index 100% rename from integration/pesudoManager/init.lua rename to integration/pseudoManager/init.lua diff --git a/integration/pesudoManager/threads.lua b/integration/pseudoManager/threads.lua similarity index 100% rename from integration/pesudoManager/threads.lua rename to integration/pseudoManager/threads.lua diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec new file mode 100644 index 0000000..06d3b3e --- /dev/null +++ b/rockspecs/multi-16.0-0.rockspec @@ -0,0 +1,39 @@ +package = "multi" +version = "16.0-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v16.0.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"] = "init.lua", + ["multi.integration.lanesManager"] = "integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", + ["multi.integration.pseudoManager"] = "integration/pseudoManager/init.lua", + ["multi.integration.pseudoManager.extensions"] = "integration/pseudoManager/extensions.lua", + ["multi.integration.pseudoManager.threads"] = "integration/pseudoManager/threads.lua", + ["multi.integration.luvitManager"] = "integration/luvitManager.lua", + ["multi.integration.threading"] = "integration/threading.lua", + --["multi.integration.networkManager"] = "integration/networkManager.lua", + } +} \ No newline at end of file -- 2.43.0 From 5cf3947b15d788bd18417c20f2c4d7857363a433 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 7 Jan 2023 01:32:22 -0500 Subject: [PATCH 002/117] Updated files --- docs/changes.md | 204 ++++++++++++++++++++++++++++++++---------------- init.lua | 88 ++++++++++++++++----- tests/test.lua | 147 +++++++--------------------------- 3 files changed, 233 insertions(+), 206 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 2c3b00d..4bd8fca 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,11 +1,83 @@ # Changelog Table of contents --- +[Update 16.0.0 - ?](#update-1600---?)
[Update 15.3.1 - Bug fix](#update-1531---bug-fix)
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
[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 16.0.0 - ? +Added +--- +- Connection objects can now be concatenated with functions, not each other. For example: + ```lua + multi, thread = require("multi"):init{print=true,findopt=true} + + local conn1, conn2 = multi:newConnection(), multi:newConnection():fastMode() + conn3 = conn1 + conn2 + + conn1(function() + print("Hi 1") + end) + + conn2(function() + print("Hi 2") + end) + + conn3(function() + print("Hi 3") + end) + + function test(a,b,c) + print("I run before all and control if things go!") + return a>b + end + + conn4 = test .. conn1 + + conn5 = conn2 .. function() print("I run after it all!") end + + conn4:Fire(3,2,3) + + conn5(function() + print("Test 1") + end) + + conn5(function() + print("Test 2") + end) + + conn5(function() + print("Test 3") + end) + + conn5:Fire() + ``` + + Output: + ``` + I run before all and control if things go! + Hi 3 + Hi 1 + Test 1 + Test 2 + Test 3 + I run after it all! + ``` + +Changed +--- + +Removed +--- + +Fixed +--- + +ToDo +--- + # Update 15.3.1 - Bug fix Fixed --- @@ -203,7 +275,7 @@ 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. PseudoThreading will kick in to emulate the threading features if you do not have access to system threading. +-- 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 @@ -368,7 +440,7 @@ Added: Changed: --- -- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Example below: +- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: ```lua multi, thread = require("multi"):init() @@ -607,7 +679,7 @@ Added: Example: ```lua local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available +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 func = jq:newFunction("test",function(a,b) @@ -636,7 +708,7 @@ multi:mainloop() ## multi.TIMEOUT -`multi.TIMEOUT` is equal to "TIMEOUT", it is recommended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. +`multi.TIMEOUT` is equal to "TIMEOUT", it is reccomended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. ## new connections on threaded functions @@ -874,7 +946,7 @@ Full Update Showcase --- ```lua local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your environment and uses what's available +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 func = jq:newFunction("test",function(a,b) @@ -906,20 +978,20 @@ multi:mainloop() ``` Note: --- -This was supposed to be released over a year ago, but work and other things got in my way. Pseudo-Threading now works. The goal of this is so you can write modules that can be scaled up to utilize threading features when available. +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) - Creates an isolated thread that prevents both locals and globals from being accessed. - - Was designed for the pseudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intended for what I wanted them to do! -- Added new integration: pseudoManager, functions just like lanesManager and loveManager, but it's actually single threaded - - This was implemented because, you may want to build your code around being multi threaded, but some systems/implementations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatically are threaded if built correctly. I am planning on adding more threadedOjbects. -- In addition to adding pseudo Threading `multi.integration.threading` can now be used to autodetect which environment you are on and use the threading features. + - Was designed for the pesudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intened for what I wanted them to do! +- Added new integration: pesudoManager, functions just like lanesManager and loveManager, but it's actually single threaded + - This was implemented because, you may want to build your code around being multi threaded, but some systems/implemetations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatcally are threaded if built correctly. I am planning on adding more threadedOjbects. +- In addition to adding pesudo Threading `multi.integration.threading` can now be used to autodetect which enviroment you are on and use the threading features. ``` GLOBAL,THREAD = require("multi.integration.threading"):init() ``` - If you are using love2d it will use that, if you have lanes available then it will use lanes. Otherwise it will use pseudo-threading. This allows module creators to implement scalable features without having to worry about which environment they are in. Can now require a consistent module: `require("multi.integration.threading"):init()` + If you are using love2d it will use that, if you have lanes avaialble then it will use lanes. Otherwise it will use pesudo threading. This allows module creators to implement scalable features without having to worry about which enviroment they are in. Can now require a consistant module: `require("multi.integration.threading"):init()` Changed: --- @@ -927,7 +999,7 @@ Changed: Removed: --- -- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatibility with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how environments worked which means that you can no longer modify an environment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded environment into a threadedfunction. This in hindsight wasn't the best practice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyone's code in a massive way. +- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatiblity with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how enviroments worked which means that you can no longer modify an enviroment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded enviroment into a threadedfunction. This in hindsight wasn't the best pratice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyones code in a massive way. Fixed: --- @@ -1048,9 +1120,9 @@ Removed: (Cleaning up a lot of old features) - multi:setDomainName(name)* - multi:linkDomain(name)* - multi:_Pause()* — Use multi:Stop() instead! -- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for checking. +- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for chacking. - multi.executeFunction(name,...)* -- multi:getError()* — Errors are no longer received like that, multi.OnError(func) is the way to go +- multi:getError()* — Errors are nolonger gotten like that, multi.OnError(func) is the way to go - multi.startFPSMonitior()* - multi.doFPS(s)* @@ -1072,7 +1144,7 @@ end) serv.OnStarted(function(self,data) print("Started!",self.Type,data) data.test = "Testing..." - -- self as reference to the object and data is a reference to the data table that the service has access to + -- self as reference to the object and data is a reference to the datatable that the service has access to end) serv:Start() serv:SetPriority(multi.Priority_Idle) @@ -1113,7 +1185,7 @@ setmetatable(example,{ print("We did it!",a,b) rawset(t,k,v) -- This means by using a threaded function we can get around the yielding across metamethods. - -- This is useful if you aren't using luajit, or if you using lua in an environment that is on version 5.1 + -- This is useful if you aren't using luajit, or if you using lua in an enviroment that is on version 5.1 -- There is a gotcha however, if using code that was meant to work with another coroutine based scheduler this may not work end, __index = thread:newFunction(function(t,k,v) -- Using a threaded function as the metamethod @@ -1125,7 +1197,7 @@ setmetatable(example,{ example["test"] = "We set a variable!" print(example["test"]) print(example.hi) --- When not in a threaded environment at root level we need to tell the code that we are waiting! Alternatively after the function argument we can pass true to force a wait +-- When not in a threaded enviroment at root level we need to tell the code that we are waiting! Alternitavely after the function argument we can pass true to force a wait c,d = test().wait() print(c,d) a,b = 6,7 @@ -1228,10 +1300,10 @@ Added: Changed: --- - threaded functions no longer auto detect the presence of arguments when within a threaded function. However, you can use the holup method to produce the same effect. If you plan on using a function in different ways then you can use .wait() and .connect() without setting the holup argument -- thread:newFunction(func,holup) — Added an argument holup to always force the threaded function to wait. Meaning you don't need to tell it to func().wait() or func().connect() +- thread:newFunction(func,holup) — Added an argument holup to always force the threaded funcion to wait. Meaning you don't need to tell it to func().wait() or func().connect() - multi:newConnection(protect,callback,kill) — Added the kill argument. Makes connections work sort of like a stack. Pop off the connections as they get called. So a one time connection handler. - - I'm not sure callback has been documented in any form. callback gets called each and every time conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. -- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both environments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistent. looping through upvalues and sterilizing them and sending them are very complex and slow. + - I'm not sure callback has been documented in any form. callback gets called each and everytime conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. +- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both enviroments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistant. looping through upvalues and sterlizing them and sending them are very complex and slow. Removed: --- @@ -1240,7 +1312,7 @@ Removed: Fixed: --- - Issue where setting the priority of lanes Threads were not working since we were using the data before one could have a chance to set it. This has been resolved! -- Issue where connections object:conn() was firing based on the existence of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing +- Issue where connections object:conn() was firing based on the existance of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing - Issue where async functions connect wasn't properly triggering when a function returned - Issue where async functions were not passing arguments properly. - Issue where async functions were not handling errors properly @@ -1263,12 +1335,12 @@ Fixed: Added: --- -- multi.init() — Initializes the library! Must be called for multiple files to have the same handle. Example below -- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but times out when a certain amount of time has passed! +- multi.init() — Initlizes the library! Must be called for multiple files to have the same handle. Example below +- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but timesout when a certain amount of time has passed! - multi.hold(function or number) — It's back and better than ever! Normal multi objs without threading will all be halted where threads will still run. If within a thread continue using thread.hold() and thread.sleep() - thread.holdWithin(NUMBER; cycles,FUNCTION; condition) — Holds until the condition is met! If the number of cycles passed is equal to cycles, hold will return a timeout error -- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicking the functionality of thread.holdWithin -**Note:** when hold has a timeout the first argument will return nil and the second argument will be TIMEOUT, if not timed out hold will return the values from the conditions +- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicing the functionality of thread.holdWithin +**Note:** when hold has a timeout the first argument will return nil and the second atgument will be TIMEOUT, if not timed out hold will return the values from the conditions - thread objects now have hooks that allow you to interact with it in more refined ways! -- tObj.OnDeath(self,status,returns[...]) — This is a connection that passes a reference to the self, the status, whether or not the thread ended or was killed, and the returns of the thread. -- tObj.OnError(self,error) — returns a reference to self and the error as a string @@ -1278,7 +1350,7 @@ Added: -- returns a function that gives you the option to wait or connect to the returns of the function. -- func().wait() — waits for the function to return works both within a thread and outside of one -- func().connect() — connects to the function finishing --- func() — If your function does not return anything you don't have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing +-- func() — If your function does not return anything you dont have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing -- 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() @@ -1351,7 +1423,7 @@ multi:mainloop() Fixed: --- -- Connections had a performance issue where they would create a non function when using connection.getConnection() of a non existing label. +- Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label. - An internal mismanagement of the threads scheduler was fixed. Now it should be quicker and free of bugs - Thread error management is the integrations was not properly implemented. This is now fixed @@ -1382,18 +1454,18 @@ Changed: ```lua local multi, thread = require("multi").init() -- The require multi function still returns the multi object like before ``` -- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistent code as well +- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistant code as well Note: Using init allows you to get access to the thread handle. This was done because thread was modifying the global space as well as multi. I wanted to not modify the global space anymore. internally most of your code can stay the same, you only need to change how the library is required. I do toy a bit with the global space, buy I use a variable name that is invalid as a variable name. The variable name is $multi. This is used internally to keep some records and maintain a clean space -Also when using integrations things now look more consistent. +Also when using intergrations things now look more consistant. ```lua local multi, thread = require("multi").init() local GLOBSL, THREAD = require("multi.integration.lanesManager").init() -- or whichever manager you are using local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti() ``` -Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create coroutine based threads within both network and system threads. This gives you quite a bit of flexibility to create something awesome. +Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome. Going forward: --- @@ -1406,7 +1478,7 @@ Added: --- - Connections:Lock() — Prevents a connection object form being fired - Connections:Unlock() — Removes the restriction imposed by conn:Lock() -- new functions added to the thread namespace +- new fucntions added to the thread namespace -- thread.request(THREAD handle,STRING cmd,VARARGS args) — allows you to push thread requests from outside the running thread! Extremely powerful. -- thread.exec(FUNCTION func) — Allows you to push code to run within the thread execution block! - handle = multi:newThread() — now returns a thread handle to interact with the object outside fo the thread @@ -1418,18 +1490,18 @@ Fixed: --- - Minor bug with multi:newThread() in how names and functions were managed - Major bug with the system thread handler. Saw healthy threads as dead ones -- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler operates. +- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler opperates. - newSystemThread()'s returned object now matches both the lanes and love2d in terms of methods that are usable. Error handling of System threads now behave the same across both love and lanes implementations. -- looks like I found a typo, thread.yield -> thread.yield +- looks like I found a typo, thread.yeild -> thread.yield Changed: --- -- getTasksDetails("t"), the table variant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details +- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details - TID has been added to multi objects. They count up from 0 and no 2 objects will have the same number - thread.hold() — As part of the memory leaks that I had to fix thread.hold() is slightly different. This change shouldn't impact previous code at all, but thread.hold() can not only return at most 7 arguments! -- You should notice some faster code execution from threads, the changes improve performance of threads greatly. They are now much faster than before! -- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be separated from the thread objects! -- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requiring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! +- You should notice some faster code execution from threads, the changes improve preformance of threads greatly. They are now much faster than before! +- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be seperated from the thread objects! +- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! Tasks Details Table format @@ -1490,7 +1562,7 @@ Changed: --- - A few things, to make concepts in the library more clear. - The way functions returned paused status. Before it would return "PAUSED" now it returns nil, true if paused -- Modified the connection object to allow for some more syntactical sugar! +- Modified the connection object to allow for some more syntaxial suger! - System threads now trigger an OnError connection that is a member of the object itself. multi.OnError() is no longer triggered for a system thread that crashes! Connection Example: @@ -1517,7 +1589,7 @@ print(func()) -- nil, true Removed: --- -- Ranges and conditions — coroutine based threads can emulate what these objects did and much better! +- Ranges and conditions — corutine based threads can emulate what these objects did and much better! - Due to the creation of hyper threaded processes the following objects are no more! - ~~multi:newThreadedEvent()~~ - ~~multi:newThreadedLoop()~~ @@ -1532,33 +1604,33 @@ These didn't have much use in their previous form, but with the addition of hype Fixed: --- -- There were some bugs in the networkmanager.lua file. Destroy -> Destroy some misspellings. +- There were some bugs in the networkmanager.lua file. Desrtoy -> Destroy some misspellings. - Massive object management bugs which caused performance to drop like a rock. - Found a bug with processors not having the Destroy() function implemented properly. -- Found an issue with the rockspec which is due to the networkManager addition. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately -- Insane performance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) -- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increasing the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? +- Found an issue with the rockspec which is due to the networkManager additon. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately +- Insane proformance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) +- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increading the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? - Fixed an issue with processors not properly destroying objects within them and not being destroyable themselves - Fixed a bug where pause and resume would duplicate objects! Not good -- Noticed that the switching of lua states, coroutine based threading, is slower than multi-objs (Not by much though). +- Noticed that the switching of lua states, corutine based threading, is slower than multi-objs (Not by much though). - multi:newSystemThreadedConnection(name,protect) — I did it! It works and I believe all the gotchas are fixed as well. - Issue one, if a thread died that was connected to that connection all connections would stop since the queue would get clogged! FIXED - There is one thing, the connection does have some handshakes that need to be done before it functions as normal! Added: --- -- Documentation, the purpose of 13.0.0, originally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. +- Documentation, the purpose of 13.0.0, orginally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. - multi:newHyperThreadedProcess(STRING name) — This is a version of the threaded process that gives each object created its own coroutine based thread which means you can use thread.* without affecting other objects created within the hyper threaded processes. Though, creating a self contained single thread is a better idea which when I eventually create the wiki page I'll discuss - multi:newConnector() — A simple object that allows you to use the new connection Fire syntax without using a multi obj or the standard object format that I follow. - multi:purge() — Removes all references to objects that are contained withing the processes list of tasks to do. Doing this will stop all objects from functioning. Calling Resume on an object should make it work again. - multi:getTasksDetails(STRING format) — Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format - multi:endTask(TID) — Use multi:getTasksDetails("t") to get the tid of a task - multi:enableLoadDetection() — Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it -- THREAD.getID() — returns a unique ID for the current thread. This variable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! +- THREAD.getID() — returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! - multi.print(...) works like normal print, but only prints if the setting print is set to true - setting: `print` enables multi.print() to work - STC: IgnoreSelf defaults to false, if true a Fire command will not be sent to the self -- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are generated for you. +- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are genereated for you. - STC: FireTo(id,...) — Described above. ```lua @@ -1598,19 +1670,19 @@ Table format for getTasksDetails(STRING format) } } ``` -**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a significant increase in performance. +**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance. **Going forward:** - Work on system threaded functions - work on the node manager - patch up bugs -- finish documentation +- finish documentstion # Update 12.2.2 - Time for some more bug fixes! Fixed: --- -- multi.Stop() not actually stopping due to the new priority management scheme and performance boost changes. +- multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes. # Update 12.2.1 - Time for some bug fixes! @@ -1621,13 +1693,13 @@ Fixed: SystemThreadedJobQueues - No longer need to use jobqueue.OnReady() The code is smarter and will send the pushed jobs automatically when the threads are ready Fixed: SystemThreadedConnection -- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that exist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. +- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that ecist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. Removed: multi:newQueuer -- This feature has no real use after coroutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. +- This feature has no real use after corutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. Going forward: -- Will I ever finish sterilization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. +- Will I ever finish steralization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. - I need to make better documentation for this library as well. In its current state, all I have are examples and not a list of what is what. Example @@ -1655,8 +1727,8 @@ multi:mainloop() - Priority 3 has been added! - ResetPriority() — This will set a flag for a process to be re evaluated for how much of an impact it is having on the performance of the system. - setting: auto_priority added! — If only lua os.clock was more fine tuned... milliseconds are not enough for this to work -- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lower bound for priority. The default is a hybrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that -- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priority +- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lowbound for pirority. The defualt is a hyrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that +- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priotity - setting: auto_delay added! — sets the time in seconds that the system will recheck for low performing processes and manage them. Will also upgrade a process if it starts to run better. ```lua -- All methods that did not return before now return a copy of itself. Thus allowing chaining. Most if not all mutators returned nil, so chaining can now be done. I will eventually write up a full documentation of everything which will show this. @@ -1672,7 +1744,7 @@ multi:mainloop{ ``` Priority 3 works a bit differently than the other 2. -P1 follows a formula that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. +P1 follows a forumla that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. ``` C: 3322269 ~I*7 H: 2847660 ~I*6 @@ -1705,17 +1777,17 @@ L: 2120906 I: 2120506 ``` -Auto Priority works by seeing what should be set high or low. Due to lua not having more precision than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the default is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. +Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. **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 improvements wont make much of a difference. -- Loops have been optimized as well! Being the most used objects I felt they needed to be made as fast as possible +- 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 explanation 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. +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 individual objects. If its possible to make the library even faster then I will go for it. +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. # Update 12.1.0 - Threads just can't hold on anymore @@ -1750,16 +1822,16 @@ multi:mainloop() Going forward: --- -Continue to make small changes as I come about them. This change was inspired when working of the net library. I was adding simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been received. While upvalues did work, i felt returning data was cleaner and added this feature. +Contunue to make small changes as I come about them. This change was inspired when working of the net library. I was addind simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been recieved. While upvalues did work, i felt returning data was cleaner and added this feature. # Update: 12.0.0 - Big update (Lots of additions some changes) **Note:** ~~After doing some testing, I have noticed that using multi-objects are slightly, quite a bit, faster than using (coroutines)multi:newthread(). Only create a thread if there is no other possibility! System threads are different and will improve performance if you know what you are doing. Using a (coroutine)thread as a loop with a -is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with separate objects. And finally, reduced function overhead help keeps the threads running better. +is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with seperate objects. And finally, reduced function overhead help keeps the threads running better. **Note:** The nodeManager is being reworked! This will take some time before it is in a stable state. The old version had some major issues that caused it to perform poorly. -**Note:** Version names were brought back to reality this update. When transitioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library +**Note:** Version names were brought back to reality this update. When transistioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library Added: --- @@ -1769,11 +1841,11 @@ Added: - `multi:nodeManager(port)` - `thread.isThread()` — for coroutine based threads - New setting to the main loop, stopOnError which defaults to true. This will cause the objects that crash, when under protect, to be destroyed. So the error does not keep happening. -- multi:threadloop(settings) works just like mainloop, but prioritizes (coroutine based) threads. Regular multi-objects will still work. This improves the performance of (coroutine based) threads greatly. +- multi:threadloop(settings) works just like mainloop, but prioritizes (corutine based) threads. Regular multi-objects will still work. This improves the preformance of (coroutine based) threads greatly. - multi.OnPreLoad — an event that is triggered right before the mainloop starts Changed: -- When a (coroutine based)thread errors it does not print anymore! Connect to multi.OnError() to get errors when they happen! +- When a (corutine based)thread errors it does not print anymore! Conect to multi.OnError() to get errors when they happen! - Connections get yet another update. Connect takes an additional argument now which is the position in the table that the func should be called. Note: Fire calls methods backwards so 1 is the back and the # of connections (the default value) is the beginning of the call table - The love2d compat layer has now been revamped allowing module creators to connect to events without the user having to add likes of code for those events. Its all done automagically. - This library is about 8 years old and using 2.0.0 makes it seem young. I changed it to 12.0.0 since it has some huge changes and there were indeed 12 major releases that added some cool things. Going forward I'll use major.minor.bugfix @@ -2523,7 +2595,7 @@ function comma_value(amount) end return formatted end -multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded environment as well as a call to multi:mainloop() +multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded enviroment as well as a call to multi:mainloop() multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 1"):OnBench(function(self,c) GLOBAL["T1"]=c multi:Stop() end) end) multi:newSystemThread("test2",function() -- spawns a thread in another lua process diff --git a/init.lua b/init.lua index 8169c61..60b966e 100644 --- a/init.lua +++ b/init.lua @@ -35,7 +35,7 @@ if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "16.0.0" +multi.Version = "15.3.1" multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL @@ -124,17 +124,19 @@ function multi:newConnection(protect,func,kill) local c={} local call_funcs = {} local lock = false + c.__connectionAdded = function() end + c.rawadd = false c.callback = func - c.Parent=self + c.Parent = self - setmetatable(c,{__call=function(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,...)) + if v == self then + local ref = self:Connect(select(2, ...)) if ref then - ref.root_link = select(1,...) + ref.root_link = select(1, ...) return ref end return self @@ -145,6 +147,36 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, + __concat = function(obj1, obj2) + local cn = multi:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) then + obj2:Fire(...) + end + end) + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + print("Fire") + obj1:Fire(...) + print("call") + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + else + error("Invalid concat!", type(obj1), type(obj2)) + end + return cn + end, __add = function(c1,c2) -- Or local cn = multi:newConnection() c1(function(...) @@ -245,16 +277,15 @@ function multi:newConnection(protect,func,kill) end end - local fast = {} function c:getConnections() return call_funcs end function c:Unconnect(conn) if conn.fast then - for i = 1, #fast do - if conn.ref == fast[i] then - table.remove(fast, i) + for i = 1, #call_funcs do + if conn.ref == call_funcs[i] then + table.remove(call_funcs, i) end end elseif conn.Destroy then @@ -266,12 +297,12 @@ function multi:newConnection(protect,func,kill) if find_optimization then return self end function self:Fire(...) if lock then return end - for i=1,#fast do - fast[i](...) + for i=1,#call_funcs do + call_funcs[i](...) end end function self:Connect(func) - table.insert(fast, func) + table.insert(call_funcs, func) local temp = {fast = true} setmetatable(temp,{ __call=function(s,...) @@ -291,6 +322,11 @@ function multi:newConnection(protect,func,kill) end, }) temp.ref = func + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(temp) + end return temp end return self @@ -349,7 +385,6 @@ function multi:newConnection(protect,func,kill) end function temp:Destroy() - multi.print("Calling Destroy on a connection link is deprecated and will be removed in v16.0.0") for i=#call_funcs,1,-1 do if call_funcs[i]~=nil then if call_funcs[i]==self.func then @@ -372,22 +407,33 @@ function multi:newConnection(protect,func,kill) return temp end - function c:Connect(...)--func,name,num + function c:Connect(...) -- func, name, num local tab = {...} - local funcs={} - for i=1,#tab do + local funcs = {} + for i = 1, #tab do if type(tab[i])=="function" then - funcs[#funcs+1] = tab[i] + 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])) + for i = 1, #funcs do + table.insert(ret, conn_helper(self, funcs[i])) + end + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(ret) end return ret else - return conn_helper(self,tab[1],tab[2],tab[3]) + local conn = conn_helper(self, tab[1], tab[2], tab[3]) + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(conn) + end + return conn end end diff --git a/tests/test.lua b/tests/test.lua index 4387b11..e264b1d 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,133 +1,42 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,findopt=true} -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -multi:getOptimizationConnection()(function(msg) - print(msg) + +local conn1, conn2 = multi:newConnection(), multi:newConnection():fastMode() +conn3 = conn1 + conn2 + +conn1(function() + print("Hi 1") end) --- local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection():fastMode(), multi:newConnection() - --- local link = conn1(function() --- print("Conn1, first") --- end) - --- local link2 = conn1(function() --- print("Conn1, second") --- end) - --- local link3 = conn1(function() --- print("Conn1, third") --- end) - --- local link4 = conn2(function() --- print("Conn2, first") --- end) - --- local link5 = conn2(function() --- print("Conn2, second") --- end) - --- local link6 = conn2(function() --- print("Conn2, third") --- end) - --- print("All conns\n-------------") --- conn1:Fire() --- conn2:Fire() - --- conn1:Unconnect(link3) --- conn2:Unconnect(link6) --- print("All conns Edit\n---------------------") --- conn1:Fire() --- conn2:Fire() - --- thread:newThread(function() --- print("Awaiting status") --- thread.hold(conn1 + (conn2 * conn3)) --- print("Conn or Conn2 and Conn3") --- end) - --- multi:newAlarm(1):OnRing(function() --- print("Conn") --- conn1:Fire() --- end) --- multi:newAlarm(2):OnRing(function() --- print("Conn2") --- conn2:Fire() --- end) --- multi:newAlarm(3):OnRing(function() --- print("Conn3") --- conn3:Fire() --- end) - -local conn = multi:newSystemThreadedConnection("conn"):init() - -multi:newSystemThread("Thread_Test_1", function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:mainloop() +conn2(function() + print("Hi 2") end) -multi:newSystemThread("Thread_Test_2", function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(2):OnRing(function() - console.print("Fire 2!!!") - conn:Fire(4,5,6) - THREAD.kill() - end) - - multi:mainloop() -end) -local console = THREAD.getConsole() -conn(function(a,b,c) - console.print("Mainloop conn got triggered!",a,b,c) +conn3(function() + print("Hi 3") end) -alarm = multi:newAlarm(1) -alarm:OnRing(function() - console.print("Fire 1!!!") - conn:Fire(1,2,3) +function test(a,b,c) + print("I run before all and control if things go!") + return a>b +end + +conn4 = test .. conn1 + +conn5 = conn2 .. function() print("I run after it all!") end + +conn4:Fire(3,2,3) + +conn5(function() + print("Test 1") end) -alarm = multi:newAlarm(3):OnRing(function() - multi:newSystemThread("Thread_Test_3",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local console = THREAD.getConsole() - conn(function(a,b,c) - console.print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(4):OnRing(function() - console.print("Fire 3!!!") - conn:Fire(7,8,9) - end) - multi:mainloop() - end) +conn5(function() + print("Test 2") end) -multi:newSystemThread("Thread_Test_4",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - local conn2 = multi:newConnection() - local console = THREAD.getConsole() - multi:newAlarm(2):OnRing(function() - conn2:Fire() - end) - multi:newThread(function() - console.print("Conn Test!") - thread.hold(conn + conn2) - console.print("It held!") - end) - multi:mainloop() +conn5(function() + print("Test 3") end) -multi:mainloop() \ No newline at end of file +conn5:Fire() \ No newline at end of file -- 2.43.0 From a81e55f41c605b58696c2b21cf5625bd5f8149d9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 7 Jan 2023 01:34:41 -0500 Subject: [PATCH 003/117] Updated readme --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f467bd2..05897c2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Multi Version: 15.3.1 A world of Connections +# Multi Version: 16.0.0 **Key Changes** -- SystemThreadedConnections -- Restructured the directory structure of the repo (Allows for keeping multi as a submodule and being able to require it as is) -- Bug fixes +- Concat connections Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! -- 2.43.0 From cf980951a4dc6ba0a01011fcbd7807783cee6d0e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 7 Jan 2023 01:35:32 -0500 Subject: [PATCH 004/117] Updated version --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 60b966e..118caa6 100644 --- a/init.lua +++ b/init.lua @@ -35,7 +35,7 @@ if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} end -multi.Version = "15.3.1" +multi.Version = "16.0.0" multi.Name = "root" multi.NIL = {Type="NIL"} local NIL = multi.NIL -- 2.43.0 From 0bccc0dd877baaeaa0e9769e6db2571c0f49fee3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 7 Jan 2023 09:57:31 -0500 Subject: [PATCH 005/117] Concat conns now properly transfer events --- init.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/init.lua b/init.lua index 118caa6..f38d49e 100644 --- a/init.lua +++ b/init.lua @@ -156,11 +156,13 @@ function multi:newConnection(protect,func,kill) obj2:Fire(...) end end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end elseif type(obj1) == "table" and type(obj2) == "function" then ref = cn(function(...) - print("Fire") obj1:Fire(...) - print("call") obj2(...) end) cn.__connectionAdded = function() @@ -325,7 +327,7 @@ function multi:newConnection(protect,func,kill) if self.rawadd then self.rawadd = false else - self.__connectionAdded(temp) + self.__connectionAdded(temp, func) end return temp end @@ -418,12 +420,13 @@ function multi:newConnection(protect,func,kill) if #funcs>1 then local ret = {} for i = 1, #funcs do - table.insert(ret, conn_helper(self, funcs[i])) - end - if self.rawadd then - self.rawadd = false - else - self.__connectionAdded(ret) + local temp = conn_helper(self, funcs[i]) + table.insert(ret, temp) + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(temp, funcs[i]) + end end return ret else @@ -431,7 +434,7 @@ function multi:newConnection(protect,func,kill) if self.rawadd then self.rawadd = false else - self.__connectionAdded(conn) + self.__connectionAdded(conn, tab[1]) end return conn end -- 2.43.0 From 7114b87bdd05eb9e4d01f0c639175f6d2dda1d52 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 10 Jan 2023 00:07:12 -0500 Subject: [PATCH 006/117] Testing types --- init.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/init.lua b/init.lua index f38d49e..dca42cc 100644 --- a/init.lua +++ b/init.lua @@ -82,6 +82,24 @@ function multi.Stop() mainloopActive = false end +-- Types +multi.DESTROYED = multi.DestroyedObj +multi.ROOTPROCESS = "rootprocess" +multi.CONNECTOR = "connector" +multi.CONNECTOR_LINK = "connector_link" +multi.TIMEMASTER = "timemaster" +multi.PROCESS = "process" +multi.TIMER = "timer" +multi.EVENT = "event" +multi.UPDATER = "updater" +multi.ALARM = "alarm" +multi.LOOP = "loop" +multi.TLOOP = "tloop" +multi.STEP = "step" +multi.TSTEP = "tstep" +multi.THREAD = "thread" +multi.SERVICE = "service" + --Processor local priorityTable = {[false]="Disabled",[true]="Enabled"} local ProcessName = {"SubProcessor","MainProcessor"} @@ -91,6 +109,10 @@ function multi:getProcessors() return processes end +function multi:isType(type) + return self.Type == type +end + function multi:getStats() local stats = { [multi.Name] = { -- 2.43.0 From 3776fdff9d65188ceadf199e0b108b8d4c5ed941 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 11 Jan 2023 22:53:03 -0500 Subject: [PATCH 007/117] Connections can be % with functions --- docs/changes.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 30 ++++++++++++++++++++- tests/test.lua | 45 ++++++------------------------- 3 files changed, 107 insertions(+), 38 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 4bd8fca..ef90391 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -10,6 +10,40 @@ Table of contents # Update 16.0.0 - ? Added --- +- Connection objects now support the % function. This supports a function % connection object. What it does is allow you to **mod**ify the incoming arguments of a connection event. + ```lua + local conn1 = multi:newConnection() + local conn2 = function(a,b,c) return a*2, b*2, c*2 end % conn1 + conn2(function(a,b,c) + print("Conn2",a,b,c) + end) + conn1(function(a,b,c) + print("Conn1",a,b,c) + end) + conn1:Fire(1,2,3) + conn2:Fire(1,2,3) + ``` + Output: + ``` + Conn2 2 4 6 + Conn1 1 2 3 + Conn2 1 2 3 + ``` + **Note:** Conn1 does not get modified, however firing conn1 will also fire conn2 and have it's arguments modified. Also firing conn2 directly **does not** modify conn2's arguments! + See it's implementation below: + ```lua + __mod = function(obj1, obj2) + local cn = multi:newConnection() + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + else + error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end + ``` - Connection objects can now be concatenated with functions, not each other. For example: ```lua multi, thread = require("multi"):init{print=true,findopt=true} @@ -66,6 +100,42 @@ Added I run after it all! ``` + **Note:** Concat of connections does modify internal events on both connections depending on the direction func .. conn or conn .. func See implemention below: + ```lua + __concat = function(obj1, obj2) + local cn = multi:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) then + obj2:Fire(...) + end + end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + obj1:Fire(...) + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + else + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + end + return cn + end + ``` + Changed --- diff --git a/init.lua b/init.lua index dca42cc..a392bad 100644 --- a/init.lua +++ b/init.lua @@ -82,6 +82,10 @@ function multi.Stop() mainloopActive = false end +local function pack(...) + return {...} +end + -- Types multi.DESTROYED = multi.DestroyedObj multi.ROOTPROCESS = "rootprocess" @@ -140,6 +144,19 @@ local CRef = { Fire = function() end } +--[[ + cn(function(...) + local data = pack(obj1(...)) + local len = #data + if len ~= 0 then + if data[1] == true then + obj2:Fire(...) + else + obj2:Fire(unpack(data)) + end + end + end) +]] local optimization_stats = {} local ignoreconn = true function multi:newConnection(protect,func,kill) @@ -169,6 +186,17 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, + __mod = function(obj1, obj2) + local cn = multi:newConnection() + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + else + error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end, __concat = function(obj1, obj2) local cn = multi:newConnection() local ref @@ -197,7 +225,7 @@ function multi:newConnection(protect,func,kill) end) end else - error("Invalid concat!", type(obj1), type(obj2)) + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") end return cn end, diff --git a/tests/test.lua b/tests/test.lua index e264b1d..abfa52c 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,42 +1,13 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,findopt=true} -local conn1, conn2 = multi:newConnection(), multi:newConnection():fastMode() -conn3 = conn1 + conn2 - -conn1(function() - print("Hi 1") +local conn1 = multi:newConnection() +local conn2 = function(a,b,c) return a*2, b*2, c*2 end % conn1 +conn2(function(a,b,c) + print("Conn2",a,b,c) end) - -conn2(function() - print("Hi 2") +conn1(function(a,b,c) + print("Conn1",a,b,c) end) - -conn3(function() - print("Hi 3") -end) - -function test(a,b,c) - print("I run before all and control if things go!") - return a>b -end - -conn4 = test .. conn1 - -conn5 = conn2 .. function() print("I run after it all!") end - -conn4:Fire(3,2,3) - -conn5(function() - print("Test 1") -end) - -conn5(function() - print("Test 2") -end) - -conn5(function() - print("Test 3") -end) - -conn5:Fire() \ No newline at end of file +conn1:Fire(1,2,3) +conn2:Fire(1,2,3) \ No newline at end of file -- 2.43.0 From a7a902acd68339aa128310ea49d56af2473e3a61 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 15 Jan 2023 13:28:45 -0500 Subject: [PATCH 008/117] Updated connections --- docs/changes.md | 9 +++ init.lua | 210 +++++++++++++----------------------------------- 2 files changed, 65 insertions(+), 154 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index ef90391..a61588e 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -138,9 +138,18 @@ Added Changed --- +- Connections internals changed, not too much changed on the surface. +- newConnection(protect, func, kill) + - `protect` disables fastmode, but protects the connection + - `func` uses `..` and appends func to the connection so it calls it after all connections run. There is some internal overhead added when using this, but it isn't much. + - `kill` removes the connection when fired + + **Note:** When using protect/kill connections are triggered in reverse order Removed --- +- conn:SetHelper(func) -- With the removal of old Connect this function is nolonger needed +- connection events can nolonger can be chained with connect. Connect only takes a function that you want to connect Fixed --- diff --git a/init.lua b/init.lua index a392bad..3d078d4 100644 --- a/init.lua +++ b/init.lua @@ -159,16 +159,16 @@ local CRef = { ]] local optimization_stats = {} local ignoreconn = true -function multi:newConnection(protect,func,kill) +function multi:newConnection(protect, func, kill) local c={} local call_funcs = {} local lock = false c.__connectionAdded = function() end c.rawadd = false - c.callback = func c.Parent = self - setmetatable(c,{__call=function(self, ...) + setmetatable(c,{ + __call=function(self, ...) -- () local t = ... if type(t)=="table" then for i,v in pairs(t) do @@ -186,7 +186,7 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, - __mod = function(obj1, obj2) + __mod = function(obj1, obj2) -- % local cn = multi:newConnection() if type(obj1) == "function" and type(obj2) == "table" then obj2(function(...) @@ -197,7 +197,7 @@ function multi:newConnection(protect,func,kill) end return cn end, - __concat = function(obj1, obj2) + __concat = function(obj1, obj2) -- .. local cn = multi:newConnection() local ref if type(obj1) == "function" and type(obj2) == "table" then @@ -294,41 +294,16 @@ function multi:newConnection(protect,func,kill) end function c:Lock() - lock = true + lock = self.Fire + self.Fire = function() end return self end function c:Unlock() - lock = false + self.Fire = lock 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 - local suc, err = pcall(call_funcs[i],...) - if not suc then - print(err) - end - if kill then - table.remove(call_funcs,i) - end - end - end - else - function c:Fire(...) - if lock then return end - for i=#call_funcs,1,-1 do - call_funcs[i](...) - if kill then - table.remove(call_funcs,i) - end - end - end - end - function c:getConnections() return call_funcs end @@ -345,75 +320,18 @@ function multi:newConnection(protect,func,kill) end end - function c:fastMode() - if find_optimization then return self end - function self:Fire(...) - if lock then return end - for i=1,#call_funcs do - call_funcs[i](...) - end + function c:Fire(...) + for i=1,#call_funcs do + call_funcs[i](...) end - function self:Connect(func) - table.insert(call_funcs, func) - local temp = {fast = true} - 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, - }) - temp.ref = func - if self.rawadd then - self.rawadd = false - else - self.__connectionAdded(temp, func) - end - return temp - end - return self 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 - } + -- Not needed anymore, since it's so light, I'll leave it in forever + function c:fastMode() return self end + function c:Connect(func) + table.insert(call_funcs, func) + local temp = {fast = true} setmetatable(temp,{ __call=function(s,...) return self:Connect(...) @@ -431,68 +349,25 @@ function multi:newConnection(protect,func,kill) rawset(t,k,v) end, }) - - function temp:Fire(...) - return call_funcs(...) + temp.ref = func + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(temp, func) 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 - local temp = conn_helper(self, funcs[i]) - table.insert(ret, temp) - if self.rawadd then - self.rawadd = false - else - self.__connectionAdded(temp, funcs[i]) - end - end - return ret - else - local conn = conn_helper(self, tab[1], tab[2], tab[3]) - if self.rawadd then - self.rawadd = false - else - self.__connectionAdded(conn, tab[1]) - end - return conn - end + function c:Bind(t) + local temp = call_funcs + call_funcs=t + return temp end - function c:SetHelper(func) - conn_helper = func - return self + function c:Remove() + local temp = call_funcs + call_funcs={} + return temp end if find_optimization then @@ -504,9 +379,36 @@ function multi:newConnection(protect,func,kill) c.HasConnections = c.hasConnections c.GetConnection = c.getConnection + if protect then -- Do some tests and override the fastmode if you want to do something differently + function c:Fire(...) + for i=#call_funcs,1,-1 do + if not call_funcs[i] then return end + local suc, err = pcall(call_funcs[i],...) + if not suc then + print(err) + end + if kill then + table.remove(call_funcs,i) + end + end + end + elseif kill then + function c:Fire(...) + for i=#call_funcs,1,-1 do + call_funcs[i](...) + table.remove(call_funcs,i) + end + end + end + + if func then + c = c .. func + end + if not(ignoreconn) then multi:create(c) end + return c end -- 2.43.0 From 6ed5555706d27fc2484db37536c56f0b9de209bd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 17 Jan 2023 23:45:26 -0500 Subject: [PATCH 009/117] Fixed issue with double thread activations (Looking for another solution) --- init.lua | 32 +++++++++++++++---------------- integration/lanesManager/init.lua | 4 ++-- integration/loveManager/init.lua | 8 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/init.lua b/init.lua index 3d078d4..c052218 100644 --- a/init.lua +++ b/init.lua @@ -976,10 +976,10 @@ end local sandcount = 1 -function multi:newProcessor(name,nothread) +function multi:newProcessor(name, nothread) local c = {} setmetatable(c,{__index = multi}) - local name = name or "Processor_"..sandcount + local name = name or "Processor_" .. sandcount sandcount = sandcount + 1 c.Mainloop = {} c.Type = "process" @@ -989,12 +989,12 @@ function multi:newProcessor(name,nothread) c.startme = {} c.parent = self - local handler = c:createHandler(c.threads,c.startme) + local handler = c:createHandler(c.threads, c.startme) if not nothread then -- Don't create a loop if we are triggering this manually c.process = self:newLoop(function() if Active then - c:uManager() + c:uManager(true) handler() end end) @@ -1005,8 +1005,8 @@ function multi:newProcessor(name,nothread) else c.OnError = multi:newConnection() end + c.OnError(multi.print) - function c:getThreads() return c.threads @@ -1027,15 +1027,15 @@ function multi:newProcessor(name,nothread) return t end - function c:newFunction(func,holdme) + function c:newFunction(func, holdme) return thread:newFunctionBase(function(...) return c:newThread("TempThread",func,...) - end,holdme)() + end, holdme)() end function c.run() if not Active then return end - c:uManager() + c:uManager(true) handler() return c end @@ -1274,7 +1274,7 @@ end local handler -function thread:newFunctionBase(generator,holdme) +function thread:newFunctionBase(generator, holdme) return function() local tfunc = {} tfunc.Active = true @@ -1351,7 +1351,7 @@ function thread:newFunctionBase(generator,holdme) t.statusconnector = temp.OnStatus return temp end - setmetatable(tfunc,tfunc) + setmetatable(tfunc, tfunc) return tfunc end end @@ -1863,17 +1863,17 @@ function multi.init(settings, realsettings) return _G["$multi"].multi,_G["$multi"].thread end -function multi:uManager() +function multi:uManager(proc) if self.Active then __CurrentProcess = self multi.OnPreLoad:Fire() self.uManager=self.uManagerRef multi.OnLoad:Fire() - handler() + if not proc then handler() end end end -function multi:uManagerRefP1() +function multi:uManagerRefP1(proc) if self.Active then __CurrentProcess = self local Loop=self.Mainloop @@ -1886,11 +1886,11 @@ function multi:uManagerRefP1() end end end - handler() + if not proc then handler() end end end -function multi:uManagerRef() +function multi:uManagerRef(proc) if self.Active then __CurrentProcess = self local Loop=self.Mainloop @@ -1899,7 +1899,7 @@ function multi:uManagerRef() __CurrentTask:Act() __CurrentProcess = self end - handler() + if not proc then handler() end end end diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index d53e12f..f9da3c4 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -55,10 +55,10 @@ local count = 1 local started = false local livingThreads = {} -function THREAD:newFunction(func,holdme) +function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) return multi:newSystemThread("TempSystemThread",func,...) - end,holdme)() + end, holdme)() end function multi:newSystemThread(name, func, ...) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index dd0e929..bdbc50f 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -72,7 +72,7 @@ function multi:newSystemThread(name,func,...) return c.name end thread:newThread(function() - if name:find("TempSystemThread") then + if name == "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 @@ -101,10 +101,10 @@ function multi:newSystemThread(name,func,...) return c end -function THREAD:newFunction(func) +function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return multi:newSystemThread("TempSystemThread"..THREAD_ID,func,...) - end)() + return multi:newSystemThread("TempSystemThread", func, ...) + end, holdme)() end THREAD.newSystemThread = multi.newSystemThread -- 2.43.0 From 22f1375380e5c74c5940144d6c6644901dc6bd2a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 18 Jan 2023 15:42:38 -0500 Subject: [PATCH 010/117] Working on issue with love threaded functions not waiting when in a thread --- init.lua | 11 ++++--- integration/loveManager/init.lua | 49 ++++++++++++++------------------ 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/init.lua b/init.lua index c052218..62eeabd 100644 --- a/init.lua +++ b/init.lua @@ -22,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] +traceback = debug.traceback + local multi = {} local mainloopActive = false local isRunning = false @@ -1471,10 +1473,10 @@ function thread:newISOThread(name,func,_env,...) env.multi = multi end if type(name) == "function" then - name = "Thread#"..threadCount + name = "Thread#" .. threadCount end - local func = isolateFunction(func,env) - return thread:newThread(name,func,...) + local func = isolateFunction(func, env) + return thread:newThread(name, func,...) end multi.newThread = thread.newThread @@ -1486,7 +1488,7 @@ local ret,_ local task, thd, ref, ready local switch = { function(th,co)--hold - if clock() - th.intervalR>=th.interval then + 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 @@ -1615,6 +1617,7 @@ co_status = { else ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) end + print(ref.Name, traceback()) if i then table.remove(th,i) else diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index bdbc50f..ed415ae 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -20,8 +20,7 @@ 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 ISTHREAD then +]] if ISTHREAD then error("You cannot require the loveManager from within a thread!") end local ThreadFileData = [[ @@ -49,50 +48,46 @@ local multi, thread = require("multi"):init() local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" -multi.integration={} +multi.integration = {} local THREAD = require("multi.integration.loveManager.threads") local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 local OBJECT_ID = 0 local stf = 0 -function multi:newSystemThread(name,func,...) +function multi:newSystemThread(name, func, ...) local c = {} c.name = name - c.ID=THREAD_ID - c.thread=love.thread.newThread(ThreadFileData) - c.thread:start(THREAD.dump(func),c.ID,c.name,...) - c.stab = THREAD.createStaticTable(name) - c.OnDeath = multi:newConnection() - c.OnError = multi:newConnection() - GLOBAL["__THREAD_"..c.ID] = {ID=c.ID, Name=c.name, Thread=c.thread} + c.ID = THREAD_ID + c.thread = love.thread.newThread(ThreadFileData) + c.thread:start(THREAD.dump(func), c.ID, c.name, ...) + c.stab = THREAD.createStaticTable(name .. c.ID) + 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 - function c:getName() - return c.name - end + THREAD_ID = THREAD_ID + 1 + function c:getName() return c.name end thread:newThread(function() if name == "TempSystemThread" then - local status_channel = love.thread.getChannel("__"..c.ID.."__MULTI__STATUS_CHANNEL__") + local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) 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 + if status:peek() ~= nil then c.statusconnector:Fire(unpack(status:pop())) end return not c.thread:isRunning() end) else - thread.hold(function() - return not c.thread:isRunning() - end) + 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) + c.OnError:Fire(c, thread_err) elseif c.stab.returns then c.OnDeath:Fire(unpack(c.stab.returns)) c.stab.returns = nil @@ -102,21 +97,19 @@ function multi:newSystemThread(name,func,...) end function THREAD:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return multi:newSystemThread("TempSystemThread", func, ...) - end, holdme)() + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread", func, ...) + end, holdme)() end THREAD.newSystemThread = multi.newSystemThread function love.threaderror(thread, errorstr) - multi.print("Thread error!\n"..errorstr) + multi.print("Thread error!\n" .. errorstr) end multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.loveManager.extensions") multi.print("Integrated Love Threading!") -return {init=function() - return GLOBAL,THREAD -end} \ No newline at end of file +return {init = function() return GLOBAL, THREAD end} -- 2.43.0 From 0994ee2d2ad20a7cfd30ca8f711955f435d22888 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 19 Jan 2023 00:16:32 -0500 Subject: [PATCH 011/117] Working on issue where threads created in threads don't work --- init.lua | 230 ++++++++++++---------------- integration/loveManager/init.lua | 16 +- integration/loveManager/threads.lua | 10 -- 3 files changed, 107 insertions(+), 149 deletions(-) diff --git a/init.lua b/init.lua index 62eeabd..9f38426 100644 --- a/init.lua +++ b/init.lua @@ -22,16 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -traceback = debug.traceback - local multi = {} local mainloopActive = false local isRunning = false local clock = os.clock local thread = {} -local in_proc = false local processes = {} local find_optimization = false +local threadManager if not _G["$multi"] then _G["$multi"] = {multi=multi,thread=thread} @@ -387,7 +385,7 @@ function multi:newConnection(protect, func, kill) if not call_funcs[i] then return end local suc, err = pcall(call_funcs[i],...) if not suc then - print(err) + multi.print(err) end if kill then table.remove(call_funcs,i) @@ -667,7 +665,7 @@ function multi:newEvent(task) end c.OnEvent = self:newConnection():fastMode() self:setPriority("core") - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end @@ -689,7 +687,7 @@ function multi:newUpdater(skip) return self end c.OnUpdate = self:newConnection():fastMode() - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end @@ -726,7 +724,7 @@ function multi:newAlarm(set) self.Parent.Pause(self) return self end - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end @@ -751,7 +749,7 @@ function multi:newLoop(func,notime) end multi:create(c) - c:SetName(c.Type) + c:setName(c.Type) return c end @@ -809,7 +807,7 @@ function multi:newStep(start,reset,count,skip) self:Resume() return self end - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end @@ -845,13 +843,13 @@ function multi:newTLoop(func,set) if func then c.OnLoop(func) end - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end -function multi:setTimeout(func,t) - thread:newThread(function() thread.sleep(t) func() end) +function multi:setTimeout(func, t) + thread:newThread("TimeoutThread",function() thread.sleep(t) func() end) end function multi:newTStep(start,reset,count,set) @@ -895,7 +893,7 @@ function multi:newTStep(start,reset,count,set) self:Resume() return self end - c:SetName(c.Type) + c:setName(c.Type) multi:create(c) return c end @@ -909,7 +907,7 @@ local function _task_handler() end function multi:newTask(func) - multi:newThread("Task Handler",function() + thread:newThread("Task Handler",function() while true do thread.hold(function() return _tasks > 0 @@ -1022,16 +1020,13 @@ function multi:newProcessor(name, nothread) return self.Name end - function c:newThread(name,func,...) - in_proc = c - local t = thread.newThread(c,name,func,...) - in_proc = false - return t + function c:newThread(name, func,...) + return thread.newThread(c, name, func, ...) end function c:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return c:newThread("TempThread",func,...) + return c:newThread("Threaded Function Handler", func, ...) end, holdme)() end @@ -1324,7 +1319,7 @@ function thread:newFunctionBase(generator, holdme) f(nil,"Function is paused") end } - end + end local t = generator(...) t.OnDeath(function(...) rets = {...} end) t.OnError(function(self,e) err = e end) @@ -1358,10 +1353,10 @@ function thread:newFunctionBase(generator, holdme) end end -function thread:newFunction(func,holdme) +function thread:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return thread:newThread("TempThread",func,...) - end,holdme)() + return thread:newThread("Threaded Function Handler", func, ...) + end, holdme)() end -- A cross version way to set enviroments, not the same as fenv though @@ -1388,7 +1383,7 @@ function thread:newThread(name,func,...) c.Name=name c.thread=create(func) c.sleep=1 - c.Type="thread" + c.Type = "thread" c.TID = threadid c.firstRunDone=false c._isPaused = false @@ -1427,12 +1422,12 @@ function thread:newThread(name,func,...) end function c:Kill() - thread.request(self,"kill") + thread.request(self, "kill") return self end function c:Sleep(n) - thread.request(self,"exec",function() + thread.request(self, "exec",function() thread.sleep(n) resumed = false end) @@ -1440,8 +1435,8 @@ function thread:newThread(name,func,...) end function c:Hold(n,opt) - thread.request(self,"exec",function() - thread.hold(n,opt) + thread.request(self, "exec",function() + thread.hold(n, opt) resumed = false end) return self @@ -1449,13 +1444,17 @@ function thread:newThread(name,func,...) c.Destroy = c.Kill - if self.Type=="process" then - table.insert(self.startme,c) + if self.Type == "process" then + multi.print("Creating thread (" .. self.Name .."):", name) + table.insert(self.startme, c) else - table.insert(startme,c) + multi.print("Creating thread (Global_Thread_Manager):", name) + if type(name) == "function" then + multi.print(debug.traceback()) + end + table.insert(threadManager.startme, c) end - - startme_len = #startme + globalThreads[c] = multi threadid = threadid + 1 multi:create(c) @@ -1463,7 +1462,7 @@ function thread:newThread(name,func,...) return c end -function thread:newISOThread(name,func,_env,...) +function thread:newISOThread(name, func, _env, ...) local func = func or name local env = _env or {} if not env.thread then @@ -1473,10 +1472,10 @@ function thread:newISOThread(name,func,_env,...) env.multi = multi end if type(name) == "function" then - name = "Thread#" .. threadCount + name = "Thread#"..threadCount end - local func = isolateFunction(func, env) - return thread:newThread(name, func,...) + local func = isolateFunction(func,env) + return thread:newThread(name,func,...) end multi.newThread = thread.newThread @@ -1488,7 +1487,7 @@ local ret,_ local task, thd, ref, ready local switch = { function(th,co)--hold - if clock() - th.intervalR >= th.interval then + 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 @@ -1617,7 +1616,6 @@ co_status = { else ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) end - print(ref.Name, traceback()) if i then table.remove(th,i) else @@ -1632,30 +1630,6 @@ co_status = { 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) @@ -1716,7 +1690,7 @@ function multi:newService(func) -- Priority managed threads return c end - local th = thread:newThread(function() + local th = thread:newThread("Service_Handler",function() while true do process() end @@ -1809,7 +1783,6 @@ local function mainloop(self) ctask:Act() __CurrentProcess = self end - handler() end else return nil, "Already Running!" @@ -1839,13 +1812,65 @@ local function p_mainloop(self) end end end - handler() end else return nil, "Already Running!" end end +local function doOpt() + 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 + local cache = string.dump(n) + local f_str = tostring(n) + local good = true + for i=1,#func_cache do + if func_cache[i][1] == cache and func_cache[i][2] ~= f_str and not func_cache[i][3] then + multi:getOptimizationConnection():Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) + func_cache[i][3] = true + good = false + end + end + if good then + table.insert(func_cache, {cache, f_str}) + end + return yield(CMD, t_hold, n or dFunc, nil, interval) + else + error("Invalid argument passed to thread.hold(...)!") + end + end +end + local init = false function multi.init(settings, realsettings) if settings == multi then settings = realsettings end @@ -1860,23 +1885,23 @@ function multi.init(settings, realsettings) end if settings.findopt then find_optimization = true + doOpt() multi.enableOptimization:Fire(multi, thread) end end return _G["$multi"].multi,_G["$multi"].thread end -function multi:uManager(proc) +function multi:uManager() if self.Active then __CurrentProcess = self multi.OnPreLoad:Fire() self.uManager=self.uManagerRef multi.OnLoad:Fire() - if not proc then handler() end end end -function multi:uManagerRefP1(proc) +function multi:uManagerRefP1() if self.Active then __CurrentProcess = self local Loop=self.Mainloop @@ -1889,11 +1914,10 @@ function multi:uManagerRefP1(proc) end end end - if not proc then handler() end end end -function multi:uManagerRef(proc) +function multi:uManagerRef() if self.Active then __CurrentProcess = self local Loop=self.Mainloop @@ -1902,7 +1926,6 @@ function multi:uManagerRef(proc) __CurrentTask:Act() __CurrentProcess = self end - if not proc then handler() end end end @@ -2211,64 +2234,7 @@ else multi.m.sentinel = newproxy(true) getmetatable(multi.m.sentinel).__gc = multi.m.onexit end -local func_cache = {} -multi:newThread(function() - thread.skip() - if find_optimization then - - 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 - local cache = string.dump(n) - local f_str = tostring(n) - local good = true - for i=1,#func_cache do - if func_cache[i][1] == cache and func_cache[i][2] ~= f_str and not func_cache[i][3] then - multi:getOptimizationConnection():Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) - func_cache[i][3] = true - good = false - end - end - if good then - table.insert(func_cache, {cache, f_str}) - end - return yield(CMD, t_hold, n or dFunc, nil, interval) - else - error("Invalid argument passed to thread.hold(...)!") - end - end - -- Add more Overrides - end -end) + +threadManager = multi:newProcessor("Global_Thread_Manager").Start() return multi \ No newline at end of file diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index ed415ae..0cb3ee9 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -20,9 +20,12 @@ 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 ISTHREAD then +]] + +if ISTHREAD then error("You cannot require the loveManager from within a thread!") end + local ThreadFileData = [[ ISTHREAD = true THREAD = require("multi.integration.loveManager.threads") @@ -35,7 +38,7 @@ math.randomseed(__THREADID__) math.random() math.random() math.random() -stab = THREAD.createStaticTable(__THREADNAME__) +stab = THREAD.createStaticTable(__THREADNAME__ .. __THREADID__) GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() multi.integration={} @@ -68,14 +71,13 @@ function multi:newSystemThread(name, func, ...) GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID = THREAD_ID + 1 function c:getName() return c.name end - thread:newThread(function() + thread:newThread(name .. "_System_Thread_Handler",function() if name == "TempSystemThread" then local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) 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())) + if status_channel:peek() ~= nil then + c.statusconnector:Fire(unpack(status_channel:pop())) end return not c.thread:isRunning() end) @@ -98,7 +100,7 @@ end function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return multi:newSystemThread("TempSystemThread", func, ...) + return multi:newSystemThread("SystemThreaded Function Handler", func, ...) end, holdme)() end diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index d867d6d..41e573f 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,7 +25,6 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") -local socket = require("socket") local multi, thread = require("multi").init() local threads = {} @@ -49,15 +48,6 @@ local function manage(channel, value) end end -local function RandomVariable(length) - local res = {} - math.randomseed(socket.gettime()*10000) - for i = 1, length do - res[#res+1] = string.char(math.random(97, 122)) - end - return table.concat(res) -end - local GNAME = "__GLOBAL_" local proxy = {} function threads.set(name,val) -- 2.43.0 From ef9267d8fc722770c73e84ce08749d09e13f1d46 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 20 Jan 2023 00:11:55 -0500 Subject: [PATCH 012/117] Fixed broken threads for love --- init.lua | 95 ++++++++++++----------------- integration/loveManager/threads.lua | 19 ++---- 2 files changed, 43 insertions(+), 71 deletions(-) diff --git a/init.lua b/init.lua index 9f38426..cfba28b 100644 --- a/init.lua +++ b/init.lua @@ -32,7 +32,7 @@ local find_optimization = false local threadManager if not _G["$multi"] then - _G["$multi"] = {multi=multi,thread=thread} + _G["$multi"] = {multi = multi, thread = thread} end multi.Version = "16.0.0" @@ -140,23 +140,7 @@ end function multi.ForEach(tab,func) for i=1,#tab do func(tab[i]) end end -local CRef = { - Fire = function() end -} ---[[ - cn(function(...) - local data = pack(obj1(...)) - local len = #data - if len ~= 0 then - if data[1] == true then - obj2:Fire(...) - else - obj2:Fire(unpack(data)) - end - end - end) -]] local optimization_stats = {} local ignoreconn = true function multi:newConnection(protect, func, kill) @@ -849,7 +833,7 @@ function multi:newTLoop(func,set) end function multi:setTimeout(func, t) - thread:newThread("TimeoutThread",function() thread.sleep(t) func() end) + thread:newThread("TimeoutThread", function() thread.sleep(t) func() end) end function multi:newTStep(start,reset,count,set) @@ -901,23 +885,18 @@ end local tasks = {} local _tasks = 0 -local function _task_handler() +local function _task_handler(self, func) tasks[#tasks + 1] = func _tasks = _tasks + 1 end function multi:newTask(func) - thread:newThread("Task Handler",function() - while true do - thread.hold(function() - return _tasks > 0 - end) - for i=1,_tasks do - tasks[i]() - end - _tasks = 0 + multi:newLoop(function(loop) + for i=1,_tasks do + tasks[i]() end - end) + _tasks = 0 + end):setName("Task Handler") -- Re bind this method to use the one that doesn't init a thread! multi.newTask = _task_handler tasks[#tasks + 1] = func @@ -989,7 +968,7 @@ function multi:newProcessor(name, nothread) c.startme = {} c.parent = self - local handler = c:createHandler(c.threads, c.startme) + local handler = c:createHandler(c) if not nothread then -- Don't create a loop if we are triggering this manually c.process = self:newLoop(function() @@ -1362,21 +1341,17 @@ end -- A cross version way to set enviroments, not the same as fenv though function multi.setEnv(func,env) local f = string.dump(func) - local chunk = load(f,"env","bt",env) + local chunk = load(f, "env", "bt", env) return chunk end -local threads = {} -local startme = {} -local startme_len = 0 -function thread:newThread(name,func,...) +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 func == name then name = name or multi.randomString(16) 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={} @@ -1404,7 +1379,7 @@ function thread:newThread(name,func,...) local resumed = false function c:Pause() if not self._isPaused then - thread.request(self,"exec",function() + thread.request(self, "exec", function() thread.hold(function() return resumed end) @@ -1427,7 +1402,7 @@ function thread:newThread(name,func,...) end function c:Sleep(n) - thread.request(self, "exec",function() + thread.request(self, "exec", function() thread.sleep(n) resumed = false end) @@ -1435,7 +1410,7 @@ function thread:newThread(name,func,...) end function c:Hold(n,opt) - thread.request(self, "exec",function() + thread.request(self, "exec", function() thread.hold(n, opt) resumed = false end) @@ -1443,16 +1418,21 @@ function thread:newThread(name,func,...) end c.Destroy = c.Kill - - if self.Type == "process" then - multi.print("Creating thread (" .. self.Name .."):", name) - table.insert(self.startme, c) + if thread.isThread() then + multi:newLoop(function(loop) + if self.Type == "process" then + table.insert(self.startme, c) + else + table.insert(threadManager.startme, c) + end + loop:Break() + end) else - multi.print("Creating thread (Global_Thread_Manager):", name) - if type(name) == "function" then - multi.print(debug.traceback()) + if self.Type == "process" then + table.insert(self.startme, c) + else + table.insert(threadManager.startme, c) end - table.insert(threadManager.startme, c) end globalThreads[c] = multi @@ -1474,8 +1454,8 @@ function thread:newISOThread(name, func, _env, ...) if type(name) == "function" then name = "Thread#"..threadCount end - local func = isolateFunction(func,env) - return thread:newThread(name,func,...) + local func = isolateFunction(func, env) + return thread:newThread(name, func, ...) end multi.newThread = thread.newThread @@ -1631,16 +1611,17 @@ co_status = { end, } -function multi:createHandler(threads,startme) - return coroutine.wrap(function(self) +function multi:createHandler() + local threads, startme = self.threads, self.startme + return coroutine.wrap(function() 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) + _, 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) + table.insert(threads, temp_start) yield() end for i=#threads,1,-1 do @@ -1649,7 +1630,7 @@ function multi:createHandler(threads,startme) task = ref.task thd = ref.thread ready = ref.__ready - co_status[status(thd)](thd,ref,task,i,threads) + co_status[status(thd)](thd, ref, task, i, threads) end yield() end @@ -1676,7 +1657,7 @@ function multi:newService(func) -- Priority managed threads time = self:newTimer() time:Start() active = true - c:OnStarted(c,Service_Data) + c:OnStarted(c, Service_Data) end return c end @@ -1685,7 +1666,7 @@ function multi:newService(func) -- Priority managed threads thread.hold(function() return active end) - func(c,Service_Data) + func(c, Service_Data) task(ap) return c end diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 41e573f..5176eaf 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,7 +25,7 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") -local multi, thread = require("multi").init() +local multi, thread = require("multi"):init() local threads = {} function threads.loadDump(d) @@ -180,20 +180,11 @@ function threads.getConsole() end if not ISTHREAD then - local clock = os.clock - local lastproc = clock() local queue = love.thread.getChannel("__CONSOLE__") - thread:newThread("consoleManager",function() - while true do - thread.yield() - dat = queue:pop() - if dat then - lastproc = clock() - print(unpack(dat)) - end - if clock()-lastproc>2 then - thread.sleep(.1) - end + multi:newLoop(function(loop) + dat = queue:pop() + if dat then + print(unpack(dat)) end end) end -- 2.43.0 From 7da192e8dc920ef158f4cafbdd5cd0d91b53b215 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 21 Jan 2023 13:18:44 -0500 Subject: [PATCH 013/117] Fixed some issues with threads --- docs/changes.md | 27 +++++++++++++++++++++++++-- init.lua | 20 ++++++++++++++++---- integration/loveManager/init.lua | 11 ++++++++--- integration/loveManager/threads.lua | 4 ++-- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index a61588e..503444b 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -148,11 +148,34 @@ Changed Removed --- -- conn:SetHelper(func) -- With the removal of old Connect this function is nolonger needed -- connection events can nolonger can be chained with connect. Connect only takes a function that you want to connect +- conn:SetHelper(func) -- With the removal of old Connect this function is no longer needed +- connection events can no longer can be chained with connect. Connect only takes a function that you want to connect Fixed --- +- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events + ```lua + func = thread:newFunction(function() + for i=1,10 do + thread.sleep(1) + thread.pushStatus(i) + end + end) + + func2 = thread:newFunction(function() + local ref = func() + ref.OnStatus(function(num) + -- do stuff with this data + + thread.pushStatus(num*2) -- Technically this is not ran within a thread. This is ran outside of a thread inside the thread handler. + end) + end) + + local handler = func2() + handler.OnStatus(function(num) + print(num) + end) + ``` ToDo --- diff --git a/init.lua b/init.lua index cfba28b..a38ab30 100644 --- a/init.lua +++ b/init.lua @@ -30,6 +30,7 @@ local thread = {} local processes = {} local find_optimization = false local threadManager +local __CurrentConnectionThread if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} @@ -305,7 +306,7 @@ function multi:newConnection(protect, func, kill) end function c:Fire(...) - for i=1,#call_funcs do + for i=1, #call_funcs do call_funcs[i](...) end end @@ -314,6 +315,17 @@ function multi:newConnection(protect, func, kill) function c:fastMode() return self end function c:Connect(func) + local th + if thread.getRunningThread then + th = thread.getRunningThread() + end + if th then + local fref = func + func = function(...) + __CurrentConnectionThread = th + fref(...) + end + end table.insert(call_funcs, func) local temp = {fast = true} setmetatable(temp,{ @@ -1244,7 +1256,7 @@ local function cleanReturns(...) end function thread.pushStatus(...) - local t = thread.getRunningThread() + local t = thread.getRunningThread() or __CurrentConnectionThread t.statusconnector:Fire(...) end @@ -1286,8 +1298,8 @@ function thread:newFunctionBase(generator, holdme) 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 end - tfunc.__call = function(t,...) - if t.Active == false then + tfunc.__call = function(th,...) + if th.Active == false then if holdme then return nil, "Function is paused" end diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 0cb3ee9..b58a84e 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -47,7 +47,14 @@ multi.integration.THREAD = THREAD pcall(require,"multi.integration.loveManager.extensions") stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] + local multi, thread = require("multi"):init() + +-- We do not want to load this module twice +if multi.integration and multi.integration.THREAD then + return multi.integration.GLOBAL, multi.integration.THREAD +end + local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" @@ -55,8 +62,6 @@ multi.integration = {} local THREAD = require("multi.integration.loveManager.threads") local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 -local OBJECT_ID = 0 -local stf = 0 function multi:newSystemThread(name, func, ...) local c = {} @@ -72,7 +77,7 @@ function multi:newSystemThread(name, func, ...) THREAD_ID = THREAD_ID + 1 function c:getName() return c.name end thread:newThread(name .. "_System_Thread_Handler",function() - if name == "TempSystemThread" then + if name == "SystemThreaded Function Handler" then local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) thread.hold(function() -- While the thread is running we might as well do something in the loop diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 5176eaf..74b4954 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -97,9 +97,9 @@ function threads.kill() end function threads.pushStatus(...) - local status_channel = love.thread.getChannel("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__") + local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) local args = {...} - status_channel:push(__THREADID__, args) + status_channel:push(args) end function threads.getThreads() -- 2.43.0 From a105d8a5758298d284354de141770fbee56f45d6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 22 Jan 2023 23:04:09 -0500 Subject: [PATCH 014/117] removed test --- integration/loveManager/init.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index b58a84e..9bf5d2c 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -50,11 +50,6 @@ stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} local multi, thread = require("multi"):init() --- We do not want to load this module twice -if multi.integration and multi.integration.THREAD then - return multi.integration.GLOBAL, multi.integration.THREAD -end - local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" -- 2.43.0 From c65b850529476b19d99c6083bbb64cfff4845aee Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Apr 2023 23:07:39 -0400 Subject: [PATCH 015/117] Updated changes.md --- docs/changes.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 503444b..ff3578b 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -5,7 +5,58 @@ Table of contents [Update 15.3.1 - Bug fix](#update-1531---bug-fix)
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
-[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](#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 16.0.0 - ? Added @@ -48,7 +99,7 @@ Added ```lua multi, thread = require("multi"):init{print=true,findopt=true} - local conn1, conn2 = multi:newConnection(), multi:newConnection():fastMode() + local conn1, conn2 = multi:newConnection(), multi:newConnection() conn3 = conn1 + conn2 conn1(function() -- 2.43.0 From 8e6d174f202b22ea93c1e4fab819734d5f2f8314 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 20 Apr 2023 23:23:51 -0400 Subject: [PATCH 016/117] Plan on testing parity between the threading modules --- docs/changes.md | 4 ++-- integration/lanesManager/extensions.lua | 1 + integration/loveManager/extensions.lua | 2 ++ integration/pseudoManager/extensions.lua | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index ff3578b..f900ae9 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,7 +1,7 @@ # Changelog Table of contents --- -[Update 16.0.0 - ?](#update-1600---?)
+[Update 16.0.0 - Connecting the dots](#update-1600---connecting-the-dots)
[Update 15.3.1 - Bug fix](#update-1531---bug-fix)
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
@@ -58,7 +58,7 @@ Table of contents [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 16.0.0 - ? +# Update 16.0.0 - Connecting the dots Added --- - Connection objects now support the % function. This supports a function % connection object. What it does is allow you to **mod**ify the incoming arguments of a connection event. diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 9600dc6..e9461d2 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -27,6 +27,7 @@ if not (GLOBAL and THREAD) then else lanes = require("lanes") end + function multi:newSystemThreadedQueue(name) local name = name or multi.randomString(16) local c = {} diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index da5b560..69fe921 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -69,6 +69,7 @@ function multi:newSystemThreadedQueue(name) THREAD.package(name,c) return c end + function multi:newSystemThreadedTable(name) local name = name or multi.randomString(16) local c = {} @@ -79,6 +80,7 @@ function multi:newSystemThreadedTable(name) THREAD.package(name,c) return c end + local jqc = 1 function multi:newSystemThreadedJobQueue(n) local c = {} diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 3881f3f..f40ccc2 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -50,6 +50,7 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedTable(name) local c = {} function c:init() @@ -68,6 +69,7 @@ if not setfenv then end end end + function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 -- 2.43.0 From d2cfdfa8e8885d820d3076772745264a2dfc36e9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 24 Apr 2023 00:15:35 -0400 Subject: [PATCH 017/117] Writing tests for system threading --- integration/lanesManager/init.lua | 25 ++++---- integration/lanesManager/threads.lua | 5 ++ integration/loveManager/threads.lua | 4 ++ integration/pseudoManager/threads.lua | 4 ++ tests/threadtests.lua | 89 +++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 tests/threadtests.lua diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index f9da3c4..e9178b9 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -78,19 +78,22 @@ function multi:newSystemThread(name, func, ...) c.alive = true c.priority = THREAD.Priority_Normal local multi_settings = multi.defaultSettings - for i,v in pairs(multi_settings) do - print(i,v) + local globe = { + THREAD_NAME = name, + THREAD_ID = count, + THREAD = THREAD, + GLOBAL = GLOBAL, + _Console = __ConsoleLinda + } + if GLOBAL["__env"] then + for i,v in pairs(GLOBAL["__env"]) do + globe[i] = v + end 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 + globals = globe, + priority = c.priority },function(...) require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") @@ -168,7 +171,7 @@ function multi.InitSystemThreadErrorHandler() end) end -multi.print("Integrated Lanes!") +multi.print("Integrated Lanes Threading!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index f1b8da1..eadbf80 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -134,6 +134,11 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) __GlobalLinda:set(k, v) end }) + + function THREAD.setENV(env) + GLOBAL["__env"] = env + end + return GLOBAL, THREAD end diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 74b4954..2629dd5 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -139,6 +139,10 @@ function threads.getGlobal() ) end +function THREAD.setENV(env) + (threads.getGlobal())["__env"] = env +end + function threads.createTable(n) local _proxy = {} local function set(name,val) diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index b8ee6aa..55e1313 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -102,6 +102,10 @@ local function INIT(thread) THREAD.hold = thread.hold + function THREAD.setENV(env) + GLOBAL["__env"] = env + end + return GLOBAL, THREAD end diff --git a/tests/threadtests.lua b/tests/threadtests.lua new file mode 100644 index 0000000..a04c367 --- /dev/null +++ b/tests/threadtests.lua @@ -0,0 +1,89 @@ +package.path = "../?/init.lua;../?.lua;"..package.path +local multi, thread = require("multi"):init{print=true}--{priority=true} +local proc = multi:newProcessor("Test",true) +local LANES, LOVE, PSEUDO = 1, 2, 3 +local env + +if love then + GLOBAL, THREAD = require("multi.integration.loveManager"):init() + env = LOVE +else + io.write("Test Pseudo(p), Lanes(l), or love(Run in love environment) Threading: ") + choice = io.read() + if choice == "p" then + GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() + env = PSEUDO + elseif choice == "l" then + GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + env = LANES + else + error("Invalid threading choice") + end +end + +THREAD.setENV({ + multi_assert = function(expected, actual, s) + print("Testing") + if expected ~= actual then + error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") + end + end +}) + +multi:newThread("Scheduler Thread",function() + print("Test 1: Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data.") + + queue = multi:newSystemThreadedQueue("Test_Queue"):init() + + th1 = multi:newSystemThread("Test_Thread_2", function(a,b,c,d,e,f) + queue = THREAD.waitFor("Test_Queue"):init() + print("!") + multi_assert("Test_Thread_1", THREAD.getName(), "Thread name does not match!") + print("!") + multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") + multi_assert(true, e, "Argument e is not true!") + multi_assert("table", type(f), "Argument f is not a table!") + queue:push("done") + end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(print) + + if thread.hold(function() + return queue:pop() == "done" + end,{sleep=1}) == nil then + thread.kill() + end + + print("Test 1: Ok") + + print("Test 2: Threaded Functions, arg passing, return passing, holding.") + + func = THREAD:newFunction(function(a,b,c) + assert(a == 3, "First argument expected '3' got '".. a .."'!") + assert(b == 2, "Second argument expected '2' got '".. a .."'!") + assert(c == 1, "Third argument expected '1' got '".. a .."'!") + return 1, 2, 3, {"a table"} + end, true) -- Hold this + + a, b, c, d = func(3,2,1) + + print("Returns passed from function", a, b, c, d) + + if not a then print(b) end + + assert(a == 1, "First return was not '1'!") + assert(b == 2, "Second return was not '2'!") + assert(c == 3, "Third return was not '3'!") + assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") + + print("Test 2: Ok") + + print("Test 3: SystemThreadedTables") + + os.exit() +end).OnError(function(self, err) + print(err) + os.exit() +end) + + + +multi:mainloop() \ No newline at end of file -- 2.43.0 From 61b5ea9d14b6510ae273126b2bdc94241a71318f Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 25 Apr 2023 00:13:47 -0400 Subject: [PATCH 018/117] Added test cases for threading, fixed issues. Todo test love2d --- docs/changes.md | 1 + integration/lanesManager/extensions.lua | 9 +- integration/lanesManager/init.lua | 21 ++-- integration/pseudoManager/extensions.lua | 17 +-- integration/pseudoManager/init.lua | 6 + integration/pseudoManager/threads.lua | 4 +- tests/threadtests.lua | 133 +++++++++++++++++++---- 7 files changed, 149 insertions(+), 42 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index f900ae9..98d1588 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -204,6 +204,7 @@ Removed Fixed --- +- Issue with lanes not handling errors properly. This is now resolved - Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events ```lua func = thread:newFunction(function() diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index e9461d2..42eee9f 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -114,7 +114,6 @@ function multi:newSystemThreadedJobQueue(n) link = c.OnJobCompleted(function(jid,...) if id==jid then rets = {...} - link:Destroy() end end) return thread.hold(function() @@ -278,7 +277,13 @@ function multi:newSystemThreadedConnection(name) self.links = {} self.proxy_conn = multi:newConnection() local mt = getmetatable(self.proxy_conn) - setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + local tempMT = {} + for i,v in pairs(mt) do + tempMT[i] = v + end + tempMT.__index = self.proxy_conn + tempMT.__call = function(t,func) self.proxy_conn(func) end + setmetatable(self, tempMT) if self.CID == THREAD.getID() then return self end thread:newThread("STC_CONN_MAN"..name,function() local item diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index e9178b9..398dc0c 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -95,10 +95,11 @@ function multi:newSystemThread(name, func, ...) globals = globe, priority = c.priority },function(...) - require("multi"):init(multi_settings) + multi, thread = require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") local has_error = true - return_linda:set("returns",{func(...)}) + returns = {pcall(func, ...)} + return_linda:set("returns", returns) has_error = false end)(...) count = count + 1 @@ -138,9 +139,15 @@ function multi.InitSystemThreadErrorHandler() temp.statusconnector:Fire(unpack(({__StatusLinda:receive(nil, temp.Id)})[2])) end if status == "done" or temp.returns:get("returns") then + returns = ({temp.returns:receive(0, "returns")})[2] livingThreads[temp.Id] = {false, temp.Name} temp.alive = false - temp.OnDeath:Fire(unpack(({temp.returns:receive(0, "returns")})[2])) + if returns[1] == false then + temp.OnError:Fire(temp, returns[2]) + else + table.remove(returns,1) + temp.OnDeath:Fire(unpack(returns)) + end GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) elseif status == "running" then @@ -148,11 +155,7 @@ function multi.InitSystemThreadErrorHandler() 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) + -- The thread never really errors, we handle this through our linda object elseif status == "cancelled" then livingThreads[temp.Id] = {false, temp.Name} temp.alive = false @@ -168,7 +171,7 @@ function multi.InitSystemThreadErrorHandler() end end end - end) + end).OnError(print) end multi.print("Integrated Lanes Threading!") diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index f40ccc2..4ae0d72 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -50,7 +50,6 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name or "_"] = c return c end - function multi:newSystemThreadedTable(name) local c = {} function c:init() @@ -69,7 +68,6 @@ if not setfenv then end end end - function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 @@ -96,7 +94,6 @@ function multi:newSystemThreadedJobQueue(n) return jid-1 end function c:isEmpty() - print(#jobs) return #jobs == 0 end local nFunc = 0 @@ -116,7 +113,6 @@ function multi:newSystemThreadedJobQueue(n) link = c.OnJobCompleted(function(jid,...) if id==jid then rets = {...} - link:Destroy() end end) return thread.hold(function() @@ -127,7 +123,7 @@ function multi:newSystemThreadedJobQueue(n) end,holup),name end for i=1,c.cores do - thread:newthread("PesudoThreadedJobQueue_"..i,function() + thread:newThread("PesudoThreadedJobQueue_"..i,function() while true do thread.yield() if #jobs>0 then @@ -137,7 +133,14 @@ function multi:newSystemThreadedJobQueue(n) thread.sleep(.05) end end - end) + end).OnError(print) end return c -end \ No newline at end of file +end + +function multi:newSystemThreadedConnection(name) + local conn = multi.newConnection() + conn.init = function(self) return self end + GLOBAL[name or "_"] = conn + return conn +end \ No newline at end of file diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 84bdca3..26a7144 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -67,6 +67,12 @@ function multi:newSystemThread(name,func,...) THREAD_ID = id, thread = thread } + + if GLOBAL["__env"] then + for i,v in pairs(GLOBAL["__env"]) do + env[i] = v + end + end for i = 1,#tab do env[tab[i]] = _G[tab[i]] diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 55e1313..51e060a 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -101,8 +101,8 @@ local function INIT(thread) THREAD.sleep = thread.sleep THREAD.hold = thread.hold - - function THREAD.setENV(env) + + function THREAD.setENV(env) GLOBAL["__env"] = env end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index a04c367..52dd112 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,6 +1,6 @@ package.path = "../?/init.lua;../?.lua;"..package.path -local multi, thread = require("multi"):init{print=true}--{priority=true} -local proc = multi:newProcessor("Test",true) +multi, thread = require("multi"):init{}--{priority=true} +proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 local env @@ -21,9 +21,9 @@ else end end +print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") THREAD.setENV({ multi_assert = function(expected, actual, s) - print("Testing") if expected ~= actual then error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") end @@ -31,20 +31,19 @@ THREAD.setENV({ }) multi:newThread("Scheduler Thread",function() - print("Test 1: Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data.") - queue = multi:newSystemThreadedQueue("Test_Queue"):init() - th1 = multi:newSystemThread("Test_Thread_2", function(a,b,c,d,e,f) + th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() - print("!") multi_assert("Test_Thread_1", THREAD.getName(), "Thread name does not match!") - print("!") multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") multi_assert(true, e, "Argument e is not true!") multi_assert("table", type(f), "Argument f is not a table!") queue:push("done") - end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(print) + end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) + print("Error:", err) + os.exit() + end) if thread.hold(function() return queue:pop() == "done" @@ -52,9 +51,7 @@ multi:newThread("Scheduler Thread",function() thread.kill() end - print("Test 1: Ok") - - print("Test 2: Threaded Functions, arg passing, return passing, holding.") + print("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") func = THREAD:newFunction(function(a,b,c) assert(a == 3, "First argument expected '3' got '".. a .."'!") @@ -65,24 +62,116 @@ multi:newThread("Scheduler Thread",function() a, b, c, d = func(3,2,1) - print("Returns passed from function", a, b, c, d) - - if not a then print(b) end - assert(a == 1, "First return was not '1'!") assert(b == 2, "Second return was not '2'!") assert(c == 3, "Third return was not '3'!") assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") - print("Test 2: Ok") + print("Threaded Functions, arg passing, return passing, holding: Ok") - print("Test 3: SystemThreadedTables") + test=multi:newSystemThreadedTable("YO"):init() + test["test1"]="tabletest" + local worked = false + multi:newSystemThread("testing tables",function() + tab=THREAD.waitFor("YO"):init() + THREAD.hold(function() return tab["test1"] end) + THREAD.sleep(.1) + tab["test2"] = "Whats so funny?" + end).OnError(print) + + multi:newThread("test2",function() + thread.hold(function() return test["test2"] end) + worked = true + end) + + t, val = thread.hold(function() + return worked + end,{sleep=1}) + + if val == multi.TIMEOUT then + print("SystemThreadedTables: Failed") + os.exit() + end + + print("SystemThreadedTables: Ok") + + local ready = false + + jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads + + func = jq:newFunction("test",function(a,b) + THREAD.sleep(.2) + return a+b + end) + + local count = 0 + for i = 1,10 do + func(i, i*3).OnReturn(function(data) + count = count + 1 + end) + end + + t, val = thread.hold(function() + return count == 10 + end,{sleep=2}) + + if val == multi.TIMEOUT then + print("SystemThreadedJobQueues: Failed") + os.exit() + end + + print("SystemThreadedJobQueues: Ok") + + queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() + multi:newSystemThread("Test_Thread_2",function() + queue2 = THREAD.waitFor("Test_Queue2"):init() + connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + connOut(function(arg) + queue2:push("Test_Thread_2") + end) + multi:mainloop() + end).OnError(print) + + multi:newSystemThread("Test_Thread_3",function() + queue2 = THREAD.waitFor("Test_Queue2"):init() + connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + connOut(function(arg) + queue2:push("Test_Thread_3") + end) + multi:mainloop() + end).OnError(print) + connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() + a=0 + connOut(function(arg) + queue2:push("Main") + end) + for i=1,3 do + thread.sleep(.1) + connOut:Fire("Test From Main Thread: "..i.."\n") + end + thread.sleep(2) + local count = 0 + multi:newThread(function() + while count < 9 do + if queue2:pop() then + count = count + 1 + end + end + end).OnError(print) + + _, err = thread.hold(function() return count == 9 end,{sleep=.3}) + + if err == multi.TIMEOUT then + print("SystemThreadedConnections: Failed") + os.exit() + end + + print("SystemThreadedConnections: Ok") + + print("Tests complete!") os.exit() -end).OnError(function(self, err) - print(err) - os.exit() -end) +end).OnError(print) -- 2.43.0 From 45b51c15c767a9308e15be922513ed8993633141 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 25 Apr 2023 21:31:36 -0400 Subject: [PATCH 019/117] Fixed love2d to succeed with tests --- integration/loveManager/extensions.lua | 1 - integration/loveManager/init.lua | 6 +++++ integration/loveManager/threads.lua | 32 ++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 69fe921..c5b1cc1 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -130,7 +130,6 @@ function multi:newSystemThreadedJobQueue(n) link = c.OnJobCompleted(function(jid,...) if id==jid then rets = {...} - link:Destroy() end end) return thread.hold(function() diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 9bf5d2c..e12098a 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -40,6 +40,12 @@ math.random() math.random() stab = THREAD.createStaticTable(__THREADNAME__ .. __THREADID__) GLOBAL = THREAD.getGlobal() +if GLOBAL["__env"] then + local env = THREAD.unpackENV(GLOBAL["__env"]) + for i,v in pairs(env) do + _G[i] = v + end +end multi, thread = require("multi").init() multi.integration={} multi.integration.GLOBAL = GLOBAL diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 2629dd5..354f46e 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -139,8 +139,36 @@ function threads.getGlobal() ) end -function THREAD.setENV(env) - (threads.getGlobal())["__env"] = env +function threads.packENV(env) + local e = {} + for i,v in pairs(env) do + if type(v) == "function" then + e["$f"..i] = string.dump(v) + elseif type(v) == "table" then + e["$t"..i] = threads.packENV(v) + else + e[i] = v + end + end + return e +end + +function threads.unpackENV(env) + local e = {} + for i,v in pairs(env) do + if type(i) == "string" and i:sub(1,2) == "$f" then + e[i:sub(3,-1)] = loadstring(v) + elseif type(i) == "string" and i:sub(1,2) == "$t" then + e[i:sub(3,-1)] = threads.unpackENV(v) + else + e[i] = v + end + end + return e +end + +function threads.setENV(env) + (threads.getGlobal())["__env"] = threads.packENV(env) end function threads.createTable(n) -- 2.43.0 From d8218f0bf7c7578cc634ab190994c9bd4962d4d2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 25 Apr 2023 21:57:25 -0400 Subject: [PATCH 020/117] All tests working --- docs/changes.md | 12 +++++++++ integration/lanesManager/threads.lua | 4 +++ integration/loveManager/threads.lua | 4 +++ integration/pseudoManager/threads.lua | 4 +++ tests/conf.lua | 39 +++++++++++++++++++++++++++ tests/main.lua | 5 ++++ tests/multi | 1 + 7 files changed, 69 insertions(+) create mode 100644 tests/conf.lua create mode 100644 tests/main.lua create mode 120000 tests/multi diff --git a/docs/changes.md b/docs/changes.md index 98d1588..7f74315 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -61,6 +61,18 @@ Table of contents # Update 16.0.0 - Connecting the dots Added --- +- THREAD.setENV(table) -- Set a simple table that will be merged into the global namespace + + **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only + ```lua + THREAD.setENV({ + shared_function = function() + print("I am shared!") + end + }) + ``` + When this function is used it writes to a special variable that is read at thread spawn time. If this function is then ran later it can be used to set a different env and be applied to future spawned threads. +- THREAD.getENV() can be used to manage advanced uses of the setENV() functionality - Connection objects now support the % function. This supports a function % connection object. What it does is allow you to **mod**ify the incoming arguments of a connection event. ```lua local conn1 = multi:newConnection() diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index eadbf80..5a91813 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -139,6 +139,10 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) GLOBAL["__env"] = env end + function THREAD.getENV() + return GLOBAL["__env"] + end + return GLOBAL, THREAD end diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 354f46e..250b2ec 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -171,6 +171,10 @@ function threads.setENV(env) (threads.getGlobal())["__env"] = threads.packENV(env) end +function threads.getENV() + return threads.unpackENV((threads.getGlobal())["__env"]) +end + function threads.createTable(n) local _proxy = {} local function set(name,val) diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 51e060a..2a3dadd 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -106,6 +106,10 @@ local function INIT(thread) GLOBAL["__env"] = env end + function THREAD.getENV() + return GLOBAL["__env"] + end + return GLOBAL, THREAD end diff --git a/tests/conf.lua b/tests/conf.lua new file mode 100644 index 0000000..1381163 --- /dev/null +++ b/tests/conf.lua @@ -0,0 +1,39 @@ +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.version = "12.0" -- The LOVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + + t.window.title = "GuiManagerTest" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 1280 -- The window width (number) + t.window.height = 720 -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = true -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Standard fullscreen or desktop fullscreen mode (string) + t.window.vsync = false -- Enable vertical sync (boolean) + t.window.fsaa = 2 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = true -- Enable the audio module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = true -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = true -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = true -- Enable the physics module (boolean) + t.modules.sound = true -- Enable the sound module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.timer = true -- Enable the timer module (boolean) + t.modules.window = true -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) +end +--1440 x 2560 diff --git a/tests/main.lua b/tests/main.lua new file mode 100644 index 0000000..b14de79 --- /dev/null +++ b/tests/main.lua @@ -0,0 +1,5 @@ +require("threadtests") + +function love.update() + +end \ No newline at end of file diff --git a/tests/multi b/tests/multi new file mode 120000 index 0000000..d78adc0 --- /dev/null +++ b/tests/multi @@ -0,0 +1 @@ +D:/VSCWorkspace/multi \ No newline at end of file -- 2.43.0 From 0e6c30b478dffb70385e96687f19136dfca5d61c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 25 Apr 2023 22:05:32 -0400 Subject: [PATCH 021/117] Updated files for testing --- tests/conf.lua | 28 ++++++++++++++-------------- tests/main.lua | 5 +---- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/conf.lua b/tests/conf.lua index 1381163..5e75c12 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -3,7 +3,7 @@ function love.conf(t) t.version = "12.0" -- The LOVE version this game was made for (string) t.console = true -- Attach a console (boolean, Windows only) - t.window.title = "GuiManagerTest" -- The window title (string) + t.window.title = "MultiThreadTest" -- The window title (string) t.window.icon = nil -- Filepath to an image to use as the window's icon (string) t.window.width = 1280 -- The window width (number) t.window.height = 720 -- The window height (number) @@ -21,19 +21,19 @@ function love.conf(t) t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) - t.modules.audio = true -- Enable the audio module (boolean) - t.modules.event = true -- Enable the event module (boolean) - t.modules.graphics = true -- Enable the graphics module (boolean) - t.modules.image = true -- Enable the image module (boolean) - t.modules.joystick = true -- Enable the joystick module (boolean) - t.modules.keyboard = true -- Enable the keyboard module (boolean) - t.modules.math = true -- Enable the math module (boolean) - t.modules.mouse = true -- Enable the mouse module (boolean) - t.modules.physics = true -- Enable the physics module (boolean) - t.modules.sound = true -- Enable the sound module (boolean) - t.modules.system = true -- Enable the system module (boolean) - t.modules.timer = true -- Enable the timer module (boolean) - t.modules.window = true -- Enable the window module (boolean) + t.modules.audio = false -- Enable the audio module (boolean) + t.modules.event = false -- Enable the event module (boolean) + t.modules.graphics = false -- Enable the graphics module (boolean) + t.modules.image = false -- Enable the image module (boolean) + t.modules.joystick = false -- Enable the joystick module (boolean) + t.modules.keyboard = false -- Enable the keyboard module (boolean) + t.modules.math = false -- Enable the math module (boolean) + t.modules.mouse = false -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = false -- Enable the sound module (boolean) + t.modules.system = false -- Enable the system module (boolean) + t.modules.timer = false -- Enable the timer module (boolean) + t.modules.window = false -- Enable the window module (boolean) t.modules.thread = true -- Enable the thread module (boolean) end --1440 x 2560 diff --git a/tests/main.lua b/tests/main.lua index b14de79..768ce1c 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,5 +1,2 @@ require("threadtests") - -function love.update() - -end \ No newline at end of file +-- Allows you to run "love tests" which runs the tests \ No newline at end of file -- 2.43.0 From 9e1d31fc2a64567bae65e52c1ca48f87322b3ce0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 25 Apr 2023 22:32:24 -0400 Subject: [PATCH 022/117] Modified tests to make it more seamless --- README.md | 10 ++++++++-- tests/main.lua | 7 ++++++- tests/runtests.lua | 25 ++++++++++++++++--------- tests/threadtests.lua | 6 ++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 05897c2..a5a5248 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Multi Version: 16.0.0 +# Multi Version: 16.0.0 Connecting the dots **Key Changes** - Concat connections @@ -34,12 +34,18 @@ https://discord.gg/U8UspuA Planned features/TODO --------------------- -- [ ] Create test suite (In progress, mostly done) +- [x] 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) ----- +You can run tests in 2 ways: +``` +lua tests/runtests.lua (Runs all tests, attempts to use lanes) +love tests (Runs all tests in love2d env) +``` + ```lua local multi, thread = require("multi"):init() GLOBAL, THREAD = require("multi.integration.threading"):init() diff --git a/tests/main.lua b/tests/main.lua index 768ce1c..232899c 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,2 +1,7 @@ +require("runtests") require("threadtests") --- Allows you to run "love tests" which runs the tests \ No newline at end of file +-- Allows you to run "love tests" which runs the tests + +function love.update() + multi:uManager() +end \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index ae8b098..ff59c52 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -22,7 +22,10 @@ end ]] local multi, thread = require("multi"):init{print=true}--{priority=true} local good = false -local proc = multi:newProcessor("Test",true) +local proc = multi:newProcessor("Test") + +proc.Start() + proc:newAlarm(3):OnRing(function() good = true end) @@ -163,23 +166,27 @@ runTest = thread:newFunction(function() end c3 = false c4 = false - d:Destroy() + conn3:Unconnect(d) 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 + if not love then + print("Testing pseudo threading") + os.execute("lua tests/threadtests.lua p") + print("Testing lanes threading") + os.execute("lua tests/threadtests.lua l") + os.exit() + end end) -print(runTest().OnError(function(...) +runTest().OnError(function(...) print("Error: Something went wrong with the test!") print(...) - os.exit(1) -end)) +end) -print("Pumping proc") -while true do - proc.run() +if not love then + multi:mainloop() end \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 52dd112..fd2e735 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -7,6 +7,12 @@ local env if love then GLOBAL, THREAD = require("multi.integration.loveManager"):init() env = LOVE +elseif arg[1] == "l" then + GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + env = LANES +elseif arg[1] == "p" then + GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() + env = PSEUDO else io.write("Test Pseudo(p), Lanes(l), or love(Run in love environment) Threading: ") choice = io.read() -- 2.43.0 From 8cf047d71322fa71bbae4bb526368045ba750d1e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 29 Apr 2023 13:47:25 -0400 Subject: [PATCH 023/117] removed extra __cores in lanes/pseudo --- NetworkManager.md | 26 ++++++++++++++++++++++++++ integration/lanesManager/threads.lua | 6 ------ integration/pseudoManager/threads.lua | 6 ------ 3 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 NetworkManager.md diff --git a/NetworkManager.md b/NetworkManager.md new file mode 100644 index 0000000..3b40295 --- /dev/null +++ b/NetworkManager.md @@ -0,0 +1,26 @@ +# NTHREAD Namespace +- [ ] NTHREAD.set(name, val) +- [ ] NTHREAD.get(name, val) +- [ ] NTHREAD.waitFor(name) +- [ ] NTHREAD.getCores()* +- [ ] NTHREAD.getConsole() +- [ ] NTHREAD.getThreads() +- [ ] NTHREAD.kill() +- [ ] NTHREAD.getName() +- [ ] NTHREAD.getID() +- [ ] NTHREAD.pushStatus(...) +- [ ] NTHREAD.sleep(n) +- [ ] NTHREAD.hold(n) +- [ ] NTHREAD.setENV(env) +- [ ] NTHREAD.getENV() + +# Extensions +- [ ] multi:newNetworkThreadedQueue(name) +- [ ] multi:newNetworkThreadedTable(name) +- [ ] multi:newNetworkThreadedJobQueue(n) +- [ ] multi:newNetworkThreadedConnection(name) + +# Core +- [ ] NTHREAD:newFunction(func, holdme) +- [ ] NTHREAD:newNetworkThread(name, func, ...) +- [ ] mulit:newNetworkThread(name, func, ...) \ No newline at end of file diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 5a91813..4ba71e7 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -58,12 +58,6 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) 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 diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 2a3dadd..26fc38c 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -80,12 +80,6 @@ local function INIT(thread) 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 -- 2.43.0 From 03dbe1ee5b8aae839d3951c673ec99a84a57c400 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 30 Apr 2023 01:06:20 -0400 Subject: [PATCH 024/117] Working on new priority scheme --- docs/changes.md | 4 ++ init.lua | 38 ++++++++----- integration/priorityManager/init.lua | 81 ++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 integration/priorityManager/init.lua diff --git a/docs/changes.md b/docs/changes.md index 7f74315..49564f3 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -61,6 +61,9 @@ Table of contents # Update 16.0.0 - Connecting the dots Added --- +- multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object +- multi.warn(...) -- Sends a warning. +- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. - THREAD.setENV(table) -- Set a simple table that will be merged into the global namespace **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only @@ -201,6 +204,7 @@ Added Changed --- +- multi.print shows "INFO" before it's message. - Connections internals changed, not too much changed on the surface. - newConnection(protect, func, kill) - `protect` disables fastmode, but protects the connection diff --git a/init.lua b/init.lua index a38ab30..6870b9c 100644 --- a/init.lua +++ b/init.lua @@ -178,7 +178,7 @@ function multi:newConnection(protect, func, kill) cn:Fire(obj1(...)) end) else - error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + multi.error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") end return cn end, @@ -210,7 +210,7 @@ function multi:newConnection(protect, func, kill) end) end else - error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + multi.error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") end return cn end, @@ -576,7 +576,7 @@ end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) - 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 + if not(self.Type=='rootprocess' or self.Type=='process') then multi.error('Can only create an object on multi or an interface obj') return false end local c = {} if self.Type=='process' then setmetatable(c, {__index = multi}) @@ -1187,7 +1187,7 @@ function thread.hold(n,opt) elseif type(n) == "function" then return yield(CMD, t_hold, n or dFunc, nil, interval) else - error("Invalid argument passed to thread.hold(...)!") + multi.error("Invalid argument passed to thread.hold(...)!") end end @@ -1207,7 +1207,7 @@ function thread.skip(n) end function thread.kill() - error("thread killed!") + multi.error("thread killed!") end function thread.yield() @@ -1759,7 +1759,7 @@ function multi:newService(func) -- Priority managed threads end -- Multi runners -local function mainloop(self) +function multi:mainloopRef() __CurrentProcess = self multi.OnPreLoad:Fire() self.uManager = self.uManagerRef @@ -1782,9 +1782,9 @@ local function mainloop(self) end end -multi.mainloop = mainloop +multi.mainloop = multi.mainloopRef -local function p_mainloop(self) +function multi:p_mainloop() __CurrentProcess = self multi.OnPreLoad:Fire() self.uManager = self.uManagerRefP1 @@ -1859,11 +1859,15 @@ local function doOpt() end return yield(CMD, t_hold, n or dFunc, nil, interval) else - error("Invalid argument passed to thread.hold(...)!") + multi.error("Invalid argument passed to thread.hold(...)!") end end end +function multi:setCurrentProcess() + __CurrentProcess = self +end + local init = false function multi.init(settings, realsettings) if settings == multi then settings = realsettings end @@ -1872,9 +1876,9 @@ function multi.init(settings, realsettings) if type(settings)=="table" then multi.defaultSettings = settings if settings.priority then - multi.mainloop = p_mainloop + multi.mainloop = multi.p_mainloop else - multi.mainloop = mainloop + multi.mainloop = multi.mainloopRef end if settings.findopt then find_optimization = true @@ -2195,10 +2199,20 @@ end function multi.print(...) if multi.defaultSettings.print then - print(...) + print("INFO:", table.concat({...}, " ")) end end +function multi.warn(...) + if multi.defaultSettings.warn then + print("WARNING:", table.concat({...}, " ")) + end +end + +function multi.error(err) + error("ERROR: " .. err) +end + multi.GetType = multi.getType multi.IsPaused = multi.isPaused multi.IsActive = multi.isActive diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua new file mode 100644 index 0000000..ca5db00 --- /dev/null +++ b/integration/priorityManager/init.lua @@ -0,0 +1,81 @@ +-- Advanced process management. Mutates the multi namespace +local multi, thread = require("multi"):init() +local ok, chronos = pcall(require, "chronos") -- hpc + +if not ok then chronos = nil end + +-- This is an integration, we cannot directly access locals that are in the main file. + +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 +} + +-- Restructered these functions since they rely on local variables from the core library + +local mainloop = multi.mainloopRef +local mainloop_p = multi.mainloop_p +local uManagerRef = multi.uManagerRef +local uManagerRefP = multi.uManagerRefP1 + +-- self:setCurrentProcess() a bit slower than using the local var, but there isn't another option + +-- function multi:uManagerRef() +-- if self.Active then +-- self:setCurrentProcess() +-- local Loop=self.Mainloop +-- for _D=#Loop,1,-1 do +-- __CurrentTask = Loop[_D] +-- __CurrentTask:Act() +-- self:setCurrentProcess() +-- end +-- end +-- end + +local function init() + local RR, PB, TB = 0, 1, 2 + + multi.priorityScheme = { + RoundRobin = 0, + PriorityBased = 1, + TimedBased = 2 + } + + function multi:setPriorityScheme(scheme) + if not self.Type == multi.PROCESS or not self.Type == multi.ROOTPROCESS then + multi.warn("You should only invoke setPriorityScheme on a processor object!") + end + if scheme == RR then + multi.mainloop = mainloop + multi.uManager = uManagerRef + elseif scheme == PB then + multi.mainloop = mainloop_p + multi.uManager = uManagerRefP + elseif scheme == TB then + -- + else + multi.error("Invalid priority scheme passed!") + end + end +end + +local function init_chronos() + +end + +if chronos then + init_chronos() +else + multi.print("In order to have time based priority management, you need to install the chronos library!") +end + +init() + +--chronos.nanotime() \ No newline at end of file -- 2.43.0 From 42149ffab206bc0d1e0ff28b79d97ec84acc2b03 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 30 Apr 2023 23:53:54 -0400 Subject: [PATCH 025/117] Working on priority management --- docs/changes.md | 1 + init.lua | 23 ++++++++++++----------- integration/priorityManager/init.lua | 24 ++++++++++++------------ tests/test.lua | 12 ++---------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 49564f3..cde5e6e 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -204,6 +204,7 @@ Added Changed --- +- multi.OnObjectCreated is only called when an object is created in a particular process. Proc.OnObjectCreated is needed to detect when an object is created within a process. - multi.print shows "INFO" before it's message. - Connections internals changed, not too much changed on the surface. - newConnection(protect, func, kill) diff --git a/init.lua b/init.lua index 6870b9c..70c23e6 100644 --- a/init.lua +++ b/init.lua @@ -402,7 +402,7 @@ function multi:newConnection(protect, func, kill) end if not(ignoreconn) then - multi:create(c) + self:create(c) end return c @@ -638,7 +638,7 @@ function multi:newTimer() time=os.clock()-time return self end - multi:create(c) + self:create(c) return c end @@ -662,7 +662,7 @@ function multi:newEvent(task) c.OnEvent = self:newConnection():fastMode() self:setPriority("core") c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -684,7 +684,7 @@ function multi:newUpdater(skip) end c.OnUpdate = self:newConnection():fastMode() c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -721,7 +721,7 @@ function multi:newAlarm(set) return self end c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -744,7 +744,7 @@ function multi:newLoop(func,notime) c.OnLoop(func) end - multi:create(c) + self:create(c) c:setName(c.Type) return c end @@ -804,7 +804,7 @@ function multi:newStep(start,reset,count,skip) return self end c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -840,7 +840,7 @@ function multi:newTLoop(func,set) c.OnLoop(func) end c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -890,7 +890,7 @@ function multi:newTStep(start,reset,count,set) return self end c:setName(c.Type) - multi:create(c) + self:create(c) return c end @@ -979,6 +979,7 @@ function multi:newProcessor(name, nothread) c.threads = {} c.startme = {} c.parent = self + c.OnObjectCreated = multi:newConnection() local handler = c:createHandler(c) @@ -1449,7 +1450,7 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 - multi:create(c) + self:create(c) c.creationTime = os.clock() return c end @@ -2172,7 +2173,7 @@ function multi:IsAnActor() return self.Act~=nil end -function multi:reallocate(o,n) +function multi:reallocate(o, n) n=n or #o.Mainloop+1 local int=self.Parent self:Destroy() diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index ca5db00..9dd8e58 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -27,17 +27,12 @@ local uManagerRefP = multi.uManagerRefP1 -- self:setCurrentProcess() a bit slower than using the local var, but there isn't another option --- function multi:uManagerRef() --- if self.Active then --- self:setCurrentProcess() --- local Loop=self.Mainloop --- for _D=#Loop,1,-1 do --- __CurrentTask = Loop[_D] --- __CurrentTask:Act() --- self:setCurrentProcess() --- end --- end --- end +local priorityManager = multi:newProcessor("Priority Manager", true) + +local function processHook(obj, proc) + obj.__restoreProc = proc + obj:reallocate(priorityManager) +end local function init() local RR, PB, TB = 0, 1, 2 @@ -67,7 +62,12 @@ local function init() end local function init_chronos() - + multi:newThread("Priority Manager", function() + while true do + thread.yield() + priorityManager.run() + end + end) end if chronos then diff --git a/tests/test.lua b/tests/test.lua index abfa52c..d844fee 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,13 +1,5 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,findopt=true} +require("multi.integration.priorityManager") -local conn1 = multi:newConnection() -local conn2 = function(a,b,c) return a*2, b*2, c*2 end % conn1 -conn2(function(a,b,c) - print("Conn2",a,b,c) -end) -conn1(function(a,b,c) - print("Conn1",a,b,c) -end) -conn1:Fire(1,2,3) -conn2:Fire(1,2,3) \ No newline at end of file +multi:mainloop() \ No newline at end of file -- 2.43.0 From 189552ac656fd19cb9cc8bc3576753f29ccf8a20 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 3 May 2023 23:09:37 -0400 Subject: [PATCH 026/117] Working on custom prioritySchemes --- README.md | 22 +- docs/changes.md | 40 +++- init.lua | 299 +++++++++++++-------------- integration/priorityManager/init.lua | 174 ++++++++++++++-- tests/test.lua | 150 +++++++++++++- 5 files changed, 500 insertions(+), 185 deletions(-) diff --git a/README.md b/README.md index a5a5248..9c1cbc1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# Multi Version: 16.0.0 Connecting the dots +# Multi Version: 16.0.0 - Getting the priorities straight **Key Changes** -- Concat connections +- Expanded connection logic +- New integration priorityManager +- Tests for threads +- Consistent behavior between the threading integrations +- Bug fixes Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! @@ -16,9 +20,19 @@ Progress is being made in [v16.0.0](https://github.com/rayaman/multi/tree/v16.0. INSTALLING ---------- Link to optional dependencies: -- [lanes](https://github.com/LuaLanes/lanes) +- [lanes](https://github.com/LuaLanes/lanes) `luarocks install lanes` + +- [chronos](https://github.com/ldrumm/chronos) `luarocks install chronos` - [love2d](https://love2d.org/) + + When using love2d add multi:uManager() or any processor to love.update() + + ```lua + function love.update(dt) + multi:uManager() + end + ``` 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 love2d game engine! @@ -34,7 +48,7 @@ https://discord.gg/U8UspuA Planned features/TODO --------------------- -- [x] Create test suite (In progress, mostly done) +- [x] ~~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) diff --git a/docs/changes.md b/docs/changes.md index cde5e6e..9fabcec 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,7 +1,7 @@ # Changelog Table of contents --- -[Update 16.0.0 - Connecting the dots](#update-1600---connecting-the-dots)
+[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)
[Update 15.3.1 - Bug fix](#update-1531---bug-fix)
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
@@ -58,15 +58,30 @@ Table of contents [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 16.0.0 - Connecting the dots +# Update 16.0.0 - Getting the priorities straight Added --- +### New Integration - priorityManager + +Allows the user to have multi auto set priorities. Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed these features will still work! + +- multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. - multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object -- multi.warn(...) -- Sends a warning. -- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. +- multi.warn(...) -- Sends a warning. Yellow `WARNING` +- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR` + + **Note:** If you want to have multi.print, multi.warn and multi.error to work you need to enable them in settings + ```lua + multi, thread = require("multi"):init { + print=true, + warn=true, + error=true -- Errors will throw regardless. Setting to true will + -- cause the library to force hard crash itself! + } + ``` - THREAD.setENV(table) -- Set a simple table that will be merged into the global namespace - **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only + **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only. ```lua THREAD.setENV({ shared_function = function() @@ -204,8 +219,18 @@ Added Changed --- +- multi errors now internally call `multi.error` instead of `multi.print` +- Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise. +- Connection:Connect(func, name) Now you can supply a name and name the connection. +- Connection:getConnection(name) This will return the connection function which you can do what you will with it. +- Fast connections are the only connections. Legacy connections have been removed completely. Not much should change on the users end. Perhaps some minor changes. +- conn:Lock(conn) When supplied with a connection reference (What is returned by Connect(func)) it will only lock that connection Reference and not the entire connection. Calling without any arguments will lock the entire connection. +- connUnlock(conn) When supplied with a connection reference it restores that reference and it can be fired again. When no arguments are supplied it unlocks the entire connection. + + **Note:** Lock and Unlock when supplied with arguments and not supplied with arguments operate on different objects. If you unlock an entire connection. Individual connection refs will not unlock. The same applies with locking. The entire connection and references are treated differently. + - multi.OnObjectCreated is only called when an object is created in a particular process. Proc.OnObjectCreated is needed to detect when an object is created within a process. -- multi.print shows "INFO" before it's message. +- multi.print shows "INFO" before it's message. Blue `INFO` - Connections internals changed, not too much changed on the surface. - newConnection(protect, func, kill) - `protect` disables fastmode, but protects the connection @@ -221,8 +246,9 @@ Removed Fixed --- +- multi:reallocate(processor, index) has been fixed to work with the current changes of the library. - Issue with lanes not handling errors properly. This is now resolved -- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events +- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now! ```lua func = thread:newFunction(function() for i=1,10 do diff --git a/init.lua b/init.lua index 70c23e6..ea4c5c0 100644 --- a/init.lua +++ b/init.lua @@ -142,25 +142,34 @@ function multi.ForEach(tab,func) for i=1,#tab do func(tab[i]) end end +function multi.randomString(n) + local str = '' + local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} + for i=1,n do + str = str..''..strings[math.random(1,#strings)] + end + return str +end + local optimization_stats = {} local ignoreconn = true -function multi:newConnection(protect, func, kill) +local empty_func = function() end +function multi:newConnection(protect,func,kill) local c={} local call_funcs = {} local lock = false - c.__connectionAdded = function() end - c.rawadd = false - c.Parent = self + local locked = {} + local fast = {} + c.Parent=self - setmetatable(c,{ - __call=function(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, ...)) + if v==self then + local ref = self:Connect(select(2,...)) if ref then - ref.root_link = select(1, ...) + ref.root_link = select(1,...) return ref end return self @@ -171,49 +180,6 @@ function multi:newConnection(protect, func, kill) return self:Connect(...) end end, - __mod = function(obj1, obj2) -- % - local cn = multi:newConnection() - if type(obj1) == "function" and type(obj2) == "table" then - obj2(function(...) - cn:Fire(obj1(...)) - end) - else - multi.error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") - end - return cn - end, - __concat = function(obj1, obj2) -- .. - local cn = multi:newConnection() - local ref - if type(obj1) == "function" and type(obj2) == "table" then - cn(function(...) - if obj1(...) then - obj2:Fire(...) - end - end) - cn.__connectionAdded = function(conn, func) - cn:Unconnect(conn) - obj2:Connect(func) - end - elseif type(obj1) == "table" and type(obj2) == "function" then - ref = cn(function(...) - obj1:Fire(...) - obj2(...) - end) - cn.__connectionAdded = function() - cn.rawadd = true - cn:Unconnect(ref) - ref = cn(function(...) - if obj2(...) then - obj1:Fire(...) - end - end) - end - else - multi.error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") - end - return cn - end, __add = function(c1,c2) -- Or local cn = multi:newConnection() c1(function(...) @@ -270,63 +236,106 @@ function multi:newConnection(protect, func, kill) return #call_funcs~=0 end - function c:getConnection(name,ignore) - if ignore then - return connections[name] or CRef - else - return connections[name] or self + function c:Lock(conn) + if conn and not conn.lock then + conn.lock = function() end + for i = 1, #fast do + if conn.ref == fast[i] then + fast[i] = conn.lock + return self + end + end + return self end - end - - function c:Lock() - lock = self.Fire - self.Fire = function() end + lock = true return self end - function c:Unlock() - self.Fire = lock + function c:Unlock(conn) + if conn and conn.lock then + for i = 1, #fast do + if conn.lock == fast[i] then + fast[i] = conn.ref + return self + end + end + return self + end + lock = false return self end + if protect then + function c:Fire(...) + if lock then return end + local kills = {} + for i=1,#fast do + local suc, err = pcall(fast[i], ...) + if not suc then + print(err) + end + if kill then + table.remove(kills,i) + multi:newTask(function() + for _,k in pairs(kills) do + table.remove(fast, k) + end + end) + end + end + end + end + function c:getConnections() return call_funcs end + function c:getConnection(name, ignore) + return fast[name] or function() multi:warning("") end + end + function c:Unconnect(conn) - if conn.fast then - for i = 1, #call_funcs do - if conn.ref == call_funcs[i] then - table.remove(call_funcs, i) + for i = 1, #fast do + if conn.ref == fast[i] then + return table.remove(fast, i), i + end + end + end + + function c:fastMode() return self end + if kill then + local kills = {} + function c:Fire(...) + if lock then return end + for i=1,#fast do + fast[i](...) + if kill then + table.insert(kills,i) + multi:newTask(function() + for k = #kills, 1, -1 do + table.remove(fast, k) + table.remove(kills,i) + end + end) end end - elseif conn.Destroy then - conn:Destroy() end - end - - function c:Fire(...) - for i=1, #call_funcs do - call_funcs[i](...) - end - end - - -- Not needed anymore, since it's so light, I'll leave it in forever - function c:fastMode() return self end - - function c:Connect(func) - local th - if thread.getRunningThread then - th = thread.getRunningThread() - end - if th then - local fref = func - func = function(...) - __CurrentConnectionThread = th - fref(...) + else + function c:Fire(...) + if lock then return end + for i=1,#fast do + fast[i](...) end end - table.insert(call_funcs, func) + end + + function c:Connect(func, name) + table.insert(fast, func) + if name then + fast[name] = func + else + fast["Conn_"..multi.randomString(12)] = func + end local temp = {fast = true} setmetatable(temp,{ __call=function(s,...) @@ -346,11 +355,7 @@ function multi:newConnection(protect, func, kill) end, }) temp.ref = func - if self.rawadd then - self.rawadd = false - else - self.__connectionAdded(temp, func) - end + temp.name = name return temp end @@ -366,41 +371,11 @@ function multi:newConnection(protect, func, kill) return temp end - if find_optimization then - -- - end - c.connect=c.Connect c.GetConnection=c.getConnection c.HasConnections = c.hasConnections c.GetConnection = c.getConnection - if protect then -- Do some tests and override the fastmode if you want to do something differently - function c:Fire(...) - for i=#call_funcs,1,-1 do - if not call_funcs[i] then return end - local suc, err = pcall(call_funcs[i],...) - if not suc then - multi.print(err) - end - if kill then - table.remove(call_funcs,i) - end - end - end - elseif kill then - function c:Fire(...) - for i=#call_funcs,1,-1 do - call_funcs[i](...) - table.remove(call_funcs,i) - end - end - end - - if func then - c = c .. func - end - if not(ignoreconn) then self:create(c) end @@ -474,6 +449,7 @@ function multi:SetTime(n) self.link:Pause() self.OnTimedOut:Fire(self.link) self:Destroy() + return true end end return self @@ -564,7 +540,7 @@ function multi:isDone() end function multi:create(ref) - self.OnObjectCreated:Fire(ref,self) + self.OnObjectCreated:Fire(ref, self) return self end @@ -653,6 +629,7 @@ function multi:newEvent(task) self:Pause() self.returns = t c.OnEvent:Fire(self) + return true end end function c:SetTask(func) @@ -675,6 +652,7 @@ function multi:newUpdater(skip) if pos >= skip then pos = 0 self.OnUpdate:Fire(self) + return true end pos = pos+1 end @@ -701,6 +679,7 @@ function multi:newAlarm(set) self.Active=false self.OnRing:Fire(self) t = clock() + return true end end function c:Resume() @@ -732,10 +711,12 @@ function multi:newLoop(func,notime) if notime then function c:Act() self.OnLoop:Fire(self) + return true end else function c:Act() self.OnLoop:Fire(self,clock()-start) + return true end end c.OnLoop = self:newConnection():fastMode() @@ -783,6 +764,7 @@ function multi:newStep(start,reset,count,skip) if self.spos>=self.skip then self.spos=0 end + return true end c.Reset=c.Resume c.OnStart = self:newConnection():fastMode() @@ -820,6 +802,7 @@ function multi:newTLoop(func,set) self.life=self.life+1 self.timer:Reset() self.OnLoop:Fire(self,self.life) + return true end end function c:Set(set) @@ -878,6 +861,7 @@ function multi:newTStep(start,reset,count,set) self.OnEnd:Fire(self) self.pos=self.start end + return true end end function c:Set(set) @@ -957,6 +941,14 @@ function multi.getCurrentTask() return __CurrentTask end +function multi:setCurrentProcess() + __CurrentProcess = self +end + +function multi:setCurrentTask() + __CurrentTask = self +end + function multi:getName() return self.Name end @@ -998,7 +990,7 @@ function multi:newProcessor(name, nothread) c.OnError = multi:newConnection() end - c.OnError(multi.print) + c.OnError(multi.error) function c:getThreads() return c.threads @@ -1379,7 +1371,7 @@ function thread:newThread(name, func, ...) c.isError = false c.OnError = multi:newConnection(true,nil,true) c.OnDeath = multi:newConnection(true,nil,true) - c.OnError(multi.print) + c.OnError(multi.error) function c:getName() return c.Name @@ -1450,7 +1442,7 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 - self:create(c) + multi:getCurrentProcess():create(c) c.creationTime = os.clock() return c end @@ -1865,10 +1857,6 @@ local function doOpt() end end -function multi:setCurrentProcess() - __CurrentProcess = self -end - local init = false function multi.init(settings, realsettings) if settings == multi then settings = realsettings end @@ -2076,15 +2064,6 @@ else end end -function multi.randomString(n) - local str = '' - local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} - for i=1,n do - str = str..''..strings[math.random(1,#strings)] - end - return str -end - function multi:getChildren() return self.Mainloop end @@ -2173,12 +2152,16 @@ function multi:IsAnActor() return self.Act~=nil end -function multi:reallocate(o, n) - n=n or #o.Mainloop+1 +function multi:reallocate(processor, index) + index=index or #processor.Mainloop+1 local int=self.Parent - self:Destroy() - self.Parent=o - table.insert(o.Mainloop,n,self) + self.Parent=processor + print("Moving task to new processor!") + if index then + table.insert(processor.Mainloop, index, self) + else + table.insert(processor.Mainloop, self) + end self.Active=true return self end @@ -2200,18 +2183,28 @@ end function multi.print(...) if multi.defaultSettings.print then - print("INFO:", table.concat({...}, " ")) + local t = {} + for i,v in pairs({...}) do t[#t+1] = tostring(v) end + io.write("\x1b[94mINFO:\x1b[0m " .. table.concat(t," ") .. "\n") end end function multi.warn(...) if multi.defaultSettings.warn then - print("WARNING:", table.concat({...}, " ")) + local t = {} + for i,v in pairs({...}) do t[#t+1] = tostring(v) end + io.write("\x1b[93mWARNING:\x1b[0m " .. table.concat(t," ") .. "\n") end end -function multi.error(err) - error("ERROR: " .. err) +function multi.error(self, err) + if type(err) == "bool" then crash = err end + if type(self) == "string" then err = self end + io.write("\x1b[91mERROR:\x1b[0m " .. err .. "\n") + error("^^^") + if multi.defaultSettings.error then + os.exit(1) + end end multi.GetType = multi.getType diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index 9dd8e58..cf8cd99 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -25,57 +25,193 @@ local mainloop_p = multi.mainloop_p local uManagerRef = multi.uManagerRef local uManagerRefP = multi.uManagerRefP1 +local PROFILE_COUNT = 5 + -- self:setCurrentProcess() a bit slower than using the local var, but there isn't another option local priorityManager = multi:newProcessor("Priority Manager", true) +priorityManager.newThread = function() multi.warn("You cannot spawn threads on the priority manager!") end + +priorityManager.setPriorityScheme = function() multi.warn("You cannot set priority on the priorityManager!") end + +local function average(t) + local sum = 0 + for _,v in pairs(t) do + sum = sum + v + end + return sum / #t +end + +local function getPriority(obj) + local avg = average(obj.__profiling) + if avg < 0.0002 then + multi.print("Setting priority to: core") + return PList[1] + elseif avg < 0.0004 then + multi.print("Setting priority to: very high") + return PList[2] + elseif avg < 0.0008 then + multi.print("Setting priority to: high") + return PList[3] + elseif avg < 0.001 then + multi.print("Setting priority to: above normal") + return PList[4] + elseif avg < 0.0025 then + multi.print("Setting priority to: normal") + return PList[5] + elseif avg < 0.005 then + multi.print("Setting priority to: below normal") + return PList[6] + elseif avg < 0.008 then + multi.print("Setting priority to: low") + return PList[7] + elseif avg < 0.01 then + multi.print("Setting priority to: very low") + return PList[8] + else + multi.print("Setting priority to: idle") + return PList[9] + end +end + +local start, stop + +priorityManager.uManager = function(self) + -- proc.run already checks if the processor is active + self:setCurrentProcess() + local Loop=self.Mainloop + local ctask + for _D=#Loop,1,-1 do + ctask = Loop[_D] + ctask:setCurrentTask() + start = chronos.nanotime() + if ctask:Act() then + stop = chronos.nanotime() + if ctask.__profiling then + table.insert(ctask.__profiling, stop - start) + end + if ctask.__profiling and #ctask.__profiling == PROFILE_COUNT then + ctask:setPriority(getPriority(ctask)) + ctask:reallocate(ctask.__restoreProc) + ctask.__restoreProc = nil + ctask.__profiling = nil + end + end + self:setCurrentProcess() + end +end local function processHook(obj, proc) + if obj.Type == multi.PROCESS or not(obj.IsAnActor) then return end obj.__restoreProc = proc + obj.__profiling = {} obj:reallocate(priorityManager) end local function init() - local RR, PB, TB = 0, 1, 2 + local registry = {} multi.priorityScheme = { - RoundRobin = 0, - PriorityBased = 1, - TimedBased = 2 + RoundRobin = "RoundRobin", + PriorityBased = "PriorityBased", + TimeBased = "TimeBased" } + function multi:setProfilerCount(count) + PROFILE_COUNT = count + end + + function multi:recalibrate() + if self.__processConn then + local items = self.Mainloop + for i,v in pairs(items) do + processHook(v, self) + end + else + multi.error("Cannot recalibrate the priority if not using Time based mangement!") + end + end + + function multi:isRegistredScheme(scheme) + -- + end + + function multi:getRegisteredScheme(scheme) + -- + end + + local empty_func = function() return true end + function multi:registerScheme(name,options) + local mainloop = options.mainloop or multi.error("You must provide a mainloop option when registring a scheme!") + local umanager = options.umanager or multi.error("You must provide a umanager option when registring a scheme!") + + if not options.condition then + multi.warn("You might want to use condition when registring a scheme! A function that returns true has been auto generated for you!") + end + + local condition = options.condition or empty_func + + if registry[name] and not registry[name].static then + multi.warn("A scheme named: \"" .. name .. "\" has already been registred, overriting!") + else + multi.error("A scheme named: \"" .. name .. "\" has already been registred!") + end + + registry[name] = { + mainloop = mainloop, + umanager = umanger, + condition = condition, + static = options.static or false + } + return true + end + function multi:setPriorityScheme(scheme) + if not self.Type == multi.PROCESS or not self.Type == multi.ROOTPROCESS then multi.warn("You should only invoke setPriorityScheme on a processor object!") end - if scheme == RR then - multi.mainloop = mainloop - multi.uManager = uManagerRef - elseif scheme == PB then - multi.mainloop = mainloop_p - multi.uManager = uManagerRefP - elseif scheme == TB then - -- + + if scheme == multi.priorityScheme.RoundRobin then + if self.__processConn then self.OnObjectCreated:Unconnect(self.__processConn) self.__processConn = nil end + self.mainloop = mainloop + self.uManager = uManagerRef + elseif scheme == multi.priorityScheme.PriorityBased then + if self.__processConn then self.OnObjectCreated:Unconnect(self.__processConn) self.__processConn = nil end + self.mainloop = mainloop_p + self.uManager = uManagerRefP + elseif scheme == multi.priorityScheme.TimeBased then + if not chronos then return multi.warn("Unable to use TimeBased Priority without the chronos library!") end + if self.__processConn then multi.warn("Already enabled TimeBased Priority!") end + self.__processConn = self.OnObjectCreated(processHook) + self.mainloop = mainloop_p + self.uManager = uManagerRefP + elseif self:isRegistredScheme(scheme) then + local mainloop, umanager, condition = self:getRegisteredScheme(scheme) + if condition() then + self.mainloop = mainloop + self.uManager = umanager + end else - multi.error("Invalid priority scheme passed!") + self.error("Invalid priority scheme selected!") end + end end local function init_chronos() - multi:newThread("Priority Manager", function() + thread:newThread("System Priority Manager", function() while true do thread.yield() priorityManager.run() end - end) + end).OnError(multi.error) end if chronos then init_chronos() else - multi.print("In order to have time based priority management, you need to install the chronos library!") + multi.warn("In order to have time based priority management, you need to install the chronos library!") end -init() - ---chronos.nanotime() \ No newline at end of file +init() \ No newline at end of file diff --git a/tests/test.lua b/tests/test.lua index d844fee..299d78f 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,5 +1,151 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{print=true,findopt=true} +multi, thread = require("multi"):init{print=true,warn=true,error=true} require("multi.integration.priorityManager") -multi:mainloop() \ No newline at end of file +test = multi:newProcessor("Test") +test:setPriorityScheme(multi.priorityScheme.TimeBased) +multi.OnObjectCreated(function(proc, obj) + print("MULTI",proc.Type,obj.Type) +end) +local a = 0 +test:newUpdater(100000):OnUpdate(function() + print("Print is slowish") +end) + +print("Running...") + +multi:mainloop() + + +-- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() + +-- local link = conn1(function() +-- print("Conn1, first") +-- end) + +-- local link2 = conn1(function() +-- print("Conn1, second") +-- end) + +-- local link3 = conn1(function() +-- print("Conn1, third") +-- end) + +-- local link4 = conn2(function() +-- print("Conn2, first") +-- end) + +-- local link5 = conn2(function() +-- print("Conn2, second") +-- end) + +-- local link6 = conn2(function() +-- print("Conn2, third") +-- end) + +-- print("Links 1-6",link,link2,link3,link4,link5,link6) +-- conn1:Lock(link) +-- print("All conns\n-------------") +-- conn1:Fire() +-- conn2:Fire() + +-- conn1:Unlock(link) + +-- conn1:Unconnect(link3) +-- conn2:Unconnect(link6) +-- print("All conns Edit\n---------------------") +-- conn1:Fire() +-- conn2:Fire() + +-- thread:newThread(function() +-- print("Awaiting status") +-- thread.hold(conn1 + (conn2 * conn3)) +-- print("Conn or Conn2 and Conn3") +-- end) + +-- multi:newAlarm(1):OnRing(function() +-- print("Conn") +-- conn1:Fire() +-- end) +-- multi:newAlarm(2):OnRing(function() +-- print("Conn2") +-- conn2:Fire() +-- end) +-- multi:newAlarm(3):OnRing(function() +-- print("Conn3") +-- conn3:Fire() +-- os.exit() +-- end) + + +-- local conn = multi:newSystemThreadedConnection("conn"):init() + +-- multi:newSystemThread("Thread_Test_1", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:mainloop() +-- end) + +-- multi:newSystemThread("Thread_Test_2", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(2):OnRing(function() +-- console.print("Fire 2!!!") +-- conn:Fire(4,5,6) +-- THREAD.kill() +-- end) + +-- multi:mainloop() +-- end) +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print("Mainloop conn got triggered!",a,b,c) +-- end) + +-- alarm = multi:newAlarm(1) +-- alarm:OnRing(function() +-- console.print("Fire 1!!!") +-- conn:Fire(1,2,3) +-- end) + +-- alarm = multi:newAlarm(3):OnRing(function() +-- multi:newSystemThread("Thread_Test_3",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(4):OnRing(function() +-- console.print("Fire 3!!!") +-- conn:Fire(7,8,9) +-- end) +-- multi:mainloop() +-- end) +-- end) + +-- multi:newSystemThread("Thread_Test_4",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local conn2 = multi:newConnection() +-- local console = THREAD.getConsole() +-- multi:newAlarm(2):OnRing(function() +-- conn2:Fire() +-- end) +-- multi:newThread(function() +-- console.print("Conn Test!") +-- thread.hold(conn + conn2) +-- console.print("It held!") +-- end) +-- multi:mainloop() +-- end) + +-- multi:mainloop() \ No newline at end of file -- 2.43.0 From 33202260e361cf489a6a37a393d9894049d7c239 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 5 May 2023 16:33:31 -0400 Subject: [PATCH 027/117] Fixed issues with missing code --- docs/changes.md | 16 ++-- init.lua | 107 +++++++++++++++++++---- integration/priorityManager/init.lua | 9 +- integration/pseudoManager/extensions.lua | 2 +- tests/runtests.lua | 60 ++++++------- tests/threadtests.lua | 47 +++++----- 6 files changed, 166 insertions(+), 75 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 9fabcec..b78ff3f 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -59,14 +59,19 @@ Table of contents [Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) # Update 16.0.0 - Getting the priorities straight -Added ---- -### New Integration - priorityManager + +## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities. Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed these features will still work! +- Allows the creation of custom priorityManagers for example: + +Added +--- +- multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. - multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. - multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object +- multi.success(...) -- Sends a success. Green `SUCCESS` mainly used for tests - multi.warn(...) -- Sends a warning. Yellow `WARNING` - multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR` @@ -145,7 +150,7 @@ Allows the user to have multi auto set priorities. Also adds the functionality t end) function test(a,b,c) - print("I run before all and control if things go!") + print("I run before all and control if execution should continue!") return a>b end @@ -246,6 +251,7 @@ Removed Fixed --- +- connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! - multi:reallocate(processor, index) has been fixed to work with the current changes of the library. - Issue with lanes not handling errors properly. This is now resolved - Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now! @@ -292,7 +298,7 @@ conn2:Fire() -- Looks like this is triggering a response. It shouldn't. We need to account for this conn1:Fire() conn1:Fire() --- Triggering conn1 twice counted as a valid way to trigger the phantom connection (conn1 * conn2) +-- Triggering conn1 twice counted as a valid way to trigger the virtual connection (conn1 * conn2) -- Now in 15.3.1, this works properly and the above doesn't do anything. Internally connections are locked until the conditions are met. conn2:Fire() diff --git a/init.lua b/init.lua index ea4c5c0..b19e453 100644 --- a/init.lua +++ b/init.lua @@ -138,6 +138,10 @@ end --Helpers +function multi.setClock(c) + clock = c +end + function multi.ForEach(tab,func) for i=1,#tab do func(tab[i]) end end @@ -156,11 +160,11 @@ local ignoreconn = true local empty_func = function() end function multi:newConnection(protect,func,kill) local c={} - local call_funcs = {} local lock = false - local locked = {} local fast = {} - c.Parent=self + c.__connectionAdded = function() end + c.rawadd = false + c.Parent = self setmetatable(c,{__call=function(self,...) local t = ... @@ -180,6 +184,49 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, + __mod = function(obj1, obj2) -- % + local cn = multi:newConnection() + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + else + error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end, + __concat = function(obj1, obj2) -- .. + local cn = multi:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) then + obj2:Fire(...) + end + end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + obj1:Fire(...) + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + else + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + end + return cn + end, __add = function(c1,c2) -- Or local cn = multi:newConnection() c1(function(...) @@ -192,6 +239,7 @@ function multi:newConnection(protect,func,kill) end, __mul = function(c1,c2) -- And local cn = multi:newConnection() + local ref1, ref2 if c1.__hasInstances == nil then cn.__hasInstances = {2} cn.__count = {0} @@ -201,25 +249,25 @@ function multi:newConnection(protect,func,kill) cn.__count = c1.__count end - c1(function(...) + ref1 = c1(function(...) cn.__count[1] = cn.__count[1] + 1 - c1:Lock() + c1:Lock(ref1) if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 - c1:Unlock() - c2:Unlock() + c1:Unlock(ref1) + c2:Unlock(ref2) end end) - c2(function(...) + ref2 = c2(function(...) cn.__count[1] = cn.__count[1] + 1 - c2:Lock() + c2:Lock(ref2) if cn.__count[1] == cn.__hasInstances[1] then cn:Fire(...) cn.__count[1] = 0 - c1:Unlock() - c2:Unlock() + c1:Unlock(ref1) + c2:Unlock(ref2) end end) return cn @@ -330,6 +378,17 @@ function multi:newConnection(protect,func,kill) end function c:Connect(func, name) + local th + if thread.getRunningThread then + th = thread.getRunningThread() + end + if th then + local fref = func + func = function(...) + __CurrentConnectionThread = th + fref(...) + end + end table.insert(fast, func) if name then fast[name] = func @@ -356,6 +415,11 @@ function multi:newConnection(protect,func,kill) }) temp.ref = func temp.name = name + if self.rawadd then + self.rawadd = false + else + self.__connectionAdded(temp, func) + end return temp end @@ -376,7 +440,12 @@ function multi:newConnection(protect,func,kill) c.HasConnections = c.hasConnections c.GetConnection = c.getConnection + if func then + c = c .. func + end + if not(ignoreconn) then + if not self then return c end self:create(c) end @@ -567,7 +636,7 @@ function multi:newBase(ins) c.TID = _tid c.Act=function() end c.Parent=self - c.creationTime = os.clock() + c.creationTime = clock() if ins then table.insert(self.Mainloop,ins,c) else @@ -593,7 +662,7 @@ function multi:newTimer() local count=0 local paused=false function c:Start() - time=os.clock() + time=clock() return self end function c:Get() @@ -611,7 +680,7 @@ function multi:newTimer() end function c:Resume() paused=false - time=os.clock()-time + time=clock()-time return self end self:create(c) @@ -1443,7 +1512,7 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 multi:getCurrentProcess():create(c) - c.creationTime = os.clock() + c.creationTime = clock() return c end @@ -1970,7 +2039,7 @@ function multi:enableLoadDetection() if multi.maxSpd then return end -- here we are going to run a quick benchMark solo local temp = self:newProcessor() - local t = os.clock() + local t = clock() local stop = false temp:benchMark(.01):OnBench(function(time,steps) stop = steps @@ -2207,6 +2276,12 @@ function multi.error(self, err) end end +function multi.success(...) + local t = {} + for i,v in pairs({...}) do t[#t+1] = tostring(v) end + io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") +end + multi.GetType = multi.getType multi.IsPaused = multi.isPaused multi.IsActive = multi.isActive diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index cf8cd99..95ed193 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -133,11 +133,11 @@ local function init() end function multi:isRegistredScheme(scheme) - -- + return registry[name] ~= nil end function multi:getRegisteredScheme(scheme) - -- + return registry[name].mainloop, registry[name].umanager, registry[name].condition end local empty_func = function() return true end @@ -163,6 +163,9 @@ local function init() condition = condition, static = options.static or false } + + multi.priorityScheme[name] = name + return true end @@ -200,6 +203,8 @@ local function init() end local function init_chronos() + -- Let's implement a higher precision clock + multi.setClock(chronos.nanotime) -- This is also in .000 format. So a plug and play works. thread:newThread("System Priority Manager", function() while true do thread.yield() diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 4ae0d72..bd59f10 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -139,7 +139,7 @@ function multi:newSystemThreadedJobQueue(n) end function multi:newSystemThreadedConnection(name) - local conn = multi.newConnection() + local conn = multi:newConnection() conn.init = function(self) return self end GLOBAL[name or "_"] = conn return conn diff --git a/tests/runtests.lua b/tests/runtests.lua index ff59c52..049c26b 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -20,7 +20,7 @@ end The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init{print=true}--{priority=true} +local multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} local good = false local proc = multi:newProcessor("Test") @@ -32,7 +32,7 @@ end) runTest = thread:newFunction(function() local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false - print("Testing Basic Features. If this fails most other features will probably not work!") + multi.print("Testing Basic Features. If this fails most other features will probably not work!") proc:newAlarm(2):OnRing(function(a) alarms = true a:Destroy() @@ -62,18 +62,18 @@ runTest = thread:newFunction(function() event.OnEvent(function(evnt) evnt:Destroy() events = true - print("Alarms: Ok") - print("Events: Ok") - if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end - if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end - if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end - if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end - if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end + multi.success("Alarms: Ok") + multi.success("Events: Ok") + if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end + if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end + if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end + if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end + if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end end) thread.hold(event.OnEvent) - print("Starting Connection and Thread tests!") + multi.print("Starting Connection and Thread tests!") func = thread:newFunction(function(count) - print("Starting Status test: ",count) + multi.print("Starting Status test: ",count) local a = 0 while true do a = a + 1 @@ -88,13 +88,13 @@ runTest = thread:newFunction(function() local ret3 = func(20) local s1,s2,s3 = 0,0,0 ret.OnError(function(...) - print("Func 1:",...) + multi.error("Func 1:",...) end) ret2.OnError(function(...) - print("Func 2:",...) + multi.error("Func 2:",...) end) ret3.OnError(function(...) - print("Func 3:",...) + multi.error("Func 3:",...) end) ret.OnStatus(function(part,whole) s1 = math.ceil((part/whole)*1000)/10 @@ -107,31 +107,31 @@ runTest = thread:newFunction(function() end) ret.OnReturn(function(...) - print("Done 1",...) + multi.success("Done 1",...) end) ret2.OnReturn(function(...) - print("Done 2",...) + multi.success("Done 2",...) end) ret3.OnReturn(function(...) - print("Done 3",...) + multi.success("Done 3",...) end) local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) if s1 == 100 and s2 == 100 and s3 == 100 then - print("Threads: All tests Ok") + multi.success("Threads: All tests Ok") else if s1>0 and s2>0 and s3 > 0 then - print("Thread OnStatus: Ok") + multi.success("Thread OnStatus: Ok") else - print("Threads OnStatus or thread.hold(conn) Error!") + multi.error("Threads OnStatus or thread.hold(conn) Error!") end if timeout then - print("Connection Error!") + multi.error("Connection Error!") else - print("Connection Test 1: Ok") + multi.success("Connection Test 1: Ok") end - print("Connection holding Error!") + multi.error("Connection holding Error!") end conn1 = proc:newConnection() @@ -160,30 +160,30 @@ runTest = thread:newFunction(function() conn3:Fire() if c1 and c2 and c3 and c4 then - print("Connection Test 2: Ok") + multi.success("Connection Test 2: Ok") else - print("Connection Test 2: Error") + multi.error("Connection Test 2: Error") end c3 = false c4 = false conn3:Unconnect(d) conn3:Fire() if c3 and not(c4) then - print("Connection Test 3: Ok") + multi.success("Connection Test 3: Ok") else - print("Connection Test 3: Error removing connection") + multi.error("Connection Test 3: Error removing connection") end if not love then - print("Testing pseudo threading") + multi.print("Testing pseudo threading") os.execute("lua tests/threadtests.lua p") - print("Testing lanes threading") + multi.print("Testing lanes threading") os.execute("lua tests/threadtests.lua l") os.exit() end end) runTest().OnError(function(...) - print("Error: Something went wrong with the test!") + multi.error("Something went wrong with the test!") print(...) end) diff --git a/tests/threadtests.lua b/tests/threadtests.lua index fd2e735..4d382d9 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,8 +1,8 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{}--{priority=true} +multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 -local env +local env, we_good if love then GLOBAL, THREAD = require("multi.integration.loveManager"):init() @@ -27,11 +27,11 @@ else end end -print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") +multi.print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") THREAD.setENV({ multi_assert = function(expected, actual, s) if expected ~= actual then - error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") + multi.error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") end end }) @@ -47,7 +47,7 @@ multi:newThread("Scheduler Thread",function() multi_assert("table", type(f), "Argument f is not a table!") queue:push("done") end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) - print("Error:", err) + multi.error(err) os.exit() end) @@ -57,7 +57,7 @@ multi:newThread("Scheduler Thread",function() thread.kill() end - print("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") + multi.success("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") func = THREAD:newFunction(function(a,b,c) assert(a == 3, "First argument expected '3' got '".. a .."'!") @@ -73,7 +73,7 @@ multi:newThread("Scheduler Thread",function() assert(c == 3, "Third return was not '3'!") assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") - print("Threaded Functions, arg passing, return passing, holding: Ok") + multi.success("Threaded Functions, arg passing, return passing, holding: Ok") test=multi:newSystemThreadedTable("YO"):init() test["test1"]="tabletest" @@ -84,7 +84,7 @@ multi:newThread("Scheduler Thread",function() THREAD.hold(function() return tab["test1"] end) THREAD.sleep(.1) tab["test2"] = "Whats so funny?" - end).OnError(print) + end).OnError(multi.error) multi:newThread("test2",function() thread.hold(function() return test["test2"] end) @@ -96,11 +96,11 @@ multi:newThread("Scheduler Thread",function() end,{sleep=1}) if val == multi.TIMEOUT then - print("SystemThreadedTables: Failed") + multi.error("SystemThreadedTables: Failed") os.exit() end - print("SystemThreadedTables: Ok") + multi.success("SystemThreadedTables: Ok") local ready = false @@ -123,11 +123,11 @@ multi:newThread("Scheduler Thread",function() end,{sleep=2}) if val == multi.TIMEOUT then - print("SystemThreadedJobQueues: Failed") + multi.error("SystemThreadedJobQueues: Failed") os.exit() end - print("SystemThreadedJobQueues: Ok") + multi.success("SystemThreadedJobQueues: Ok") queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() multi:newSystemThread("Test_Thread_2",function() @@ -137,7 +137,7 @@ multi:newThread("Scheduler Thread",function() queue2:push("Test_Thread_2") end) multi:mainloop() - end).OnError(print) + end).OnError(multi.error) multi:newSystemThread("Test_Thread_3",function() queue2 = THREAD.waitFor("Test_Queue2"):init() @@ -146,7 +146,7 @@ multi:newThread("Scheduler Thread",function() queue2:push("Test_Thread_3") end) multi:mainloop() - end).OnError(print) + end).OnError(multi.error) connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() a=0 connOut(function(arg) @@ -164,21 +164,26 @@ multi:newThread("Scheduler Thread",function() count = count + 1 end end - end).OnError(print) + end).OnError(multi.error) _, err = thread.hold(function() return count == 9 end,{sleep=.3}) if err == multi.TIMEOUT then - print("SystemThreadedConnections: Failed") - os.exit() + multi.error("SystemThreadedConnections: Failed") end - print("SystemThreadedConnections: Ok") + multi.success("SystemThreadedConnections: Ok") - print("Tests complete!") + we_good = true os.exit() -end).OnError(print) - +end).OnError(multi.error) +multi.OnExit(function(err) + if not we_good then + multi.error("There was an error running some tests!") + else + multi.success("Tests complete!") + end +end) multi:mainloop() \ No newline at end of file -- 2.43.0 From 5cc18b04ae542d0a34d6934061f68b87fa037e83 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 6 May 2023 03:18:00 -0400 Subject: [PATCH 028/117] Threaded processors --- docs/changes.md | 8 +++++-- init.lua | 39 +++++++++++++++++++++++++++++++- tests/test.lua | 59 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index b78ff3f..0667130 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -62,12 +62,13 @@ Table of contents ## Added New Integration: **priorityManager** -Allows the user to have multi auto set priorities. Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed these features will still work! +Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! - Allows the creation of custom priorityManagers for example: - Added --- +- multi.Processors:getHandler() -- returns the thread handler for a process +- multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! - multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. - multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. - multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object @@ -160,6 +161,9 @@ Added conn4:Fire(3,2,3) + -- This second one won't trigger the Hi's + conn4:Fire(1,2,3) + conn5(function() print("Test 1") end) diff --git a/init.lua b/init.lua index b19e453..8057a15 100644 --- a/init.lua +++ b/init.lua @@ -633,6 +633,7 @@ function multi:newBase(ins) c.funcTM={} c.funcTMR={} c.OnBreak = multi:newConnection() + c.OnPriorityChanged = multi:newConnection() c.TID = _tid c.Act=function() end c.Parent=self @@ -1061,6 +1062,10 @@ function multi:newProcessor(name, nothread) c.OnError(multi.error) + function c:getHandler() + return handler + end + function c:getThreads() return c.threads end @@ -1412,6 +1417,38 @@ function thread:newFunction(func, holdme) end, holdme)() end +function thread:newProcessor(name) + -- Inactive proxy proc + local proc = multi:getCurrentProcess():newProcessor(name, true) + local thread_proc = multi:getCurrentProcess():newProcessor(name, true) + + local handler = thread_proc:getHandler() + + function proc:getThreads() + return thread_proc.threads + end + + function proc:newThread(name, func,...) + return thread.newThread(thread_proc, name, func, ...) + end + + function c:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return thread_proc:newThread("Threaded Function Handler", func, ...) + end, holdme)() + end + + proc.OnObjectCreated(function(obj) + thread_proc:newThread(function() + while true do + thread.yield() + -- + end + end) + end) + return proc +end + -- A cross version way to set enviroments, not the same as fenv though function multi.setEnv(func,env) local f = string.dump(func) @@ -2102,6 +2139,7 @@ function multi:setPriority(s) elseif s:lower()=='idle' or s:lower()=='i' then self.Priority=self.Priority_Idle end + self.OnPriorityChanged:Fire(self, self.Priority) end if not self.PrioritySet then self.defPriority = self.Priority @@ -2225,7 +2263,6 @@ function multi:reallocate(processor, index) index=index or #processor.Mainloop+1 local int=self.Parent self.Parent=processor - print("Moving task to new processor!") if index then table.insert(processor.Mainloop, index, self) else diff --git a/tests/test.lua b/tests/test.lua index 299d78f..2b0e4e1 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -2,19 +2,58 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,error=true} require("multi.integration.priorityManager") -test = multi:newProcessor("Test") -test:setPriorityScheme(multi.priorityScheme.TimeBased) -multi.OnObjectCreated(function(proc, obj) - print("MULTI",proc.Type,obj.Type) -end) -local a = 0 -test:newUpdater(100000):OnUpdate(function() - print("Print is slowish") +-- test = multi:newProcessor("Test") +-- test:setPriorityScheme(multi.priorityScheme.TimeBased) + +-- test:newUpdater(10000000):OnUpdate(function() +-- print("Print is slowish") +-- end) + +-- print("Running...") + +local conn1, conn2 = multi:newConnection(), multi:newConnection() +conn3 = conn1 + conn2 + +conn1(function() + print("Hi 1") end) -print("Running...") +conn2(function() + print("Hi 2") +end) -multi:mainloop() +conn3(function() + print("Hi 3") +end) + +function test(a,b,c) + print("I run before all and control if execution should continue!") + return a>b +end + +conn4 = test .. conn1 + +conn5 = conn2 .. function() print("I run after it all!") end + +conn4:Fire(3,2,3) +-- This second one won't trigger the Hi's +conn4:Fire(1,2,3) + +conn5(function() + print("Test 1") +end) + +conn5(function() + print("Test 2") +end) + +conn5(function() + print("Test 3") +end) + +conn5:Fire() + +--multi:mainloop() -- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() -- 2.43.0 From 4fe428e5728aaf14eaf5d87bf7ed47feededa3cc Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 6 May 2023 16:46:15 -0400 Subject: [PATCH 029/117] THREAD.exposeENV(), thread:newProcessor() --- docs/changes.md | 7 ++- init.lua | 48 +++++++++++--- integration/lanesManager/threads.lua | 18 ++++-- integration/loveManager/threads.lua | 19 ++++-- integration/pseudoManager/init.lua | 3 +- integration/pseudoManager/threads.lua | 19 ++++-- tests/test.lua | 91 ++++++++++++++++----------- 7 files changed, 144 insertions(+), 61 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 0667130..ec4996c 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -67,6 +67,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. - multi.Processors:getHandler() -- returns the thread handler for a process - multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! - multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. @@ -85,7 +86,8 @@ Added -- cause the library to force hard crash itself! } ``` -- THREAD.setENV(table) -- Set a simple table that will be merged into the global namespace +- THREAD.exposeEnv(name) -- Merges set env into the global namespace of the system thread it was called in. +- THREAD.setENV(table [, name]) -- Set a simple table that will be merged into the global namespace. If a name is supplied the global namespace will not be merged. Call THREAD.exposeEnv(name) to expose that namespace within a thread. **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only. ```lua @@ -284,6 +286,7 @@ Fixed ToDo --- +- N/A # Update 15.3.1 - Bug fix Fixed @@ -1984,7 +1987,7 @@ L: 2120906 I: 2120506 ``` -Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. +Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. This is nolonger the case in version 16.0.0 multi has evolved ;) **Improved:** - Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. diff --git a/init.lua b/init.lua index 8057a15..53cf5e8 100644 --- a/init.lua +++ b/init.lua @@ -323,9 +323,10 @@ function multi:newConnection(protect,func,kill) print(err) end if kill then - table.remove(kills,i) + table.insert(kills,i) multi:newTask(function() - for _,k in pairs(kills) do + for _, k in pairs(kills) do + table.remove(kills, _) table.remove(fast, k) end end) @@ -360,9 +361,9 @@ function multi:newConnection(protect,func,kill) if kill then table.insert(kills,i) multi:newTask(function() - for k = #kills, 1, -1 do + for _, k in pairs(kills) do + table.remove(kills, _) table.remove(fast, k) - table.remove(kills,i) end end) end @@ -1420,7 +1421,8 @@ end function thread:newProcessor(name) -- Inactive proxy proc local proc = multi:getCurrentProcess():newProcessor(name, true) - local thread_proc = multi:getCurrentProcess():newProcessor(name, true) + local thread_proc = multi:getCurrentProcess():newProcessor(name).Start() + local Active = true local handler = thread_proc:getHandler() @@ -1428,24 +1430,54 @@ function thread:newProcessor(name) return thread_proc.threads end + function proc:getFullName() + return thread_proc.parent:getFullName() .. "." .. c.Name + end + + function proc:getName() + return thread_proc.Name + end + + function proc:isActive() + return Active + end + function proc:newThread(name, func,...) return thread.newThread(thread_proc, name, func, ...) end - function c:newFunction(func, holdme) + function proc:newFunction(func, holdme) return thread:newFunctionBase(function(...) return thread_proc:newThread("Threaded Function Handler", func, ...) end, holdme)() end + + function proc.Start() + Active = true + return c + end + + function proc.Stop() + Active = false + return c + end + + function proc:Destroy() + Active = false + thread_proc:Destroy() + end proc.OnObjectCreated(function(obj) + if not obj.Act then return end thread_proc:newThread(function() + obj.reallocate = empty_func while true do - thread.yield() - -- + thread.hold(function() return Active end) + obj:Act() end end) end) + return proc end diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 4ba71e7..274a562 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -129,12 +129,22 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end }) - function THREAD.setENV(env) - GLOBAL["__env"] = env + function THREAD.setENV(env, name) + name = name or "__env" + GLOBAL[name] = env end - function THREAD.getENV() - return GLOBAL["__env"] + function THREAD.getENV(name) + name = name or "__env" + return GLOBAL[name] + end + + function THREAD.exposeENV(name) + name = name or "__env" + local env = THREAD.getENV(name) + for i,v in pairs(env) do + _G[i] = v + end end return GLOBAL, THREAD diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 250b2ec..b0de12f 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -167,12 +167,23 @@ function threads.unpackENV(env) return e end -function threads.setENV(env) - (threads.getGlobal())["__env"] = threads.packENV(env) + +function threads.setENV(env, name) + name = name or "__env" + (threads.getGlobal())[name] = threads.packENV(env) end -function threads.getENV() - return threads.unpackENV((threads.getGlobal())["__env"]) +function threads.getENV(name) + name = name or "__env" + return threads.unpackENV((threads.getGlobal())[name]) +end + +function threads.exposeENV(name) + name = name or "__env" + local env = threads.getENV(name) + for i,v in pairs(env) do + _G[i] = v + end end function threads.createTable(n) diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 26a7144..d503b4d 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -59,13 +59,14 @@ function multi:newSystemThread(name,func,...) GLOBAL["$__THREADNAME__"] = name GLOBAL["$THREAD_ID"] = id GLOBAL["$thread"] = thread + local env = { GLOBAL = GLOBAL, THREAD = THREAD, THREAD_NAME = name, __THREADNAME__ = name, THREAD_ID = id, - thread = thread + thread = thread, } if GLOBAL["__env"] then diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 26fc38c..6e44a23 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -96,12 +96,23 @@ local function INIT(thread) THREAD.hold = thread.hold - function THREAD.setENV(env) - GLOBAL["__env"] = env + function THREAD.setENV(env, name) + name = name or "__env" + GLOBAL[name] = env end - function THREAD.getENV() - return GLOBAL["__env"] + function THREAD.getENV(name) + name = name or "__env" + return GLOBAL[name] + end + + function THREAD.exposeENV(name) + name = name or "__env" + local env = THREAD.getENV(name) + for i,v in pairs(env) do + -- This may need to be reworked! + _G[i] = v + end end return GLOBAL, THREAD diff --git a/tests/test.lua b/tests/test.lua index 2b0e4e1..0d326f2 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -11,49 +11,64 @@ require("multi.integration.priorityManager") -- print("Running...") -local conn1, conn2 = multi:newConnection(), multi:newConnection() -conn3 = conn1 + conn2 +-- local conn1, conn2 = multi:newConnection(), multi:newConnection() +-- conn3 = conn1 + conn2 -conn1(function() - print("Hi 1") +-- conn1(function() +-- print("Hi 1") +-- end) + +-- conn2(function() +-- print("Hi 2") +-- end) + +-- conn3(function() +-- print("Hi 3") +-- end) + +-- function test(a,b,c) +-- print("I run before all and control if execution should continue!") +-- return a>b +-- end + +-- conn4 = test .. conn1 + +-- conn5 = conn2 .. function() print("I run after it all!") end + +-- conn4:Fire(3,2,3) +-- -- This second one won't trigger the Hi's +-- conn4:Fire(1,2,3) + +-- conn5(function() +-- print("Test 1") +-- end) + +-- conn5(function() +-- print("Test 2") +-- end) + +-- conn5(function() +-- print("Test 3") +-- end) + +-- conn5:Fire() +multi.print("Testing thread:newProcessor()") + +proc = thread:newProcessor("Test") + +proc:newLoop(function() + multi.print("Running...") + thread.sleep(1) end) -conn2(function() - print("Hi 2") +proc:newThread(function() + while true do + multi.warn("Everything is a thread in this proc!") + thread.sleep(1) + end end) -conn3(function() - print("Hi 3") -end) - -function test(a,b,c) - print("I run before all and control if execution should continue!") - return a>b -end - -conn4 = test .. conn1 - -conn5 = conn2 .. function() print("I run after it all!") end - -conn4:Fire(3,2,3) --- This second one won't trigger the Hi's -conn4:Fire(1,2,3) - -conn5(function() - print("Test 1") -end) - -conn5(function() - print("Test 2") -end) - -conn5(function() - print("Test 3") -end) - -conn5:Fire() - ---multi:mainloop() +multi:mainloop() -- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() -- 2.43.0 From 9f3b663fa2ad504d68be460529129e27bde3c14e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 6 May 2023 16:52:48 -0400 Subject: [PATCH 030/117] Typo in changes.md --- docs/changes.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changes.md b/docs/changes.md index ec4996c..20457ea 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -60,6 +60,13 @@ Table of contents # Update 16.0.0 - Getting the priorities straight +Full Update Showcase +--- +```lua +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() +``` + ## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! -- 2.43.0 From cfa4c0f0b625f48e3bf9249dae5ec4356cc96227 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 7 May 2023 00:19:34 -0400 Subject: [PATCH 031/117] Fixed typo in pseudoManager --- init.lua | 3 ++- integration/pseudoManager/init.lua | 2 +- tests/threadtests.lua | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 53cf5e8..1cd1576 100644 --- a/init.lua +++ b/init.lua @@ -1442,7 +1442,7 @@ function thread:newProcessor(name) return Active end - function proc:newThread(name, func,...) + function proc:newThread(name, func, ...) return thread.newThread(thread_proc, name, func, ...) end @@ -2149,6 +2149,7 @@ function multi:getLoad() end function multi:setPriority(s) + if not self.IsAnActor or self.Type == multi.PROCESS then return end if type(s)=="number" then self.Priority=s elseif type(s)=='string' then diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index d503b4d..e428445 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -32,7 +32,7 @@ if multi.integration then } end -local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) +local GLOBAL, THREAD = require("multi.integration.pseudoManager.threads").init(thread) function multi:canSystemThread() -- We are emulating system threading return true diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 4d382d9..430334e 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,4 +1,4 @@ -package.path = "../?/init.lua;../?.lua;"..package.path +--package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 @@ -34,13 +34,14 @@ THREAD.setENV({ multi.error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") end end -}) +},"Test_ENV") multi:newThread("Scheduler Thread",function() queue = multi:newSystemThreadedQueue("Test_Queue"):init() th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() + THREAD.exposeENV("Test_ENV") multi_assert("Test_Thread_1", THREAD.getName(), "Thread name does not match!") multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") multi_assert(true, e, "Argument e is not true!") -- 2.43.0 From e616b51d6f2249a2f8bcb9f0bbb4035351c9b134 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 7 May 2023 00:24:07 -0400 Subject: [PATCH 032/117] fixing --- integration/pseudoManager/init.lua | 2 +- tests/threadtests.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index e428445..a17daf2 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -100,7 +100,7 @@ multi.print("Integrated Pesudo Threading!") multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD -require("multi.integration.pesudoManager.extensions") +require("multi.integration.pseudoManager.extensions") return { init = function() return GLOBAL, THREAD diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 430334e..6698e8a 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,4 +1,4 @@ ---package.path = "../?/init.lua;../?.lua;"..package.path +package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 -- 2.43.0 From ec9f7dec614f14e7d7b7987f1a06dd7f9bfb72af Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 7 May 2023 01:31:29 -0400 Subject: [PATCH 033/117] Trying to fix exposeENV with pseudoThreading --- init.lua | 8 ++++---- integration/pseudoManager/init.lua | 3 +++ integration/pseudoManager/threads.lua | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 1cd1576..092cf1f 100644 --- a/init.lua +++ b/init.lua @@ -472,15 +472,15 @@ function multi:isFindingOptimizing() end -- Used with ISO Threads -local function isolateFunction(func,env) +local function isolateFunction(func, env) local dmp = string.dump(func) local env = env or {} if setfenv then - local f = loadstring(dmp,"IsolatedThread_PesudoThreading") - setfenv(f,env) + local f = loadstring(dmp, "IsolatedThread_PesudoThreading") + setfenv(f, env) return f else - return load(dmp,"IsolatedThread_PesudoThreading","bt",env) + return load(dmp,"IsolatedThread_PesudoThreading", "bt", env) end end diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index a17daf2..887d1ad 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -54,6 +54,7 @@ local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getf tab = split(tab) local id = 0 + function multi:newSystemThread(name,func,...) GLOBAL["$THREAD_NAME"] = name GLOBAL["$__THREADNAME__"] = name @@ -69,6 +70,8 @@ function multi:newSystemThread(name,func,...) thread = thread, } + env.__env = env + if GLOBAL["__env"] then for i,v in pairs(GLOBAL["__env"]) do env[i] = v diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 6e44a23..9aa2621 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -107,6 +107,7 @@ local function INIT(thread) end function THREAD.exposeENV(name) + print("env",__env) name = name or "__env" local env = THREAD.getENV(name) for i,v in pairs(env) do -- 2.43.0 From a5add937478671a8dee0726790e191d5a7370782 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 7 May 2023 23:03:15 -0400 Subject: [PATCH 034/117] Changes to threads --- .gitignore | 3 +- docs/changes.md | 2 ++ init.lua | 22 +++++++-------- integration/lanesManager/extensions.lua | 6 ++-- integration/lanesManager/threads.lua | 8 ------ integration/loveManager/init.lua | 2 ++ integration/loveManager/threads.lua | 8 ------ integration/pseudoManager/extensions.lua | 13 +++++++-- integration/pseudoManager/init.lua | 36 ++++++++++++++---------- integration/pseudoManager/threads.lua | 16 +++-------- tests/multi | 1 - tests/threadtests.lua | 13 +++++---- 12 files changed, 63 insertions(+), 67 deletions(-) delete mode 120000 tests/multi diff --git a/.gitignore b/.gitignore index f3ede8b..0a96143 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.code-workspace -lua5.4/* \ No newline at end of file +lua5.4/* +test.lua \ No newline at end of file diff --git a/docs/changes.md b/docs/changes.md index 20457ea..92673dd 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -259,6 +259,8 @@ Changed Removed --- +- THREAD.getName() use THREAD_NAME instead +- THREAD.getID() use THREAD_ID instead - conn:SetHelper(func) -- With the removal of old Connect this function is no longer needed - connection events can no longer can be chained with connect. Connect only takes a function that you want to connect diff --git a/init.lua b/init.lua index 092cf1f..16ec046 100644 --- a/init.lua +++ b/init.lua @@ -473,15 +473,13 @@ end -- Used with ISO Threads local function isolateFunction(func, env) - local dmp = string.dump(func) - local env = env or {} - if setfenv then - local f = loadstring(dmp, "IsolatedThread_PesudoThreading") - setfenv(f, env) - return f - else - return load(dmp,"IsolatedThread_PesudoThreading", "bt", env) - end + if setfenv then + return setfenv(func, env) + else + local env = env or {} + local dmp = string.dump(func) + return load(dmp,"IsolatedThread_PesudoThreading", "bt", env) + end end function multi:Break() @@ -1585,9 +1583,9 @@ function thread:newThread(name, func, ...) return c end -function thread:newISOThread(name, func, _env, ...) +function thread:newISOThread(name, func, env, ...) local func = func or name - local env = _env or {} + local env = env or {} if not env.thread then env.thread = thread end @@ -2340,7 +2338,7 @@ function multi.error(self, err) if type(err) == "bool" then crash = err end if type(self) == "string" then err = self end io.write("\x1b[91mERROR:\x1b[0m " .. err .. "\n") - error("^^^") + error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type) if multi.defaultSettings.error then os.exit(1) end diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 42eee9f..e2db201 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -196,7 +196,7 @@ function multi:newSystemThreadedConnection(name) end return r end - c.CID = THREAD.getID() + c.CID = THREAD_ID c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() c.Name = name c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. @@ -262,7 +262,7 @@ function multi:newSystemThreadedConnection(name) function c:Fire(...) local args = {...} - if self.CID == THREAD.getID() then -- Host Call + if self.CID == THREAD_ID then -- Host Call for _, link in pairs(self.links) do link:push {self.TRIG, args} end @@ -284,7 +284,7 @@ function multi:newSystemThreadedConnection(name) tempMT.__index = self.proxy_conn tempMT.__call = function(t,func) self.proxy_conn(func) end setmetatable(self, tempMT) - if self.CID == THREAD.getID() then return self end + if self.CID == THREAD_ID then return self end thread:newThread("STC_CONN_MAN"..name,function() local item local link_self_ref = multi:newSystemThreadedQueue() diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 274a562..a1477ef 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -88,14 +88,6 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) 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 = {...} diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index e12098a..8f26e30 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -34,6 +34,8 @@ __IMPORTS = {...} __FUNC__=table.remove(__IMPORTS,1) __THREADID__=table.remove(__IMPORTS,1) __THREADNAME__=table.remove(__IMPORTS,1) +THREAD_NAME = __THREADNAME__ +THREAD_ID = __THREADID__ math.randomseed(__THREADID__) math.random() math.random() diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index b0de12f..25008fe 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -114,14 +114,6 @@ 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 diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index bd59f10..5227573 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -50,6 +50,7 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name or "_"] = c return c end + function multi:newSystemThreadedTable(name) local c = {} function c:init() @@ -58,6 +59,7 @@ function multi:newSystemThreadedTable(name) GLOBAL[name or "_"] = c return c end + local setfenv = setfenv if not setfenv then if not debug then @@ -68,6 +70,7 @@ if not setfenv then end end end + function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 @@ -76,26 +79,32 @@ function multi:newSystemThreadedJobQueue(n) local ID=1 local jid = 1 local env = {} + setmetatable(env,{ __index = _G }) + local funcs = {} function c:doToAll(func) setfenv(func,env)() return self end + function c:registerFunction(name,func) funcs[name] = setfenv(func,env) return self end + function c:pushJob(name,...) table.insert(jobs,{name,jid,{...}}) jid = jid + 1 return jid-1 end + function c:isEmpty() return #jobs == 0 end + local nFunc = 0 function c:newFunction(name,func,holup) -- This registers with the queue local func = stripUpValues(func) @@ -120,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) return unpack(rets) or multi.NIL end end) - end,holup),name + end, holup), name end for i=1,c.cores do thread:newThread("PesudoThreadedJobQueue_"..i,function() @@ -133,7 +142,7 @@ function multi:newSystemThreadedJobQueue(n) thread.sleep(.05) end end - end).OnError(print) + end).OnError(multi.error) end return c end diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 887d1ad..e495223 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -31,8 +31,8 @@ if multi.integration then end } end - -local GLOBAL, THREAD = require("multi.integration.pseudoManager.threads").init(thread) +local activator = require("multi.integration.pseudoManager.threads") +local GLOBAL, THREAD = activator.init(thread) function multi:canSystemThread() -- We are emulating system threading return true @@ -55,22 +55,28 @@ 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 +print("Outerglobal",_G) - local env = { +function multi:newSystemThread(name, func, ...) + local env + env = { GLOBAL = GLOBAL, THREAD = THREAD, - THREAD_NAME = name, - __THREADNAME__ = name, + THREAD_NAME = tostring(name), + __THREADNAME__ = tostring(name), + test = "testing", THREAD_ID = id, thread = thread, + multi = multi, } - env.__env = env + for i, v in pairs(_G) do + if not(env[i]) and not(i == "_G") and not(i == "local_global") then + env[i] = v + else + multi.warn("skipping:",i) + end + end if GLOBAL["__env"] then for i,v in pairs(GLOBAL["__env"]) do @@ -78,11 +84,11 @@ function multi:newSystemThread(name,func,...) end end - for i = 1,#tab do - env[tab[i]] = _G[tab[i]] - end + env._G = env + + local GLOBAL, THREAD = activator.init(thread, env) - local th = thread:newISOThread(name,func,env,...) + local th = thread:newISOThread(name, func, env, ...) id = id + 1 diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 9aa2621..2b46982 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -33,6 +33,7 @@ end local function INIT(thread) local THREAD = {} local GLOBAL = {} + THREAD.Priority_Core = 3 THREAD.Priority_High = 2 THREAD.Priority_Above_Normal = 1 @@ -84,14 +85,6 @@ local function INIT(thread) error("Thread was killed!") end - function THREAD.getName() - return GLOBAL["$THREAD_NAME"] - end - - function THREAD.getID() - return GLOBAL["$THREAD_ID"] - end - THREAD.sleep = thread.sleep THREAD.hold = thread.hold @@ -107,18 +100,17 @@ local function INIT(thread) end function THREAD.exposeENV(name) - print("env",__env) name = name or "__env" local env = THREAD.getENV(name) for i,v in pairs(env) do -- This may need to be reworked! - _G[i] = v + local_global[i] = v end end return GLOBAL, THREAD end -return {init = function(thread) - return INIT(thread) +return {init = function(thread, global) + return INIT(thread, global) end} \ No newline at end of file diff --git a/tests/multi b/tests/multi deleted file mode 120000 index d78adc0..0000000 --- a/tests/multi +++ /dev/null @@ -1 +0,0 @@ -D:/VSCWorkspace/multi \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 6698e8a..f7126d1 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,5 +1,5 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} +multi, thread = require("multi"):init{}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 local env, we_good @@ -31,18 +31,21 @@ multi.print("Testing THREAD.setENV() if the multi_assert is not found then there THREAD.setENV({ multi_assert = function(expected, actual, s) if expected ~= actual then - multi.error(s .. " Expected: '".. expected .."' Actual: '".. actual .."'") + multi.error(s .. " Expected: '".. tostring(expected) .."' Actual: '".. tostring(actual) .."'") end end -},"Test_ENV") +}) multi:newThread("Scheduler Thread",function() queue = multi:newSystemThreadedQueue("Test_Queue"):init() + + multi:newSystemThread("Test_Thread_0", function() + print("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) + end) th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() - THREAD.exposeENV("Test_ENV") - multi_assert("Test_Thread_1", THREAD.getName(), "Thread name does not match!") + multi_assert("Test_Thread_1", THREAD_NAME, "Thread name does not match!") multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") multi_assert(true, e, "Argument e is not true!") multi_assert("table", type(f), "Argument f is not a table!") -- 2.43.0 From 5caa90f6c7005632aae36c88fb11d1741adc0d16 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 7 May 2023 23:04:06 -0400 Subject: [PATCH 035/117] updated changes.md --- docs/changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changes.md b/docs/changes.md index 92673dd..a4f65ae 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -266,6 +266,7 @@ Removed Fixed --- +- Issue with pseudo threading env's being messed up. Required removal of getName and getID! - connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! - multi:reallocate(processor, index) has been fixed to work with the current changes of the library. - Issue with lanes not handling errors properly. This is now resolved -- 2.43.0 From 160c72d2f3411b28bfabd4486a1471f2e83ea1f2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 14 May 2023 00:52:35 -0400 Subject: [PATCH 036/117] Working on systemthreadedprocess, and experimental newProxy for threading --- docs/changes.md | 3 +- init.lua | 147 ++++++++++++------------ integration/lanesManager/extensions.lua | 34 ++++-- integration/lanesManager/init.lua | 2 + integration/lanesManager/threads.lua | 6 +- integration/loveManager/extensions.lua | 14 ++- integration/loveManager/init.lua | 2 + integration/pseudoManager/init.lua | 1 + integration/sharedExtensions/init.lua | 127 ++++++++++++++++++++ 9 files changed, 239 insertions(+), 97 deletions(-) create mode 100644 integration/sharedExtensions/init.lua diff --git a/docs/changes.md b/docs/changes.md index a4f65ae..82e882a 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -237,6 +237,7 @@ Added Changed --- +- multi:newUpdater(skip, func) -- Now accepts func as the second argument. So you don't need to call OnUpdate(func) after creation. - multi errors now internally call `multi.error` instead of `multi.print` - Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise. - Connection:Connect(func, name) Now you can supply a name and name the connection. @@ -296,7 +297,7 @@ Fixed ToDo --- -- N/A +- Network Manager, I know I said it will be in this release, but I'm still planning it out. # Update 15.3.1 - Bug fix Fixed diff --git a/init.lua b/init.lua index 16ec046..10202d9 100644 --- a/init.lua +++ b/init.lua @@ -32,6 +32,51 @@ local find_optimization = false local threadManager local __CurrentConnectionThread +-- Types + +multi.DestroyedObj = { + Type = "DESTROYED", +} + +local function uni() + return multi.DestroyedObj +end + +local function uniN() end +function multi.setType(obj,t) + if t == multi.DestroyedObj then + for i,v in pairs(obj) do + obj[i] = nil + end + setmetatable(obj, { + __index = function(t,k) + return setmetatable({},{__index = uni,__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}) + 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 + }) + end +end +setmetatable(multi.DestroyedObj, { + __index = function(t,k) + return setmetatable({},{__index = uni,__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}) + 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 +}) + +multi.DESTROYED = multi.DestroyedObj +multi.ROOTPROCESS = "rootprocess" +multi.CONNECTOR = "connector" +multi.TIMEMASTER = "timemaster" +multi.PROCESS = "process" +multi.TIMER = "timer" +multi.EVENT = "event" +multi.UPDATER = "updater" +multi.ALARM = "alarm" +multi.LOOP = "loop" +multi.TLOOP = "tloop" +multi.STEP = "step" +multi.TSTEP = "tstep" +multi.THREAD = "thread" +multi.SERVICE = "service" + if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} end @@ -43,7 +88,7 @@ local NIL = multi.NIL multi.Mainloop = {} multi.Children = {} multi.Active = true -multi.Type = "rootprocess" +multi.Type = multi.ROOTPROCESS multi.LinkedPath = multi multi.TIMEOUT = "TIMEOUT" multi.TID = 0 @@ -87,24 +132,6 @@ local function pack(...) return {...} end --- Types -multi.DESTROYED = multi.DestroyedObj -multi.ROOTPROCESS = "rootprocess" -multi.CONNECTOR = "connector" -multi.CONNECTOR_LINK = "connector_link" -multi.TIMEMASTER = "timemaster" -multi.PROCESS = "process" -multi.TIMER = "timer" -multi.EVENT = "event" -multi.UPDATER = "updater" -multi.ALARM = "alarm" -multi.LOOP = "loop" -multi.TLOOP = "tloop" -multi.STEP = "step" -multi.TSTEP = "tstep" -multi.THREAD = "thread" -multi.SERVICE = "service" - --Processor local priorityTable = {[false]="Disabled",[true]="Enabled"} local ProcessName = {"SubProcessor","MainProcessor"} @@ -273,7 +300,7 @@ function multi:newConnection(protect,func,kill) return cn end}) - c.Type='connector' + c.Type=multi.CONNECTOR c.func={} c.ID=0 local protect=protect or false @@ -504,7 +531,7 @@ end function multi:SetTime(n) if not n then n=3 end local c=self:newBase() - c.Type='timemaster' + c.Type=multi.TIMEMASTER c.timer=self:newTimer() c.timer:Start() c.set=n @@ -533,7 +560,7 @@ end -- Timer stuff done multi.PausedObjects = {} function multi:Pause() - if self.Type=='rootprocess' then + if self.Type==multi.ROOTPROCESS then multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") else self.Active=false @@ -550,7 +577,7 @@ function multi:Pause() end function multi:Resume() - if self.Type=='process' or self.Type=='rootprocess' then + if self.Type==multi.PROCESS or self.Type==multi.ROOTPROCESS then self.Active=true local c=self:getChildren() for i=1,#c do @@ -567,7 +594,7 @@ function multi:Resume() end function multi:Destroy() - if self.Type=='process' or self.Type=='rootprocess' then + if self.Type==multi.PROCESS or self.Type==multi.ROOTPROCESS then local c=self:getChildren() for i=1,#c do self.OnObjectDestroyed:Fire(c[i]) @@ -620,9 +647,9 @@ end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) - if not(self.Type=='rootprocess' or self.Type=='process') then multi.error('Can only create an object on multi or an interface obj') return false end + if not(self.Type==multi.ROOTPROCESS or self.Type==multi.PROCESS) then multi.error('Can only create an object on multi or an interface obj') return false end local c = {} - if self.Type=='process' then + if self.Type==multi.PROCESS then setmetatable(c, {__index = multi}) else setmetatable(c, {__index = multi}) @@ -646,18 +673,13 @@ function multi:newBase(ins) return c end -function multi:newConnector() - local c = {Type = "connector"} - return c -end - multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() multi.OnLoad = multi:newConnection(nil,nil,true) ignoreconn = false function multi:newTimer() local c={} - c.Type='timer' + c.Type=multi.TIMER local time=0 local count=0 local paused=false @@ -690,7 +712,7 @@ end --Core Actors function multi:newEvent(task) local c=self:newBase() - c.Type='event' + c.Type=multi.EVENT local task = task or function() end function c:Act() local t = task(self) @@ -712,9 +734,9 @@ function multi:newEvent(task) return c end -function multi:newUpdater(skip) +function multi:newUpdater(skip, func) local c=self:newBase() - c.Type='updater' + c.Type=multi.UPDATER local pos = 1 local skip = skip or 1 function c:Act() @@ -731,13 +753,16 @@ function multi:newUpdater(skip) end c.OnUpdate = self:newConnection():fastMode() c:setName(c.Type) + if func then + c.OnUpdate(func) + end self:create(c) return c end function multi:newAlarm(set) local c=self:newBase() - c.Type='alarm' + c.Type=multi.ALARM c:setPriority("Low") c.set=set or 0 local count = 0 @@ -773,9 +798,9 @@ function multi:newAlarm(set) return c end -function multi:newLoop(func,notime) +function multi:newLoop(func, notime) local c=self:newBase() - c.Type='loop' + c.Type=multi.LOOP local start=clock() if notime then function c:Act() @@ -802,7 +827,7 @@ end function multi:newStep(start,reset,count,skip) local c=self:newBase() think=1 - c.Type='step' + c.Type=multi.STEP c.pos=start or 1 c.endAt=reset or math.huge c.skip=skip or 0 @@ -861,7 +886,7 @@ end function multi:newTLoop(func,set) local c=self:newBase() - c.Type='tloop' + c.Type=multi.TLOOP c.set=set or 0 c.timer=self:newTimer() c.life=0 @@ -902,7 +927,7 @@ end function multi:newTStep(start,reset,count,set) local c=self:newStep(start,reset,count) - c.Type='tstep' + c.Type=multi.TSTEP c:setPriority("Low") local reset = reset or math.huge c.timer=clock() @@ -1034,7 +1059,7 @@ function multi:newProcessor(name, nothread) local name = name or "Processor_" .. sandcount sandcount = sandcount + 1 c.Mainloop = {} - c.Type = "process" + c.Type = multi.PROCESS local Active = nothread or false c.Name = name or "" c.threads = {} @@ -1248,7 +1273,7 @@ function thread.hold(n,opt) 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 + elseif type(n) == "table" and n.Type == multi.CONNECTOR then return yield(CMD, t_hold, conn_test(n), nil, interval) elseif type(n) == "function" then return yield(CMD, t_hold, n or dFunc, nil, interval) @@ -1499,7 +1524,7 @@ function thread:newThread(name, func, ...) c.Name=name c.thread=create(func) c.sleep=1 - c.Type = "thread" + c.Type = multi.THREAD c.TID = threadid c.firstRunDone=false c._isPaused = false @@ -1561,7 +1586,7 @@ function thread:newThread(name, func, ...) c.Destroy = c.Kill if thread.isThread() then multi:newLoop(function(loop) - if self.Type == "process" then + if self.Type == multi.PROCESS then table.insert(self.startme, c) else table.insert(threadManager.startme, c) @@ -1569,7 +1594,7 @@ function thread:newThread(name, func, ...) loop:Break() end) else - if self.Type == "process" then + if self.Type == multi.PROCESS then table.insert(self.startme, c) else table.insert(threadManager.startme, c) @@ -1782,7 +1807,7 @@ end function multi:newService(func) -- Priority managed threads local c = {} - c.Type = "service" + c.Type = multi.SERVICE c.OnStopped = self:newConnection() c.OnStarted = self:newConnection() local Service_Data = {} @@ -1957,7 +1982,7 @@ local function doOpt() 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 + elseif type(n) == "table" and n.Type == multi.CONNECTOR then local rdy = function() return false end @@ -2074,32 +2099,6 @@ if table.unpack and not unpack then unpack=table.unpack end -multi.DestroyedObj = { - Type = "destroyed", -} - -local function uni() - return multi.DestroyedObj -end - -local function uniN() end -function multi.setType(obj,t) - if t == multi.DestroyedObj then - for i,v in pairs(obj) do - obj[i] = nil - end - setmetatable(obj, { - __index = function(t,k) - return setmetatable({},{__index = uni,__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}) - 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 - }) - end -end -setmetatable(multi.DestroyedObj, { - __index = function(t,k) - return setmetatable({},{__index = uni,__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}) - 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()) function multi:enableLoadDetection() diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index e2db201..fd687d2 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -22,8 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi, thread = require("multi"):init() + if not (GLOBAL and THREAD) then - local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD + GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD else lanes = require("lanes") end @@ -54,17 +55,24 @@ function multi:newSystemThreadedTable(name) local c = {} c.link = lanes.linda() c.Name = name - setmetatable(c,{ + + -- function c:getIndex() + -- return c.link:dump() + -- end + + function c:init() + return self + end + + setmetatable(c,{ __index = function(t,k) return c.link:get(k) end, __newindex = function(t,k,v) - c.link:set(k,v) + c.link:set(k, v) end }) - function c:init() - return self - end + GLOBAL[name or "_"] = c return c end @@ -134,7 +142,7 @@ function multi:newSystemThreadedJobQueue(n) end) for i=1,c.cores do multi:newSystemThread("SystemThreadedJobQueue",function(queue) - local multi,thread = require("multi"):init() + local multi, thread = require("multi"):init() local idle = os.clock() local clock = os.clock local ref = 0 @@ -145,12 +153,14 @@ function multi:newSystemThreadedJobQueue(n) return queueJob:pop() end) idle = clock() - local name = table.remove(dat,1) - local jid = table.remove(dat,1) - local args = table.remove(dat,1) - queueReturn:push{jid, funcs[name](unpack(args)),queue} + thread:newThread("test",function() + local name = table.remove(dat, 1) + local jid = table.remove(dat, 1) + local args = table.remove(dat, 1) + queueReturn:push{jid, funcs[name](unpack(args)), queue} + end) end - end) + end).OnError(multi.error) thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 398dc0c..3cb85a4 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -97,6 +97,7 @@ function multi:newSystemThread(name, func, ...) },function(...) multi, thread = require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") + require("multi.integration.sharedExtensions") local has_error = true returns = {pcall(func, ...)} return_linda:set("returns", returns) @@ -179,6 +180,7 @@ multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.lanesManager.extensions") +require("multi.integration.sharedExtensions") return { init = function() return GLOBAL, THREAD diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index a1477ef..d8a3838 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -122,13 +122,11 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) }) function THREAD.setENV(env, name) - name = name or "__env" - GLOBAL[name] = env + GLOBAL[name or "__env"] = env end function THREAD.getENV(name) - name = name or "__env" - return GLOBAL[name] + return GLOBAL[name or "__env"] end function THREAD.exposeENV(name) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index c5b1cc1..dfa9688 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -184,12 +184,14 @@ function multi:newSystemThreadedJobQueue(n) end local dat = queue:performAtomic(atomic) if dat then - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) + multi:newThread("Test",function() + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end) end end end):OnError(function(...) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 8f26e30..2b5245c 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -53,6 +53,7 @@ multi.integration={} multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD pcall(require,"multi.integration.loveManager.extensions") +pcall(require,"multi.integration.sharedExtensions") stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} ]] @@ -121,5 +122,6 @@ end multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.loveManager.extensions") +require("multi.integration.sharedExtensions") multi.print("Integrated Love Threading!") return {init = function() return GLOBAL, THREAD end} diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index e495223..6c8d187 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -110,6 +110,7 @@ multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.pseudoManager.extensions") +require("multi.integration.sharedExtensions") return { init = function() return GLOBAL, THREAD diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua new file mode 100644 index 0000000..cbb81a5 --- /dev/null +++ b/integration/sharedExtensions/init.lua @@ -0,0 +1,127 @@ +local multi, thread = require("multi"):init() + + +-- Returns a handler that allows a user to interact with an object on another thread! +-- Create on the thread that you want to interact with, send over the handle +function multi:newProxy(obj) + + local c = { + __index = function() + -- + end, + __newindex = function() + -- + end, + __call = function() + -- + end + } + + c.name = multi.randomString(12) + + function c:init() + if not multi.isMainThread then + c.send = multi:newSystemThreadedQueue(self.name.."_S"):init() + c.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() + c.ref = obj + else + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD + c.send = THREAD.waitFor(self.name.."_S") + c.recv = THREAD.waitFor(self.name.."_R") + end + end + + return c +end + +function multi:newSystemThreadedProcessor(name, cores) + + local name = name or "STP_"multi.randomString(4) -- set a random name if none was given. + + local autoscale = autoscale or false -- Will scale up the number of cores that the process uses. + local c = {} + + setmetatable(c,{__index = multi}) + + c.cores = cores or 8 + c.Name = name + c.Mainloop = {} + c.__count = 0 + c.processors = {} + c.proc_list = {} + c.OnObjectCreated = multi:newConnection() + c.parent = self + c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) + + c.jobqueue:registerFunction("__spawnThread__", function(name, func, ...) + local multi, thread = require("multi"):init() + thread:newThread(name, func, ...) + return true + end) + + c.jobqueue:registerFunction("__spawnTask__", function(obj, ...) + local multi, thread = require("multi"):init() + multi[obj](multi, func) + return true + end) + + c.OnObjectCreated(function(proc, obj) + if obj.Type == multi.UPDATER then + local func = obj.OnUpdate:Remove()[1] + c.jobqueue:pushJob("__spawnTask__", "newUpdater", func) + elseif obj.Type == multi.LOOP then + local func = obj.OnLoop:Remove()[1] + c.jobqueue:pushJob("__spawnTask__", "newLoop", func) + else + return multi.error("Invalid type!") + end + end) + + function c:getHandler() + -- Not needed + end + + function c:getThreads() + -- We might want to keep track of the number of threads we have + end + + function c:getFullName() + return self.parent:getFullName() .. "." .. c.Name + end + + function c:getName() + return self.Name + end + + function c:newThread(name, func, ...) + c.jobqueue:pushJob("__spawnThread__", name, func, ...) + end + + function c:newFunction(func, holdme) + return c.jobqueue:newFunction(func, holdme) + end + + function c.run() + -- Not needed + end + + function c.isActive() + -- + end + + function c.Start() + -- + end + + function c.Stop() + -- + end + + function c:Destroy() + -- + end + + return c +end + -- 2.43.0 From ea4be86ae249abdfc4099ce0682a1d46cd3a8634 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 16 May 2023 23:40:14 -0400 Subject: [PATCH 037/117] newProxy and STP work --- docs/changes.md | 3 + init.lua | 32 +++--- integration/lanesManager/extensions.lua | 17 ++-- integration/lanesManager/init.lua | 5 +- integration/loveManager/extensions.lua | 12 +-- integration/loveManager/init.lua | 6 +- integration/loveManager/threads.lua | 2 +- integration/lovrManager/extensions.lua | 6 +- integration/lovrManager/init.lua | 4 +- integration/lovrManager/threads.lua | 2 +- integration/pseudoManager/extensions.lua | 4 +- integration/pseudoManager/init.lua | 2 +- integration/sharedExtensions/init.lua | 124 ++++++++++++++++------- 13 files changed, 138 insertions(+), 81 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 82e882a..3b49ce0 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -237,6 +237,7 @@ Added Changed --- +- changed how multi adds unpack to the global namespace. Instead we capture that value into multi.unpack. - multi:newUpdater(skip, func) -- Now accepts func as the second argument. So you don't need to call OnUpdate(func) after creation. - multi errors now internally call `multi.error` instead of `multi.print` - Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise. @@ -260,6 +261,8 @@ Changed Removed --- +- multi.CONNECTOR_LINK -- No longer used +- multi:newConnector() -- No longer used - THREAD.getName() use THREAD_NAME instead - THREAD.getID() use THREAD_ID instead - conn:SetHelper(func) -- With the removal of old Connect this function is no longer needed diff --git a/init.lua b/init.lua index 10202d9..14c538c 100644 --- a/init.lua +++ b/init.lua @@ -1,7 +1,7 @@ --[[ MIT License -Copyright (c) 2022 Ryan Ward +Copyright (c) 2023 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 @@ -32,6 +32,10 @@ local find_optimization = false local threadManager local __CurrentConnectionThread +multi.unpack = table.unpack or unpack + +if table.unpack then unpack = table.unpack end + -- Types multi.DestroyedObj = { @@ -1171,7 +1175,7 @@ function multi.hold(func,opt) multi:uManager() end proc:Resume() - return unpack(rets) + return multi.unpack(rets) end end @@ -1224,7 +1228,7 @@ function thread._Requests() if t then thread.requests[running()] = nil local cmd,args = t[1],t[2] - thread[cmd](unpack(args)) + thread[cmd](multi.unpack(args)) end end @@ -1244,7 +1248,7 @@ local function conn_test(conn) conn(func) return function() if ready then - return unpack(args) or multi.NIL + return multi.unpack(args) or multi.NIL end end end @@ -1343,7 +1347,7 @@ local function cleanReturns(...) break end end - return unpack(returns,1,ind) + return multi.unpack(returns,1,ind) end function thread.pushStatus(...) @@ -1351,8 +1355,6 @@ function thread.pushStatus(...) t.statusconnector:Fire(...) end -local handler - function thread:newFunctionBase(generator, holdme) return function() local tfunc = {} @@ -1381,7 +1383,7 @@ function thread:newFunctionBase(generator, holdme) end) else while not rets and not err do - handler() + multi:getCurrentProcess():getHandler()() end if err then return nil,err @@ -1415,7 +1417,7 @@ function thread:newFunctionBase(generator, holdme) isTFunc = true, wait = wait, getReturns = function() - return unpack(rets) + return multi.unpack(rets) end, connect = function(f) local tempConn = multi:newConnection(true) @@ -1785,7 +1787,7 @@ function multi:createHandler() 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)) + _, ret, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = resume(temp_start.thread, multi.unpack(temp_start.startArgs)) co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) table.insert(threads, temp_start) yield() @@ -2095,10 +2097,6 @@ function table.merge(t1, t2) return t1 end -if table.unpack and not unpack then - unpack=table.unpack -end - math.randomseed(os.time()) function multi:enableLoadDetection() @@ -2308,7 +2306,7 @@ function multi.timer(func,...) args={func(...)} local t = timer:Get() timer = nil - return t,unpack(args) + return t,multi.unpack(args) end if os.getOS()=="windows" then @@ -2380,4 +2378,8 @@ end threadManager = multi:newProcessor("Global_Thread_Manager").Start() +function multi:getHandler() + return threadManager:getHandler() +end + return multi \ No newline at end of file diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index fd687d2..cd0a73a 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -116,6 +116,7 @@ function multi:newSystemThreadedJobQueue(n) nFunc = nFunc + 1 c:registerFunction(name,func) return thread:newFunction(function(...) + print("Called!") local id = c:pushJob(name,...) local link local rets @@ -126,10 +127,10 @@ function multi:newSystemThreadedJobQueue(n) end) return thread.hold(function() if rets then - return unpack(rets) or multi.NIL + return multi.unpack(rets) or multi.NIL end end) - end,holup),name + end,holup), name end thread:newThread("JobQueueManager",function() while true do @@ -137,7 +138,7 @@ function multi:newSystemThreadedJobQueue(n) return queueReturn:pop() end) local id = table.remove(job,1) - c.OnJobCompleted:Fire(id,unpack(job)) + c.OnJobCompleted:Fire(id,multi.unpack(job)) end end) for i=1,c.cores do @@ -157,10 +158,10 @@ function multi:newSystemThreadedJobQueue(n) local name = table.remove(dat, 1) local jid = table.remove(dat, 1) local args = table.remove(dat, 1) - queueReturn:push{jid, funcs[name](unpack(args)), queue} + queueReturn:push{jid, funcs[name](multi.unpack(args)), queue} end) end - end).OnError(multi.error) + end).OnError(print) thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() @@ -263,8 +264,8 @@ function multi:newSystemThreadedConnection(name) end) c.links[#c.links+1] = item[2] elseif item[1] == c.TRIG then - fire(unpack(item[2])) - c.proxy_conn:Fire(unpack(item[2])) + fire(multi.unpack(item[2])) + c.proxy_conn:Fire(multi.unpack(item[2])) end end end) @@ -312,7 +313,7 @@ function multi:newSystemThreadedConnection(name) end link_self_ref:pop() elseif item[1] == self.TRIG then - self.proxy_conn:Fire(unpack(item[2])) + self.proxy_conn:Fire(multi.unpack(item[2])) link_self_ref:pop() else -- This shouldn't be the case diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 3cb85a4..ebfb569 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -131,13 +131,12 @@ function multi.InitSystemThreadErrorHandler() while true do thread.yield() _,data = __ConsoleLinda:receive(0, "Q") - if data then print(unpack(data)) end for i = #threads, 1, -1 do 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])) + temp.statusconnector:Fire(multi.unpack(({__StatusLinda:receive(nil, temp.Id)})[2])) end if status == "done" or temp.returns:get("returns") then returns = ({temp.returns:receive(0, "returns")})[2] @@ -147,7 +146,7 @@ function multi.InitSystemThreadErrorHandler() temp.OnError:Fire(temp, returns[2]) else table.remove(returns,1) - temp.OnDeath:Fire(unpack(returns)) + temp.OnDeath:Fire(multi.unpack(returns)) end GLOBAL["__THREADS__"] = livingThreads table.remove(threads, i) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index dfa9688..10e8e17 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -134,7 +134,7 @@ function multi:newSystemThreadedJobQueue(n) end) return thread.hold(function() if rets then - return unpack(rets) or multi.NIL + return multi.unpack(rets) or multi.NIL end end) end,holup),name @@ -144,7 +144,7 @@ function multi:newSystemThreadedJobQueue(n) thread.yield() local dat = c.queueReturn:pop() if dat then - c.OnJobCompleted:Fire(unpack(dat)) + c.OnJobCompleted:Fire(multi.unpack(dat)) end end end) @@ -188,7 +188,7 @@ function multi:newSystemThreadedJobQueue(n) lastProc = os.clock() local name = table.remove(dat,1) local id = table.remove(dat,1) - local tab = {funcs[name](unpack(dat))} + local tab = {funcs[name](multi.unpack(dat))} table.insert(tab,1,id) queueReturn:push(tab) end) @@ -264,7 +264,7 @@ function multi:newSystemThreadedConnection(name) end link_self_ref:pop() elseif item[1] == self.TRIG then - self.proxy_conn:Fire(unpack(item[2])) + self.proxy_conn:Fire(multi.unpack(item[2])) link_self_ref:pop() else -- This shouldn't be the case @@ -341,8 +341,8 @@ function multi:newSystemThreadedConnection(name) c.links[#c.links+1] = item[2] elseif item[1] == c.TRIG then - fire(unpack(item[2])) - c.proxy_conn:Fire(unpack(item[2])) + fire(multi.unpack(item[2])) + c.proxy_conn:Fire(multi.unpack(item[2])) end end end).OnError(print) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 2b5245c..2bb596b 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -54,7 +54,7 @@ multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD pcall(require,"multi.integration.loveManager.extensions") pcall(require,"multi.integration.sharedExtensions") -stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} +stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} ]] local multi, thread = require("multi"):init() @@ -86,7 +86,7 @@ function multi:newSystemThread(name, func, ...) thread.hold(function() -- While the thread is running we might as well do something in the loop if status_channel:peek() ~= nil then - c.statusconnector:Fire(unpack(status_channel:pop())) + c.statusconnector:Fire(multi.unpack(status_channel:pop())) end return not c.thread:isRunning() end) @@ -100,7 +100,7 @@ function multi:newSystemThread(name, func, ...) elseif thread_err then c.OnError:Fire(c, thread_err) elseif c.stab.returns then - c.OnDeath:Fire(unpack(c.stab.returns)) + c.OnDeath:Fire(multi.unpack(c.stab.returns)) c.stab.returns = nil end end) diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 25008fe..12f5471 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -223,7 +223,7 @@ if not ISTHREAD then multi:newLoop(function(loop) dat = queue:pop() if dat then - print(unpack(dat)) + print(multi.unpack(dat)) end end) end diff --git a/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua index 7032b1d..232183a 100644 --- a/integration/lovrManager/extensions.lua +++ b/integration/lovrManager/extensions.lua @@ -124,7 +124,7 @@ function multi:newSystemThreadedJobQueue(n) end) return thread.hold(function() if rets then - return unpack(rets) or multi.NIL + return multi.unpack(rets) or multi.NIL end end) end,holup),name @@ -134,7 +134,7 @@ function multi:newSystemThreadedJobQueue(n) thread.yield() local dat = c.queueReturn:pop() if dat then - c.OnJobCompleted:Fire(unpack(dat)) + c.OnJobCompleted:Fire(multi.unpack(dat)) end end end) @@ -177,7 +177,7 @@ function multi:newSystemThreadedJobQueue(n) lastProc = os.clock() local name = table.remove(dat,1) local id = table.remove(dat,1) - local tab = {funcs[name](unpack(dat))} + local tab = {funcs[name](multi.unpack(dat))} table.insert(tab,1,id) queueReturn:push(tab) end diff --git a/integration/lovrManager/init.lua b/integration/lovrManager/init.lua index 53cafa3..d09b2d9 100644 --- a/integration/lovrManager/init.lua +++ b/integration/lovrManager/init.lua @@ -36,7 +36,7 @@ __THREADNAME__=table.remove(__IMPORTS,1) stab = THREAD.createStaticTable(__THREADNAME__) GLOBAL = THREAD.getGlobal() multi, thread = require("multi").init() -stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} +stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} ]] local multi, thread = require("multi.compat.lovr2d"):init() local THREAD = {} @@ -58,7 +58,7 @@ function THREAD:newFunction(func,holup) if t.stab["returns"] then local dat = t.stab.returns t.stab.returns = nil - return unpack(dat) + return multi.unpack(dat) end end) end,holup)() diff --git a/integration/lovrManager/threads.lua b/integration/lovrManager/threads.lua index 6a95a1e..12429c4 100644 --- a/integration/lovrManager/threads.lua +++ b/integration/lovrManager/threads.lua @@ -174,7 +174,7 @@ if not ISTHREAD then dat = queue:pop() if dat then lastproc = clock() - print(unpack(dat)) + print(multi.unpack(dat)) end if clock()-lastproc>2 then thread.sleep(.1) diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 5227573..bfdd982 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -126,7 +126,7 @@ function multi:newSystemThreadedJobQueue(n) end) return thread.hold(function() if rets then - return unpack(rets) or multi.NIL + return multi.unpack(rets) or multi.NIL end end) end, holup), name @@ -137,7 +137,7 @@ function multi:newSystemThreadedJobQueue(n) thread.yield() if #jobs>0 then local j = table.remove(jobs,1) - c.OnJobCompleted:Fire(j[2],funcs[j[1]](unpack(j[3]))) + c.OnJobCompleted:Fire(j[2],funcs[j[1]](multi.unpack(j[3]))) else thread.sleep(.05) end diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 6c8d187..7e41344 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -50,7 +50,7 @@ local function split(str) return tab end -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]] +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,xpcall,math,coroutine,string,table]] tab = split(tab) local id = 0 diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index cbb81a5..368cd39 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -1,34 +1,77 @@ -local multi, thread = require("multi"):init() +--[[ +MIT License +Copyright (c) 2023 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. +]] + +local multi, thread = require("multi"):init() -- Returns a handler that allows a user to interact with an object on another thread! -- Create on the thread that you want to interact with, send over the handle function multi:newProxy(obj) - local c = { - __index = function() - -- - end, - __newindex = function() - -- - end, - __call = function() - -- - end - } - c.name = multi.randomString(12) function c:init() if not multi.isMainThread then - c.send = multi:newSystemThreadedQueue(self.name.."_S"):init() - c.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() - c.ref = obj + local multi, thread = require("multi"):init() + local function check() + return self.send:pop() + end + self.send = multi:newSystemThreadedQueue(self.name.."_S"):init() + self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() + self.ref = obj + self.funcs = {} + for i, v in pairs(obj) do + if type(v) == "function" then + self.funcs[#self.funcs] = i + end + end + thread:newThread(function() + while true do + local data = thread.hold(check) + local func = table.remove(data, 1) + local ret = {self.ref[func](multi.unpack(data))} + table.insert(ret, 1, func) + self.recv:push(ret) + end + end) else GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD - c.send = THREAD.waitFor(self.name.."_S") - c.recv = THREAD.waitFor(self.name.."_R") + self.send = THREAD.waitFor(self.name.."_S") + self.recv = THREAD.waitFor(self.name.."_R") + for _,v in pairs(self.funcs) do + self[v] = thread:newFunction(function(...) + self.send:push({v, ...}) + return thread.hold(function() + local data = self.recv:peek() + if data[1] == v then + self.recv:pop() + thread.remove(data, 1) + return multi.unpack(data) + end + end) + end, true) + end end end @@ -54,26 +97,35 @@ function multi:newSystemThreadedProcessor(name, cores) c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) - c.jobqueue:registerFunction("__spawnThread__", function(name, func, ...) + local spawnThread = c.jobqueue:newFunction(function(name, func, ...) local multi, thread = require("multi"):init() - thread:newThread(name, func, ...) - return true - end) + print("hmm") + local proxy = multi:newProxy(thread:newThread(name, func, ...)) + multi:newTask(function() + proxy:init() + end) + return proxy + end, true) - c.jobqueue:registerFunction("__spawnTask__", function(obj, ...) + local spawnTask = c.jobqueue:newFunction(function(name, func, ...) local multi, thread = require("multi"):init() - multi[obj](multi, func) - return true - end) + local proxy = multi:newProxy(multi[obj](multi, func)) + multi:newTask(function() + proxy:init() + end) + return proxy + end, true) + + c.newLoop = thread:newFunction(function(self, func, notime) + return spawnTask("newLoop", func, notime):init() + end, true) + + c.newUpdater = thread:newFunction(function(self, skip, func) + return spawnTask("newUpdater", func, notime):init() + end, true) c.OnObjectCreated(function(proc, obj) - if obj.Type == multi.UPDATER then - local func = obj.OnUpdate:Remove()[1] - c.jobqueue:pushJob("__spawnTask__", "newUpdater", func) - elseif obj.Type == multi.LOOP then - local func = obj.OnLoop:Remove()[1] - c.jobqueue:pushJob("__spawnTask__", "newLoop", func) - else + if not(obj.Type == multi.UPDATER or obj.Type == multi.LOOP) then return multi.error("Invalid type!") end end) @@ -94,9 +146,9 @@ function multi:newSystemThreadedProcessor(name, cores) return self.Name end - function c:newThread(name, func, ...) - c.jobqueue:pushJob("__spawnThread__", name, func, ...) - end + c.newThread = thread:newFunction(function(self, name, func, ...) + return spawnThread(name, func, ...):init() + end, true) function c:newFunction(func, holdme) return c.jobqueue:newFunction(func, holdme) -- 2.43.0 From ab9e949b682da0eb3350db4999db2e7513a3c092 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 21 May 2023 09:43:44 -0400 Subject: [PATCH 038/117] newProxy implemented --- init.lua | 35 +++++--- integration/lanesManager/extensions.lua | 19 +++-- integration/sharedExtensions/init.lua | 106 +++++++++++++++--------- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/init.lua b/init.lua index 14c538c..26fbed6 100644 --- a/init.lua +++ b/init.lua @@ -568,14 +568,8 @@ function multi:Pause() multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") else self.Active=false - local loop = self.Parent.Mainloop - for i=1,#loop do - if loop[i] == self then - multi.PausedObjects[self] = true - table.remove(loop,i) - break - end - end + self._Act = self.Act + self.Act = empty_func end return self end @@ -589,8 +583,7 @@ function multi:Resume() end else if self.Active==false then - table.insert(self.Parent.Mainloop,self) - multi.PausedObjects[self] = nil + self.Act = self._Act self.Active=true end end @@ -668,6 +661,17 @@ function multi:newBase(ins) c.Act=function() end c.Parent=self c.creationTime = clock() + + function c:Pause() + c.Parent.Pause(self) + return self + end + + function c:Resume() + c.Parent.Resume(self) + return self + end + if ins then table.insert(self.Mainloop,ins,c) else @@ -817,6 +821,7 @@ function multi:newLoop(func, notime) return true end end + c.OnLoop = self:newConnection():fastMode() if func then @@ -1232,6 +1237,10 @@ function thread._Requests() end end +function thread.exec(func) + func() +end + function thread.sleep(n) thread._Requests() thread.getRunningThread().lastSleep = clock() @@ -1755,8 +1764,8 @@ co_status = { end r1=nil r2=nil r3=nil r4=nil r5=nil end, - ["normal"] = function(thd,ref) end, - ["running"] = function(thd,ref) 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 @@ -1929,7 +1938,7 @@ function multi:mainloopRef() for _D=#Loop,1,-1 do __CurrentTask = Loop[_D] ctask = __CurrentTask - ctask:Act() + if ctask then ctask:Act() end __CurrentProcess = self end end diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index cd0a73a..af9221b 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -107,7 +107,7 @@ function multi:newSystemThreadedJobQueue(n) return jid-1 end local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue + function c:newFunction(name, func, holup) -- This registers with the queue if type(name)=="function" then holup = func func = name @@ -116,7 +116,6 @@ function multi:newSystemThreadedJobQueue(n) nFunc = nFunc + 1 c:registerFunction(name,func) return thread:newFunction(function(...) - print("Called!") local id = c:pushJob(name,...) local link local rets @@ -130,19 +129,21 @@ function multi:newSystemThreadedJobQueue(n) return multi.unpack(rets) or multi.NIL end end) - end,holup), name + end, holup), name end thread:newThread("JobQueueManager",function() while true do local job = thread.hold(function() return queueReturn:pop() end) - local id = table.remove(job,1) - c.OnJobCompleted:Fire(id,multi.unpack(job)) + if job then + local id = table.remove(job,1) + c.OnJobCompleted:Fire(id,multi.unpack(job)) + end end end) for i=1,c.cores do - multi:newSystemThread("SystemThreadedJobQueue",function(queue) + multi:newSystemThread("SystemThreadedJobQueue_"..multi.randomString(4),function(queue) local multi, thread = require("multi"):init() local idle = os.clock() local clock = os.clock @@ -176,7 +177,7 @@ function multi:newSystemThreadedJobQueue(n) end end end - end) + end).OnError(print) thread:newThread("IdleHandler",function() while true do thread.hold(function() @@ -184,9 +185,9 @@ function multi:newSystemThreadedJobQueue(n) end) THREAD.sleep(.01) end - end) + end).OnError(print) multi:mainloop() - end,i).priority = thread.Priority_Core + end,i).OnError(print) end return c end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 368cd39..30f6e37 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -26,52 +26,84 @@ local multi, thread = require("multi"):init() -- Returns a handler that allows a user to interact with an object on another thread! -- Create on the thread that you want to interact with, send over the handle -function multi:newProxy(obj) + +function multi:chop(obj) + local multi, thread = require("multi"):init() + list = {[0] = multi.randomString(12)} + _G[list[0]] = obj + for i,v in pairs(obj) do + if type(v) == "function" then + list[#list+1] = i + end + end + return list +end + +function multi:newProxy(list) + local c = {} + c.name = multi.randomString(12) function c:init() - if not multi.isMainThread then + local multi, thread = nil, nil + if THREAD_NAME then local multi, thread = require("multi"):init() local function check() return self.send:pop() end self.send = multi:newSystemThreadedQueue(self.name.."_S"):init() self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() - self.ref = obj - self.funcs = {} - for i, v in pairs(obj) do - if type(v) == "function" then - self.funcs[#self.funcs] = i - end - end + self.funcs = list thread:newThread(function() while true do local data = thread.hold(check) local func = table.remove(data, 1) - local ret = {self.ref[func](multi.unpack(data))} + local sref = table.remove(data, 1) + local ret + if sref then + ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} + else + ret = {_G[list[0]][func](multi.unpack(data))} + end + if ret[1] == _G[list[0]] then + -- We cannot return itself, that return can contain bad values. + ret[1] = {_self_ref_ = true} + end table.insert(ret, 1, func) self.recv:push(ret) end - end) + end).OnError(print) + return self else + local multi, thread = require("multi"):init() + local me = self GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD self.send = THREAD.waitFor(self.name.."_S") self.recv = THREAD.waitFor(self.name.."_R") for _,v in pairs(self.funcs) do - self[v] = thread:newFunction(function(...) - self.send:push({v, ...}) + self[v] = thread:newFunction(function(self,...) + if self == me then + me.send:push({v, true, ...}) + else + me.send:push({v, false, ...}) + end return thread.hold(function() - local data = self.recv:peek() - if data[1] == v then - self.recv:pop() - thread.remove(data, 1) + local data = me.recv:peek() + if data and data[1] == v then + me.recv:pop() + table.remove(data, 1) + if type(data[1]) == "table" and data[1]._self_ref_ then + -- So if we get a self return as a return, we should return the proxy! + data[1] = me + end return multi.unpack(data) end end) end, true) end + return self end end @@ -80,7 +112,7 @@ end function multi:newSystemThreadedProcessor(name, cores) - local name = name or "STP_"multi.randomString(4) -- set a random name if none was given. + local name = name or "STP_"..multi.randomString(4) -- set a random name if none was given. local autoscale = autoscale or false -- Will scale up the number of cores that the process uses. local c = {} @@ -97,32 +129,30 @@ function multi:newSystemThreadedProcessor(name, cores) c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) - local spawnThread = c.jobqueue:newFunction(function(name, func, ...) + c.spawnThread = c.jobqueue:newFunction("__spawnThread__", function(name, func, ...) local multi, thread = require("multi"):init() - print("hmm") - local proxy = multi:newProxy(thread:newThread(name, func, ...)) - multi:newTask(function() - proxy:init() - end) + local proxy = multi:newProxy(multi:chop(thread:newThread(name, func, ...))):init() return proxy end, true) - local spawnTask = c.jobqueue:newFunction(function(name, func, ...) + c.spawnTask = c.jobqueue:newFunction("__spawnTask__", function(obj, func, ...) local multi, thread = require("multi"):init() - local proxy = multi:newProxy(multi[obj](multi, func)) - multi:newTask(function() - proxy:init() - end) + local obj = multi[obj](multi, func, ...) + local proxy = multi:newProxy(multi:chop(obj)):init() return proxy end, true) - c.newLoop = thread:newFunction(function(self, func, notime) - return spawnTask("newLoop", func, notime):init() - end, true) + function c:newLoop(func, notime) + return self.spawnTask("newLoop", func, notime):init() + end - c.newUpdater = thread:newFunction(function(self, skip, func) - return spawnTask("newUpdater", func, notime):init() - end, true) + function c:newTLoop(func, time) + return self.spawnTask("newTLoop", func, time):init() + end + + function c:newUpdater(skip, func) + return self.spawnTask("newUpdater", func, notime):init() + end c.OnObjectCreated(function(proc, obj) if not(obj.Type == multi.UPDATER or obj.Type == multi.LOOP) then @@ -146,9 +176,9 @@ function multi:newSystemThreadedProcessor(name, cores) return self.Name end - c.newThread = thread:newFunction(function(self, name, func, ...) - return spawnThread(name, func, ...):init() - end, true) + function c:newThread(name, func, ...) + return self.spawnThread(name, func, ...):init() + end function c:newFunction(func, holdme) return c.jobqueue:newFunction(func, holdme) -- 2.43.0 From de9b08fa2e4412f67cbef85e2dcd0b5f47936137 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 24 May 2023 23:17:18 -0400 Subject: [PATCH 039/117] Proxies work with connections now :D --- docs/changes.md | 27 +++++++++ init.lua | 19 ++++-- integration/sharedExtensions/init.lua | 86 +++++++++++++++++++-------- tests/threadtests.lua | 1 + 4 files changed, 103 insertions(+), 30 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 3b49ce0..9896517 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -74,6 +74,33 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- multi:chop(obj) -- We cannot directly interact with a local object on lanes, so we chop the object and set some globals on the thread side. Should use like: `mulit:newProxy(multi:chop(multi:newThread(function() ... end)))` +- multi:newProxy(ChoppedObject) -- Creates a proxy object that allows you to interact with an object on a thread + + **Note:** Objects with __index=table do not work with the proxy object! The object must have that function in it's own table for proxy to pick it up and have it work properly. Connections on a proxy allow you to subscribe to an event on the thread side of things. The function that is being connected to happens on the thread! +- multi:newSystemThreadedProcessor(name) -- Works like newProcessor(name) each object created returns a proxy object that you can use to interact with the objects on the system thread + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor("Test STP") + + alarm = stp:newAlarm(3) + + alarm.OnRing:Connect(function(alarm) + print("Hmm...", THREAD_NAME) + end) + ``` + Output: + ``` + Hmm... SystemThreadedJobQueue_A5tp + ``` + Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. + + There are currently limitations to proxies. Connection proxy do not receive events on the non thread side. So connection metamethods do not work! Also you cannot use the proxy holds. For full features develop using a systemThreadedConnection() which does support all connection features. I planned on using STCs originally, but decided not to because I didn't want proxy objects to affect the non thread side of things! Subscribing to an event that isn't on the thread being proxied would cause the object to no longer be a proxy. + - thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. - multi.Processors:getHandler() -- returns the thread handler for a process - multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! diff --git a/init.lua b/init.lua index 26fbed6..2c38a88 100644 --- a/init.lua +++ b/init.lua @@ -319,7 +319,7 @@ function multi:newConnection(protect,func,kill) if conn and not conn.lock then conn.lock = function() end for i = 1, #fast do - if conn.ref == fast[i] then + if fast[conn.ref] == fast[i] then fast[i] = conn.lock return self end @@ -334,7 +334,7 @@ function multi:newConnection(protect,func,kill) if conn and conn.lock then for i = 1, #fast do if conn.lock == fast[i] then - fast[i] = conn.ref + fast[i] = fast[conn.ref] return self end end @@ -376,7 +376,7 @@ function multi:newConnection(protect,func,kill) function c:Unconnect(conn) for i = 1, #fast do - if conn.ref == fast[i] then + if fast[conn.ref] == fast[i] then return table.remove(fast, i), i end end @@ -445,7 +445,8 @@ function multi:newConnection(protect,func,kill) rawset(t,k,v) end, }) - temp.ref = func + temp.ref = multi.randomString(24) + fast[temp.ref] = func temp.name = name if self.rawadd then self.rawadd = false @@ -718,7 +719,7 @@ function multi:newTimer() end --Core Actors -function multi:newEvent(task) +function multi:newEvent(task, func) local c=self:newBase() c.Type=multi.EVENT local task = task or function() end @@ -736,6 +737,9 @@ function multi:newEvent(task) return self end c.OnEvent = self:newConnection():fastMode() + if func then + c.OnEvent(func) + end self:setPriority("core") c:setName(c.Type) self:create(c) @@ -768,7 +772,7 @@ function multi:newUpdater(skip, func) return c end -function multi:newAlarm(set) +function multi:newAlarm(set, func) local c=self:newBase() c.Type=multi.ALARM c:setPriority("Low") @@ -801,6 +805,9 @@ function multi:newAlarm(set) self.Parent.Pause(self) return self end + if func then + c.OnRing(func) + end c:setName(c.Type) self:create(c) return c diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 30f6e37..77df404 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -29,11 +29,20 @@ local multi, thread = require("multi"):init() function multi:chop(obj) local multi, thread = require("multi"):init() - list = {[0] = multi.randomString(12)} + local list = {[0] = multi.randomString(12)} _G[list[0]] = obj for i,v in pairs(obj) do if type(v) == "function" then - list[#list+1] = i + table.insert(list, i) + elseif type(v) == "table" and v.Type == multi.CONNECTOR then + table.insert(list, {i, multi:newProxy(multi:chop(v)):init()}) + -- local stc = "stc_"..list[0].."_"..i + -- list[-1][#list[-1] + 1] = {i, stc} + -- list[#list+1] = i + -- obj[stc] = multi:newSystemThreadedConnection(stc):init() + -- obj["_"..i.."_"] = function(...) + -- return obj[stc](...) + -- end end end return list @@ -55,6 +64,7 @@ function multi:newProxy(list) self.send = multi:newSystemThreadedQueue(self.name.."_S"):init() self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() self.funcs = list + self.conns = list[-1] thread:newThread(function() while true do local data = thread.hold(check) @@ -65,10 +75,15 @@ function multi:newProxy(list) ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} else ret = {_G[list[0]][func](multi.unpack(data))} - end - if ret[1] == _G[list[0]] then - -- We cannot return itself, that return can contain bad values. - ret[1] = {_self_ref_ = true} + end + for i = 1,#ret do + if type(ret[i]) == "table" and getmetatable(ret[i]) then + setmetatable(ret[i],{}) -- remove that metatable, we do not need it on the other side! + end + if ret[i] == _G[list[0]] then + -- We cannot return itself, that return can contain bad values. + ret[i] = {_self_ref_ = true} + end end table.insert(ret, 1, func) self.recv:push(ret) @@ -83,25 +98,36 @@ function multi:newProxy(list) self.send = THREAD.waitFor(self.name.."_S") self.recv = THREAD.waitFor(self.name.."_R") for _,v in pairs(self.funcs) do - self[v] = thread:newFunction(function(self,...) - if self == me then - me.send:push({v, true, ...}) - else - me.send:push({v, false, ...}) - end - return thread.hold(function() - local data = me.recv:peek() - if data and data[1] == v then - me.recv:pop() - table.remove(data, 1) - if type(data[1]) == "table" and data[1]._self_ref_ then - -- So if we get a self return as a return, we should return the proxy! - data[1] = me - end - return multi.unpack(data) + if type(v) == "table" then + -- We got a connection + v[2]:init() + + --setmetatable(v[2],getmetatable(multi:newConnection())) + + self[v[1]] = v[2] + else + self[v] = thread:newFunction(function(self,...) + if self == me then + me.send:push({v, true, ...}) + else + me.send:push({v, false, ...}) end - end) - end, true) + return thread.hold(function() + local data = me.recv:peek() + if data and data[1] == v then + me.recv:pop() + table.remove(data, 1) + for i=1,#data do + if type(data[i]) == "table" and data[i]._self_ref_ then + -- So if we get a self return as a return, we should return the proxy! + data[i] = me + end + end + return multi.unpack(data) + end + end) + end, true) + end end return self end @@ -154,6 +180,18 @@ function multi:newSystemThreadedProcessor(name, cores) return self.spawnTask("newUpdater", func, notime):init() end + function c:newEvent(task, func) + return self.spawnTask("newEvent", task, func):init() + end + + function c:newAlarm(set, func) + return self.spawnTask("newAlarm", set, func):init() + end + + function c:newStep(start, reset, count, skip) + return self.spawnTask("newStep", start, reset, count, skip):init() + end + c.OnObjectCreated(function(proc, obj) if not(obj.Type == multi.UPDATER or obj.Type == multi.LOOP) then return multi.error("Invalid type!") diff --git a/tests/threadtests.lua b/tests/threadtests.lua index f7126d1..a3024fc 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -151,6 +151,7 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end).OnError(multi.error) + connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() a=0 connOut(function(arg) -- 2.43.0 From af38ebbb81c31c6cd51877f7fbfe9634040f5571 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 24 May 2023 23:31:07 -0400 Subject: [PATCH 040/117] Added tstep to STP, updated changes.md --- docs/changes.md | 15 +++++++++++++++ integration/sharedExtensions/init.lua | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/docs/changes.md b/docs/changes.md index 9896517..d8939f4 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -101,6 +101,21 @@ Added There are currently limitations to proxies. Connection proxy do not receive events on the non thread side. So connection metamethods do not work! Also you cannot use the proxy holds. For full features develop using a systemThreadedConnection() which does support all connection features. I planned on using STCs originally, but decided not to because I didn't want proxy objects to affect the non thread side of things! Subscribing to an event that isn't on the thread being proxied would cause the object to no longer be a proxy. + This event is subscribed to on the proxy threads side of things! + + Currently supporting: + - STP:newLoop(...) + - STP:newTLoop(...) + - STP:newUpdater(...) + - STP:newEvent(...) + - STP:newAlarm(...) + - STP:newStep(...) + - STP:newTStep(...) + - STP:newThread(...) + - STP:newFunction(...) + + If you would like to connect to a "STP Connection" object you can do so in a STP Function using hold and connect to the function OnReturn event or have the function wait (When in a coroutine it will only pause execution for that coroutine(multi:newThread(...))). The function is still runs on the Thread that the STP is running on. There is no guarantee that the function will run on the same thread each time, unlike with the multi objects/cothreads. Those stay on the systhread they are created on. + - thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. - multi.Processors:getHandler() -- returns the thread handler for a process - multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 77df404..8bdda94 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -192,6 +192,10 @@ function multi:newSystemThreadedProcessor(name, cores) return self.spawnTask("newStep", start, reset, count, skip):init() end + function c:newTStep(start ,reset, count, set) + return self.spawnTask("newTStep", start, reset, count, set):init() + end + c.OnObjectCreated(function(proc, obj) if not(obj.Type == multi.UPDATER or obj.Type == multi.LOOP) then return multi.error("Invalid type!") -- 2.43.0 From 5c03b342904334d348c2b67a876719f224724481 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 27 May 2023 00:10:21 -0400 Subject: [PATCH 041/117] thread.hold(proxy.conn) --- docs/changes.md | 29 +++- integration/lanesManager/extensions.lua | 43 +++-- integration/lanesManager/init.lua | 3 + integration/loveManager/extensions.lua | 1 + integration/loveManager/init.lua | 1 + integration/lovrManager/extensions.lua | 1 + integration/luvitManager.lua | 2 +- integration/pseudoManager/init.lua | 1 + integration/sharedExtensions/init.lua | 218 ++++++++++++++++++++---- 9 files changed, 249 insertions(+), 50 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index d8939f4..be715a8 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -74,6 +74,9 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- multi:newTargetedFunction(ID, proc, name, func, holup) -- This is used internally to handle thread.hold(proxy.conn) +- proxy.getThreadID() -- Returns the threadID of the thread that the proxy is running in +- proxy:getUniqueName() -- Gets the special name that identifies the object on the thread the proxy refers to - multi:chop(obj) -- We cannot directly interact with a local object on lanes, so we chop the object and set some globals on the thread side. Should use like: `mulit:newProxy(multi:chop(multi:newThread(function() ... end)))` - multi:newProxy(ChoppedObject) -- Creates a proxy object that allows you to interact with an object on a thread @@ -99,7 +102,7 @@ Added ``` Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. - There are currently limitations to proxies. Connection proxy do not receive events on the non thread side. So connection metamethods do not work! Also you cannot use the proxy holds. For full features develop using a systemThreadedConnection() which does support all connection features. I planned on using STCs originally, but decided not to because I didn't want proxy objects to affect the non thread side of things! Subscribing to an event that isn't on the thread being proxied would cause the object to no longer be a proxy. + There are currently limitations to proxies. Connection proxy do not receive events on the non thread side. So connection metamethods do not work! thread.hold(proxy.conn) does work! The backend to get this to work was annoying :P This event is subscribed to on the proxy threads side of things! @@ -114,7 +117,28 @@ Added - STP:newThread(...) - STP:newFunction(...) - If you would like to connect to a "STP Connection" object you can do so in a STP Function using hold and connect to the function OnReturn event or have the function wait (When in a coroutine it will only pause execution for that coroutine(multi:newThread(...))). The function is still runs on the Thread that the STP is running on. There is no guarantee that the function will run on the same thread each time, unlike with the multi objects/cothreads. Those stay on the systhread they are created on. + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor() + + alarm = stp:newAlarm(3) + + alarm.OnRing:Connect(function(alarm) + print("Hmm...", THREAD_NAME) + end) + + thread:newThread(function() + print("Holding...") + local a = thread.hold(alarm.OnRing) -- it works :D + print("We work!") + end) + + multi:mainloop() + ``` - thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. - multi.Processors:getHandler() -- returns the thread handler for a process @@ -312,6 +336,7 @@ Removed Fixed --- +- multi.isMainThread was not properly handled in each integration. This has been resolved. - Issue with pseudo threading env's being messed up. Required removal of getName and getID! - connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! - multi:reallocate(processor, index) has been fixed to work with the current changes of the library. diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index af9221b..596f5f6 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -24,7 +24,7 @@ SOFTWARE. local multi, thread = require("multi"):init() if not (GLOBAL and THREAD) then - GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD + GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD else lanes = require("lanes") end @@ -34,19 +34,29 @@ function multi:newSystemThreadedQueue(name) local c = {} c.Name = name 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:peek() return self.linda:get("Q") end + function c:init() return self end - GLOBAL[name or "_"] = c + + if multi.isMainThread then + multi.integration.GLOBAL[name] = c + else + GLOBAL[name] = c + end + return c end @@ -56,10 +66,6 @@ function multi:newSystemThreadedTable(name) c.link = lanes.linda() c.Name = name - -- function c:getIndex() - -- return c.link:dump() - -- end - function c:init() return self end @@ -73,7 +79,12 @@ function multi:newSystemThreadedTable(name) end }) - GLOBAL[name or "_"] = c + if multi.isMainThread then + multi.integration.GLOBAL[name] = c + else + GLOBAL[name] = c + end + return c end @@ -90,9 +101,9 @@ function multi:newSystemThreadedJobQueue(n) function c:isEmpty() return queueJob:peek()==nil end - function c:doToAll(func) + function c:doToAll(func,...) for i=1,c.cores do - doAll:push{ID,func} + doAll:push{ID,func,...} end ID = ID + 1 return self @@ -143,11 +154,12 @@ function multi:newSystemThreadedJobQueue(n) end end) for i=1,c.cores do - multi:newSystemThread("SystemThreadedJobQueue_"..multi.randomString(4),function(queue) + multi:newSystemThread("STJQ_"..multi.randomString(8),function(queue) local multi, thread = require("multi"):init() local idle = os.clock() local clock = os.clock local ref = 0 + _G["__QR"] = queueReturn setmetatable(_G,{__index = funcs}) thread:newThread("JobHandler",function() while true do @@ -170,9 +182,10 @@ function multi:newSystemThreadedJobQueue(n) end) if dat then if dat[1]>ref then + ref = table.remove(dat, 1) + func = table.remove(dat, 1) idle = clock() - ref = dat[1] - dat[2]() + func(unpack(dat)) doAll:pop() end end @@ -324,7 +337,11 @@ function multi:newSystemThreadedConnection(name) return self end - GLOBAL[name] = c + if multi.isMainThread then + multi.integration.GLOBAL[name] = c + else + GLOBAL[name] = c + end return c end \ No newline at end of file diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index ebfb569..e4df31a 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -131,6 +131,9 @@ function multi.InitSystemThreadErrorHandler() while true do thread.yield() _,data = __ConsoleLinda:receive(0, "Q") + if data then + print(data[1]) + end for i = #threads, 1, -1 do temp = threads[i] status = temp.thread.status diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 10e8e17..70bad7a 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -162,6 +162,7 @@ function multi:newSystemThreadedJobQueue(n) local lastProc = clock() local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} + _G["__QR"] = queueReturn setmetatable(_G,{__index = funcs}) thread:newThread("startUp",function() while true do diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 2bb596b..5300a92 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -66,6 +66,7 @@ multi.integration = {} local THREAD = require("multi.integration.loveManager.threads") local GLOBAL = THREAD.getGlobal() local THREAD_ID = 1 +multi.isMainThread = true function multi:newSystemThread(name, func, ...) local c = {} diff --git a/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua index 232183a..a4f231a 100644 --- a/integration/lovrManager/extensions.lua +++ b/integration/lovrManager/extensions.lua @@ -152,6 +152,7 @@ function multi:newSystemThreadedJobQueue(n) local lastProc = clock() local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local registry = {} + _G["__QR"] = queueReturn setmetatable(_G,{__index = funcs}) thread:newThread("startUp",function() while true do diff --git a/integration/luvitManager.lua b/integration/luvitManager.lua index 0f53cb9..c1e8dcb 100644 --- a/integration/luvitManager.lua +++ b/integration/luvitManager.lua @@ -35,7 +35,7 @@ local function _INIT(luvitThread, timer) end -- Step 1 get setup threads on luvit... Sigh how do i even... local multi, thread = require("multi").init() - isMainThread = true + multi.isMainThread = true function multi:canSystemThread() return true end diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 7e41344..30879f3 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -31,6 +31,7 @@ if multi.integration then end } end +multi.isMainThread = true local activator = require("multi.integration.pseudoManager.threads") local GLOBAL, THREAD = activator.init(thread) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 8bdda94..86f292c 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -35,16 +35,27 @@ function multi:chop(obj) if type(v) == "function" then table.insert(list, i) elseif type(v) == "table" and v.Type == multi.CONNECTOR then - table.insert(list, {i, multi:newProxy(multi:chop(v)):init()}) - -- local stc = "stc_"..list[0].."_"..i - -- list[-1][#list[-1] + 1] = {i, stc} - -- list[#list+1] = i - -- obj[stc] = multi:newSystemThreadedConnection(stc):init() - -- obj["_"..i.."_"] = function(...) - -- return obj[stc](...) - -- end + v.getThreadID = function() -- Special function we are adding + return THREAD_ID + end + + v.getUniqueName = function(self) + return self.__link_name + end + + local l = multi:chop(v) + v.__link_name = l[0] + v.__name = i + + table.insert(list, {i, multi:newProxy(l):init()}) end end + table.insert(list, "isConnection") + if obj.Type == multi.CONNECTOR then + obj.isConnection = function() return true end + else + obj.isConnection = function() return false end + end return list end @@ -97,15 +108,14 @@ function multi:newProxy(list) THREAD = multi.integration.THREAD self.send = THREAD.waitFor(self.name.."_S") self.recv = THREAD.waitFor(self.name.."_R") + self.Type = multi.PROXY for _,v in pairs(self.funcs) do if type(v) == "table" then - -- We got a connection v[2]:init() - - --setmetatable(v[2],getmetatable(multi:newConnection())) - self[v[1]] = v[2] + v[2].Parent = self else + lastObj = self self[v] = thread:newFunction(function(self,...) if self == me then me.send:push({v, true, ...}) @@ -119,7 +129,6 @@ function multi:newProxy(list) table.remove(data, 1) for i=1,#data do if type(data[i]) == "table" and data[i]._self_ref_ then - -- So if we get a self return as a return, we should return the proxy! data[i] = me end end @@ -136,9 +145,40 @@ function multi:newProxy(list) return c end -function multi:newSystemThreadedProcessor(name, cores) +multi.PROXY = "proxy" - local name = name or "STP_"..multi.randomString(4) -- set a random name if none was given. +local targets = {} + +local nFunc = 0 +function multi:newTargetedFunction(ID, proc, name, func, holup) -- This registers with the queue + if type(name)=="function" then + holup = func + func = name + name = "JQ_TFunc_"..nFunc + end + nFunc = nFunc + 1 + proc.jobqueue:registerFunction(name, func) + return thread:newFunction(function(...) + local id = proc:pushJob(ID, name, ...) + local link + local rets + link = proc.jobqueue.OnJobCompleted(function(jid,...) + if id==jid then + rets = {...} + end + end) + return thread.hold(function() + if rets then + return multi.unpack(rets) or multi.NIL + end + end) + end, holup), name +end + +local jid = -1 +function multi:newSystemThreadedProcessor(cores) + + local name = "STP_"..multi.randomString(4) -- set a random name if none was given. local autoscale = autoscale or false -- Will scale up the number of cores that the process uses. local c = {} @@ -154,54 +194,123 @@ function multi:newSystemThreadedProcessor(name, cores) c.OnObjectCreated = multi:newConnection() c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) - + c.targetedQueue = multi:newSystemThreadedQueue(name.."_target"):init() + + c.jobqueue:registerFunction("enable_targets",function(name) + local multi, thread = require("multi"):init() + local qname = THREAD_NAME .. "_t_queue" + local targetedQueue = THREAD.waitFor(name):init() + local tjq = multi:newSystemThreadedQueue(qname):init() + targetedQueue:push({tonumber(THREAD_ID), qname}) + multi:newThread("TargetedJobHandler", function() + local queueReturn = _G["__QR"] + while true do + local dat = thread.hold(function() + return tjq:pop() + end) + if dat then + thread:newThread("test",function() + local name = table.remove(dat, 1) + local jid = table.remove(dat, 1) + local args = table.remove(dat, 1) + queueReturn:push{jid, _G[name](multi.unpack(args)), queue} + end).OnError(multi.error) + end + end + end).OnError(multi.error) + end) + + function c:pushJob(ID, name, ...) + targets[ID]:push{name, jid, {...}} + jid = jid - 1 + return jid + 1 + end + + c.jobqueue:doToAll(function(name) + enable_targets(name) + end, name.."_target") + + local count = 0 + while count < c.cores do + local dat = c.targetedQueue:pop() + if dat then + targets[dat[1]] = multi.integration.THREAD.waitFor(dat[2]):init() + count = count + 1 + end + end + + c.jobqueue:registerFunction("packObj",function(obj) + local multi, thread = require("multi"):init() + obj.getThreadID = function() -- Special function we are adding + return THREAD_ID + end + + obj.getUniqueName = function(self) + return self.__link_name + end + + local list = multi:chop(obj) + obj.__link_name = list[0] + + local proxy = multi:newProxy(list):init() + + return proxy + end) + c.spawnThread = c.jobqueue:newFunction("__spawnThread__", function(name, func, ...) local multi, thread = require("multi"):init() - local proxy = multi:newProxy(multi:chop(thread:newThread(name, func, ...))):init() - return proxy + local obj = thread:newThread(name, func, ...) + return packObj(obj) end, true) c.spawnTask = c.jobqueue:newFunction("__spawnTask__", function(obj, func, ...) local multi, thread = require("multi"):init() local obj = multi[obj](multi, func, ...) - local proxy = multi:newProxy(multi:chop(obj)):init() - return proxy + return packObj(obj) end, true) function c:newLoop(func, notime) - return self.spawnTask("newLoop", func, notime):init() + proxy = self.spawnTask("newLoop", func, notime):init() + proxy.__proc = self + return proxy end function c:newTLoop(func, time) - return self.spawnTask("newTLoop", func, time):init() + proxy = self.spawnTask("newTLoop", func, time):init() + proxy.__proc = self + return proxy end function c:newUpdater(skip, func) - return self.spawnTask("newUpdater", func, notime):init() + proxy = self.spawnTask("newUpdater", func, notime):init() + proxy.__proc = self + return proxy end function c:newEvent(task, func) - return self.spawnTask("newEvent", task, func):init() + proxy = self.spawnTask("newEvent", task, func):init() + proxy.__proc = self + return proxy end function c:newAlarm(set, func) - return self.spawnTask("newAlarm", set, func):init() + proxy = self.spawnTask("newAlarm", set, func):init() + proxy.__proc = self + return proxy end function c:newStep(start, reset, count, skip) - return self.spawnTask("newStep", start, reset, count, skip):init() + proxy = self.spawnTask("newStep", start, reset, count, skip):init() + proxy.__proc = self + return proxy end function c:newTStep(start ,reset, count, set) - return self.spawnTask("newTStep", start, reset, count, set):init() + proxy = self.spawnTask("newTStep", start, reset, count, set):init() + proxy.__proc = self + return proxy end - c.OnObjectCreated(function(proc, obj) - if not(obj.Type == multi.UPDATER or obj.Type == multi.LOOP) then - return multi.error("Invalid type!") - end - end) - function c:getHandler() -- Not needed end @@ -219,7 +328,9 @@ function multi:newSystemThreadedProcessor(name, cores) end function c:newThread(name, func, ...) - return self.spawnThread(name, func, ...):init() + proxy = self.spawnThread(name, func, ...):init() + proxy.__proc = self + return proxy end function c:newFunction(func, holdme) @@ -249,3 +360,42 @@ function multi:newSystemThreadedProcessor(name, cores) return c end +-- Modify thread.hold to handle proxies +local thread_ref = thread.hold +function thread.hold(n, opt) + if type(n) == "table" and n.Type == multi.PROXY and n.isConnection() then + local ready = false + local args + local id = n.getThreadID() + local name = n:getUniqueName() + local func = multi:newTargetedFunction(id, n.Parent.__proc, "conn_"..multi.randomString(8), function(_name) + local multi, thread = require("multi"):init() + local obj = _G[_name] + local rets = {thread.hold(obj)} + for i,v in pairs(rets) do + if v.Type then + rets[i] = {_self_ref_ = "parent"} + end + end + return unpack(rets) + end) + func(name).OnReturn(function(...) + ready = true + args = {...} + end) + local ret = {thread_ref(function() + if ready then + return multi.unpack(args) or multi.NIL + end + end, opt)} + for i,v in pairs(ret) do + if type(v) == "table" and v._self_ref_ == "parent" then + ret[i] = n.Parent + end + end + return unpack(ret) + else + return thread_ref(n, opt) + end +end + -- 2.43.0 From 06c31bee85873a143b38937738a1d8a831d1052a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 27 May 2023 23:57:58 -0400 Subject: [PATCH 042/117] Clean up connection events when holding, working on scheduling tasks/threads to system threaded processors --- init.lua | 4 +- integration/loveManager/extensions.lua | 6 +-- integration/sharedExtensions/init.lua | 57 ++++++++++++++++++++------ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/init.lua b/init.lua index 2c38a88..1d45f14 100644 --- a/init.lua +++ b/init.lua @@ -1261,9 +1261,11 @@ local function conn_test(conn) ready = true args = {...} end - conn(func) + + local ref = conn(func) return function() if ready then + conn:Unconnect(ref) return multi.unpack(args) or multi.NIL end end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 70bad7a..8740018 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -231,7 +231,7 @@ function multi:newSystemThreadedConnection(name) function self:Fire(...) local args = {...} - if self.CID == THREAD.getID() then -- Host Call + if self.CID == THREAD_ID then -- Host Call for _, link in pairs(self.links) do love.thread.getChannel(link):push{self.TRIG, args} end @@ -246,7 +246,7 @@ function multi:newSystemThreadedConnection(name) self.proxy_conn = multi:newConnection() local mt = getmetatable(self.proxy_conn) setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) - if self.CID == THREAD.getID() then return self end + if self.CID == THREAD_ID then return self end thread:newThread("STC_CONN_MAN" .. self.Name,function() local item local string_self_ref = "LSF_" .. multi.randomString(16) @@ -284,7 +284,7 @@ function multi:newSystemThreadedConnection(name) end return r end - c.CID = THREAD.getID() + c.CID = THREAD_ID c.Name = name c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. -- Locals will only live in the thread that creates the original object diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 86f292c..0e7ba6e 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -141,7 +141,6 @@ function multi:newProxy(list) return self end end - return c end @@ -185,6 +184,7 @@ function multi:newSystemThreadedProcessor(cores) setmetatable(c,{__index = multi}) + c.threads = {} c.cores = cores or 8 c.Name = name c.Mainloop = {} @@ -196,7 +196,7 @@ function multi:newSystemThreadedProcessor(cores) c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) c.targetedQueue = multi:newSystemThreadedQueue(name.."_target"):init() - c.jobqueue:registerFunction("enable_targets",function(name) + c.jobqueue:registerFunction("STP_enable_targets",function(name) local multi, thread = require("multi"):init() local qname = THREAD_NAME .. "_t_queue" local targetedQueue = THREAD.waitFor(name):init() @@ -220,6 +220,14 @@ function multi:newSystemThreadedProcessor(cores) end).OnError(multi.error) end) + c.jobqueue:registerFunction("STP_GetThreadCount",function() + return {"t_thread", _G["__THREADS"]} + end) + + c.jobqueue:registerFunction("STP_GetTaskCount",function() + return {"t_task", _G["__TASKS"]} + end) + function c:pushJob(ID, name, ...) targets[ID]:push{name, jid, {...}} jid = jid - 1 @@ -227,7 +235,9 @@ function multi:newSystemThreadedProcessor(cores) end c.jobqueue:doToAll(function(name) - enable_targets(name) + STP_enable_targets(name) + _G["__THREADS"] = 0 + _G["__TASKS"] = 0 end, name.."_target") local count = 0 @@ -235,13 +245,14 @@ function multi:newSystemThreadedProcessor(cores) local dat = c.targetedQueue:pop() if dat then targets[dat[1]] = multi.integration.THREAD.waitFor(dat[2]):init() + table.insert(c.proc_list, dat[1]) -- Add thread_id to proc list count = count + 1 end end c.jobqueue:registerFunction("packObj",function(obj) local multi, thread = require("multi"):init() - obj.getThreadID = function() -- Special function we are adding + obj.getThreadID = function() -- Special functions we are adding return THREAD_ID end @@ -260,12 +271,14 @@ function multi:newSystemThreadedProcessor(cores) c.spawnThread = c.jobqueue:newFunction("__spawnThread__", function(name, func, ...) local multi, thread = require("multi"):init() local obj = thread:newThread(name, func, ...) + _G["__THREADS"] = _G["__THREADS"] + 1 return packObj(obj) end, true) c.spawnTask = c.jobqueue:newFunction("__spawnTask__", function(obj, func, ...) local multi, thread = require("multi"):init() local obj = multi[obj](multi, func, ...) + _G["__TASKS"] = _G["__TASKS"] + 1 return packObj(obj) end, true) @@ -312,11 +325,11 @@ function multi:newSystemThreadedProcessor(cores) end function c:getHandler() - -- Not needed + return function() end -- return empty function end function c:getThreads() - -- We might want to keep track of the number of threads we have + return self.threads end function c:getFullName() @@ -330,6 +343,7 @@ function multi:newSystemThreadedProcessor(cores) function c:newThread(name, func, ...) proxy = self.spawnThread(name, func, ...):init() proxy.__proc = self + table.insert(self.threads, proxy) return proxy end @@ -338,25 +352,40 @@ function multi:newSystemThreadedProcessor(cores) end function c.run() - -- Not needed + return self end function c.isActive() - -- + return true end function c.Start() - -- + return self end function c.Stop() - -- + return self end function c:Destroy() - -- + return false end + -- Special functions + c.getLeastLoaded = thread:newFunction(function(self) + local loads = {} + local jid = {} + for i,v in pairs(self.proc_list) do + table.insert(jid, self:pushJob(v, "STP_GetThreadCount")) + table.insert(jid, self:pushJob(v, "STP_GetTaskCount")) + end + + end) + + c.jobqueue.OnJobCompleted(function(id, ...) + -- + end) + return c end @@ -379,10 +408,14 @@ function thread.hold(n, opt) end return unpack(rets) end) - func(name).OnReturn(function(...) + local conn + local handle = func(name) + conn = handle.OnReturn(function(...) ready = true args = {...} + handle.OnReturn:Unconnect(conn) end) + local ret = {thread_ref(function() if ready then return multi.unpack(args) or multi.NIL -- 2.43.0 From 3effcb738426a35f30a3c7913a9245eb131b446a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 28 May 2023 00:37:17 -0400 Subject: [PATCH 043/117] Getting loads of processors implemented --- integration/sharedExtensions/init.lua | 83 ++++++++++++++++++--------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 0e7ba6e..188efcd 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -79,25 +79,27 @@ function multi:newProxy(list) thread:newThread(function() while true do local data = thread.hold(check) - local func = table.remove(data, 1) - local sref = table.remove(data, 1) - local ret - if sref then - ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} - else - ret = {_G[list[0]][func](multi.unpack(data))} - end - for i = 1,#ret do - if type(ret[i]) == "table" and getmetatable(ret[i]) then - setmetatable(ret[i],{}) -- remove that metatable, we do not need it on the other side! + if data then + local func = table.remove(data, 1) + local sref = table.remove(data, 1) + local ret + if sref then + ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} + else + ret = {_G[list[0]][func](multi.unpack(data))} end - if ret[i] == _G[list[0]] then - -- We cannot return itself, that return can contain bad values. - ret[i] = {_self_ref_ = true} + for i = 1,#ret do + if type(ret[i]) == "table" and getmetatable(ret[i]) then + setmetatable(ret[i],{}) -- remove that metatable, we do not need it on the other side! + end + if ret[i] == _G[list[0]] then + -- We cannot return itself, that return can contain bad values. + ret[i] = {_self_ref_ = true} + end end + table.insert(ret, 1, func) + self.recv:push(ret) end - table.insert(ret, 1, func) - self.recv:push(ret) end end).OnError(print) return self @@ -221,11 +223,11 @@ function multi:newSystemThreadedProcessor(cores) end) c.jobqueue:registerFunction("STP_GetThreadCount",function() - return {"t_thread", _G["__THREADS"]} + return _G["__THREADS"] end) c.jobqueue:registerFunction("STP_GetTaskCount",function() - return {"t_task", _G["__TASKS"]} + return _G["__TASKS"] end) function c:pushJob(ID, name, ...) @@ -372,18 +374,40 @@ function multi:newSystemThreadedProcessor(cores) end -- Special functions - c.getLeastLoaded = thread:newFunction(function(self) + c.getLeastLoaded = thread:newFunction(function(self, tp) local loads = {} - local jid = {} - for i,v in pairs(self.proc_list) do - table.insert(jid, self:pushJob(v, "STP_GetThreadCount")) - table.insert(jid, self:pushJob(v, "STP_GetTaskCount")) - end - - end) + local func - c.jobqueue.OnJobCompleted(function(id, ...) - -- + if tp then + func = "STP_GetThreadCount" + else + func = "STP_GetTaskCount" + end + + for i,v in pairs(self.proc_list) do + local conn + local jid = self:pushJob(v, func) + + conn = c.jobqueue.OnJobCompleted(function(id, data) + if id == jid then + table.insert(loads, {v, data}) + multi:newAlarm(1):OnRing(function(alarm) + c.jobqueue.OnJobCompleted:Unconnect(conn) + alarm:Destroy() + end) + end + end) + end + + thread.hold(function() return #loads == c.cores end) + if tp then + multi.print("Threads\n-------") + else + multi.print("Tasks\n-----") + end + for i,v in pairs(loads) do + print(v[1], v[2]) + end end) return c @@ -408,6 +432,7 @@ function thread.hold(n, opt) end return unpack(rets) end) + local conn local handle = func(name) conn = handle.OnReturn(function(...) @@ -421,11 +446,13 @@ function thread.hold(n, opt) return multi.unpack(args) or multi.NIL end end, opt)} + for i,v in pairs(ret) do if type(v) == "table" and v._self_ref_ == "parent" then ret[i] = n.Parent end end + return unpack(ret) else return thread_ref(n, opt) -- 2.43.0 From 03ecb47f6f4a9175968dfab242236aa0858c1029 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 29 May 2023 01:29:12 -0400 Subject: [PATCH 044/117] Finished getLoad(type) --- docs/changes.md | 1 + integration/sharedExtensions/init.lua | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index be715a8..afbc542 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -74,6 +74,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- STP:getLoad(type) -- returns a table where the index is the threadID and the value is the number of objects[type] running on that thread. `type`: "threads" for coroutines running or nil for all other objects running. - multi:newTargetedFunction(ID, proc, name, func, holup) -- This is used internally to handle thread.hold(proxy.conn) - proxy.getThreadID() -- Returns the threadID of the thread that the proxy is running in - proxy:getUniqueName() -- Gets the special name that identifies the object on the thread the proxy refers to diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 188efcd..209cd36 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -374,7 +374,7 @@ function multi:newSystemThreadedProcessor(cores) end -- Special functions - c.getLeastLoaded = thread:newFunction(function(self, tp) + c.getLoad = thread:newFunction(function(self, tp) local loads = {} local func @@ -390,7 +390,7 @@ function multi:newSystemThreadedProcessor(cores) conn = c.jobqueue.OnJobCompleted(function(id, data) if id == jid then - table.insert(loads, {v, data}) + loads[v] = data multi:newAlarm(1):OnRing(function(alarm) c.jobqueue.OnJobCompleted:Unconnect(conn) alarm:Destroy() @@ -400,14 +400,8 @@ function multi:newSystemThreadedProcessor(cores) end thread.hold(function() return #loads == c.cores end) - if tp then - multi.print("Threads\n-------") - else - multi.print("Tasks\n-----") - end - for i,v in pairs(loads) do - print(v[1], v[2]) - end + + return loads end) return c -- 2.43.0 From d520e0a93ad6cd8243a7063353d06ee4335e3f70 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 30 May 2023 23:50:36 -0400 Subject: [PATCH 045/117] Fixed some bugs --- docs/changes.md | 2 ++ init.lua | 39 ++++++++++++------------- integration/lanesManager/extensions.lua | 4 +-- integration/lanesManager/init.lua | 2 +- integration/sharedExtensions/init.lua | 16 +++++----- tests/threadtests.lua | 2 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index afbc542..3b45ee4 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -337,6 +337,8 @@ Removed Fixed --- +- Issue with thread:newFunction() where a threaded function will keep a record of their returns and pass them to future calls of the function. +- Issue with multi:newTask(func) not properly handling tasks to be removed. Now uses a thread internally to manage things. - multi.isMainThread was not properly handled in each integration. This has been resolved. - Issue with pseudo threading env's being messed up. Required removal of getName and getID! - connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! diff --git a/init.lua b/init.lua index 1d45f14..6f9755f 100644 --- a/init.lua +++ b/init.lua @@ -728,7 +728,7 @@ function multi:newEvent(task, func) if t then self:Pause() self.returns = t - c.OnEvent:Fire(self) + self.OnEvent:Fire(self) return true end end @@ -989,24 +989,9 @@ function multi:newTStep(start,reset,count,set) end local tasks = {} -local _tasks = 0 - -local function _task_handler(self, func) - tasks[#tasks + 1] = func - _tasks = _tasks + 1 -end function multi:newTask(func) - multi:newLoop(function(loop) - for i=1,_tasks do - tasks[i]() - end - _tasks = 0 - end):setName("Task Handler") - -- Re bind this method to use the one that doesn't init a thread! - multi.newTask = _task_handler tasks[#tasks + 1] = func - _tasks = _tasks + 1 end local scheduledjobs = {} @@ -1396,17 +1381,22 @@ function thread:newFunctionBase(generator, holdme) 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]) + local g = rets + rets = nil + return cleanReturns((g[1] or multi.NIL),g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) end end) else while not rets and not err do multi:getCurrentProcess():getHandler()() + multi:getHandler()() end + local g = rets + rets = nil 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]) + return cleanReturns(g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) end end tfunc.__call = function(th,...) @@ -1776,7 +1766,7 @@ co_status = { ["normal"] = function(thd,ref) end, ["running"] = function(thd,ref) end, ["dead"] = function(thd,ref,task,i,th) - if ref.__processed then return end + if ref.__processed then table.remove(th,i) 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 @@ -1792,7 +1782,7 @@ co_status = { end end end - _=nil r1=nil r2=nil r3=nil r4=nil r5=nil + _=nil r1=nil r2=nil r3=nil r4=nil r5=nil r6=nil r7=nil r8=nil r9=nil r10=nil r11=nil r12=nil r13=nil r14=nil r15=nil r16=nil ref.__processed = true end, } @@ -2400,4 +2390,13 @@ function multi:getHandler() return threadManager:getHandler() end +multi:newThread("Task Handler", function() + local check = function() + return table.remove(tasks) + end + while true do + thread.hold(check)() + end +end).OnError(multi.error) + return multi \ No newline at end of file diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 596f5f6..6a98121 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -167,12 +167,12 @@ function multi:newSystemThreadedJobQueue(n) return queueJob:pop() end) idle = clock() - thread:newThread("test",function() + thread:newThread("JobQueue-Spawn",function() local name = table.remove(dat, 1) local jid = table.remove(dat, 1) local args = table.remove(dat, 1) queueReturn:push{jid, funcs[name](multi.unpack(args)), queue} - end) + end).OnError(multi.error) end end).OnError(print) thread:newThread("DoAllHandler",function() diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index e4df31a..8e9fe59 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -132,7 +132,7 @@ function multi.InitSystemThreadErrorHandler() thread.yield() _,data = __ConsoleLinda:receive(0, "Q") if data then - print(data[1]) + --print(data[1]) end for i = #threads, 1, -1 do temp = threads[i] diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 209cd36..893ebdc 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -211,7 +211,7 @@ function multi:newSystemThreadedProcessor(cores) return tjq:pop() end) if dat then - thread:newThread("test",function() + thread:newThread("JQ-TargetThread",function() local name = table.remove(dat, 1) local jid = table.remove(dat, 1) local args = table.remove(dat, 1) @@ -387,22 +387,20 @@ function multi:newSystemThreadedProcessor(cores) for i,v in pairs(self.proc_list) do local conn local jid = self:pushJob(v, func) - - conn = c.jobqueue.OnJobCompleted(function(id, data) + + conn = self.jobqueue.OnJobCompleted(function(id, data) if id == jid then - loads[v] = data - multi:newAlarm(1):OnRing(function(alarm) - c.jobqueue.OnJobCompleted:Unconnect(conn) - alarm:Destroy() + table.insert(loads, {v, data}) + multi:newTask(function() + self.jobqueue.OnJobCompleted:Unconnect(conn) end) end end) end thread.hold(function() return #loads == c.cores end) - return loads - end) + end, true) return c end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index a3024fc..b67a7e1 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -110,7 +110,7 @@ multi:newThread("Scheduler Thread",function() jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads - func = jq:newFunction("test",function(a,b) + func = jq:newFunction("test-thread",function(a,b) THREAD.sleep(.2) return a+b end) -- 2.43.0 From 5e2ab9af3d6af261cd91430df7150bf93b6bb171 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 31 May 2023 21:51:47 -0400 Subject: [PATCH 046/117] Added an easy way to share a table, found some limitations with lanes threading. --- docs/changes.md | 69 +++++++++++++++++++++++---- init.lua | 13 ++--- integration/sharedExtensions/init.lua | 10 +++- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 3b45ee4..24bd809 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -74,6 +74,51 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor(8) + + local shared = stp:newSharedTable("shared") + + shared["test"] = "We work!" + + for i=1,5 do + -- There is a bit of overhead when creating threads on a process. Takes some time, mainly because we are creating a proxy. + stp:newThread(function() + local multi, thread = require("multi"):init() + local shared = _G["shared"] + print(THREAD_NAME, shared.test, shared.test2) + multi:newAlarm(.5):OnRing(function() -- Play around with the time. System threads do not create instantly. They take quite a bit of time to get spawned. + print(THREAD_NAME, shared.test, shared.test2) + end) + end) + end + + shared["test2"] = "We work!!!" + + multi:mainloop() + ``` + + Output: + ``` + INFO: Integrated Lanes Threading! + STJQ_cPXT8GOx We work! nil + STJQ_hmzdYDVr We work! nil + STJQ_3lwMhnfX We work! nil + STJQ_hmzdYDVr We work! nil + STJQ_cPXT8GOx We work! nil + STJQ_cPXT8GOx We work! We work!!! + STJQ_hmzdYDVr We work! We work!!! + STJQ_3lwMhnfX We work! We work!!! + STJQ_hmzdYDVr We work! We work!!! + STJQ_cPXT8GOx We work! We work!!! + ``` + - STP:getLoad(type) -- returns a table where the index is the threadID and the value is the number of objects[type] running on that thread. `type`: "threads" for coroutines running or nil for all other objects running. - multi:newTargetedFunction(ID, proc, name, func, holup) -- This is used internally to handle thread.hold(proxy.conn) - proxy.getThreadID() -- Returns the threadID of the thread that the proxy is running in @@ -108,16 +153,22 @@ Added This event is subscribed to on the proxy threads side of things! Currently supporting: - - STP:newLoop(...) - - STP:newTLoop(...) - - STP:newUpdater(...) - - STP:newEvent(...) - - STP:newAlarm(...) - - STP:newStep(...) - - STP:newTStep(...) - - STP:newThread(...) - - STP:newFunction(...) + - proxyLoop = STP:newLoop(...) + - proxyTLoop = STP:newTLoop(...) + - proxyUpdater = STP:newUpdater(...) + - proxyEvent = STP:newEvent(...) + - proxyAlarm = STP:newAlarm(...) + - proxyStep = STP:newStep(...) + - proxyTStep = STP:newTStep(...) + - proxyThread = STP:newThread(...) + - threadedFunction = STP:newFunction(...) + Unique: + - STP:newSharedTable(name) + +
+ + **STP** functions (The ones above) cannot be called within coroutine based thread when using lanes. This causes thread.hold to break. Objects(proxies) returned by these functions are ok to use in coroutine based threads! ```lua package.path = "?/init.lua;?.lua;"..package.path diff --git a/init.lua b/init.lua index 6f9755f..2398697 100644 --- a/init.lua +++ b/init.lua @@ -312,7 +312,7 @@ function multi:newConnection(protect,func,kill) c.FC=0 function c:hasConnections() - return #call_funcs~=0 + return #fast~=0 end function c:Lock(conn) @@ -367,7 +367,7 @@ function multi:newConnection(protect,func,kill) end function c:getConnections() - return call_funcs + return fast end function c:getConnection(name, ignore) @@ -383,6 +383,7 @@ function multi:newConnection(protect,func,kill) end function c:fastMode() return self end + if kill then local kills = {} function c:Fire(...) @@ -457,14 +458,14 @@ function multi:newConnection(protect,func,kill) end function c:Bind(t) - local temp = call_funcs - call_funcs=t + local temp = fast + fast=t return temp end function c:Remove() - local temp = call_funcs - call_funcs={} + local temp = fast + fast={} return temp end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 893ebdc..bbd3691 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -353,6 +353,15 @@ function multi:newSystemThreadedProcessor(cores) return c.jobqueue:newFunction(func, holdme) end + function c:newSharedTable(name) + if not name then multi.error("You must provide a name when creating a table!") end + local tbl_name = "TABLE_"..multi.randomString(8) + c.jobqueue:doToAll(function(tbl_name, interaction) + _G[interaction] = THREAD.waitFor(tbl_name):init() + end, tbl_name, name) + return multi:newSystemThreadedTable(tbl_name):init() + end + function c.run() return self end @@ -373,7 +382,6 @@ function multi:newSystemThreadedProcessor(cores) return false end - -- Special functions c.getLoad = thread:newFunction(function(self, tp) local loads = {} local func -- 2.43.0 From 257ed03728d1f33ebf1c0469d8d963534702f66f Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 3 Jun 2023 12:07:33 -0400 Subject: [PATCH 047/117] THREAD_NAME set for main thread, connections break the rules for proxies --- docs/changes.md | 67 +++++++++++++++++++++++++-- init.lua | 1 + integration/lanesManager/init.lua | 3 ++ integration/loveManager/init.lua | 18 ++++--- integration/lovrManager/init.lua | 2 + integration/pseudoManager/init.lua | 3 ++ integration/sharedExtensions/init.lua | 17 +++++-- 7 files changed, 93 insertions(+), 18 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 24bd809..44a30ca 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -138,7 +138,7 @@ Added alarm = stp:newAlarm(3) - alarm.OnRing:Connect(function(alarm) + alarm._OnRing:Connect(function(alarm) print("Hmm...", THREAD_NAME) end) ``` @@ -148,9 +148,70 @@ Added ``` Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. - There are currently limitations to proxies. Connection proxy do not receive events on the non thread side. So connection metamethods do not work! thread.hold(proxy.conn) does work! The backend to get this to work was annoying :P + Connection proxies break the rules a bit. Normally methods should always work on the thread side, however for connections in order to have actions work on the thread side you would call the connection using `obj._connName` instead of calling `obj.connName`. This allows you to have more control over connection events. See example below: + ```lua + package.path = "?/init.lua;?.lua;"..package.path - This event is subscribed to on the proxy threads side of things! + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor(8) + + alarm = stp:newAlarm(3) + + -- This doesn't work since this event has already been subscribed to internally on the thread to get thread.hold(alarm.OnRing) to work. But as many events to alarm.OnRing can be made! + thread:newThread(function() + print("Hold on proxied connection", thread.hold(alarm._OnRing)) + end) + + alarm.OnRing(function(a) + print("OnRing",a, THREAD_NAME, THREAD_ID) + end) + + print("alarm.OnRing", alarm.OnRing.Type) + print("alarm._OnRing", alarm._OnRing.Type) + + thread:newThread(function() + print("Hold on proxied no proxy connection", thread.hold(alarm.OnRing)) + end) + + thread:newThread(function() + print("Hold on proxied no proxy connection", thread.hold(alarm.OnRing)) + end) + + -- This doesn't work since this event has already been subscribed to internally on the thread to get thread.hold(alarm.OnRing) to work. But as many events to alarm.OnRing can be made! + thread:newThread(function() + print("Hold on proxied connection", thread.hold(alarm._OnRing)) + end) + + alarm._OnRing(function(a) + print("_OnRing",a, THREAD_NAME, THREAD_ID) + a:Reset(1) + end) + + multi:mainloop() + ``` + Output: + ``` + INFO: Integrated Lanes Threading! + alarm.OnRing connector + alarm._OnRing proxy + _OnRing table: 025EB128 STJQ_cjKsEZHg 1 <-- This can change each time you run this example! + OnRing table: 018BC0C0 MAIN_THREAD 0 + Hold on proxied no proxy connection table: 018BC0C0 nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil + Hold on proxied no proxy connection table: 018BC0C0 nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil + _OnRing table: 025EB128 STJQ_cjKsEZHg 1 + OnRing table: 018BC0C0 MAIN_THREAD 0 + _OnRing table: 025EB128 STJQ_cjKsEZHg 1 + OnRing table: 018BC0C0 MAIN_THREAD 0 + + ... (Will repeat ever second now) + _OnRing table: 025EB128 STJQ_cjKsEZHg 1 + + OnRing table: 018BC0C0 MAIN_THREAD 0 + ``` + + The proxy version can only subscribe to events on the proxy thread, which means that connection metamethods will not work with the proxy version (`_OnRing` on the non proxy thread side), but the (`OnRing`) version will work. Cleverly handling the proxy thread and the non proxy thread will allow powerful connection logic. Also this is not a full system threaded connection. **Proxies should only be used between 2 threads!** To keep things fast I'm using simple queues to transfer data. There is no guarantee that things will work! Currently supporting: - proxyLoop = STP:newLoop(...) diff --git a/init.lua b/init.lua index 2398697..881f832 100644 --- a/init.lua +++ b/init.lua @@ -80,6 +80,7 @@ multi.STEP = "step" multi.TSTEP = "tstep" multi.THREAD = "thread" multi.SERVICE = "service" +multi.PROXY = "proxy" if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 8e9fe59..547c7c6 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -36,6 +36,9 @@ lanes = require("lanes").configure() multi.SystemThreads = {} multi.isMainThread = true +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 + function multi:canSystemThread() return true end diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 5300a92..7c5289f 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -32,15 +32,13 @@ THREAD = require("multi.integration.loveManager.threads") sThread = THREAD __IMPORTS = {...} __FUNC__=table.remove(__IMPORTS,1) -__THREADID__=table.remove(__IMPORTS,1) -__THREADNAME__=table.remove(__IMPORTS,1) -THREAD_NAME = __THREADNAME__ -THREAD_ID = __THREADID__ -math.randomseed(__THREADID__) +THREAD_ID=table.remove(__IMPORTS,1) +THREAD_NAME=table.remove(__IMPORTS,1) +math.randomseed(THREAD_ID) math.random() math.random() math.random() -stab = THREAD.createStaticTable(__THREADNAME__ .. __THREADID__) +stab = THREAD.createStaticTable(THREAD_NAME .. THREAD_ID) GLOBAL = THREAD.getGlobal() if GLOBAL["__env"] then local env = THREAD.unpackENV(GLOBAL["__env"]) @@ -60,15 +58,15 @@ stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} local multi, thread = require("multi"):init() local THREAD = {} -__THREADID__ = 0 -__THREADNAME__ = "MainThread" +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 multi.integration = {} local THREAD = require("multi.integration.loveManager.threads") local GLOBAL = THREAD.getGlobal() -local THREAD_ID = 1 multi.isMainThread = true function multi:newSystemThread(name, func, ...) + THREAD_ID = THREAD_ID + 1 local c = {} c.name = name c.ID = THREAD_ID @@ -79,7 +77,7 @@ function multi:newSystemThread(name, func, ...) 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 + function c:getName() return c.name end thread:newThread(name .. "_System_Thread_Handler",function() if name == "SystemThreaded Function Handler" then diff --git a/integration/lovrManager/init.lua b/integration/lovrManager/init.lua index d09b2d9..6a64c98 100644 --- a/integration/lovrManager/init.lua +++ b/integration/lovrManager/init.lua @@ -42,6 +42,8 @@ local multi, thread = require("multi.compat.lovr2d"):init() local THREAD = {} __THREADID__ = 0 __THREADNAME__ = "MainThread" +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 multi.integration={} multi.integration.lovr2d={} local THREAD = require("multi.integration.lovrManager.threads") diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 30879f3..490cff9 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -35,6 +35,9 @@ multi.isMainThread = true local activator = require("multi.integration.pseudoManager.threads") local GLOBAL, THREAD = activator.init(thread) +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 + function multi:canSystemThread() -- We are emulating system threading return true end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index bbd3691..33ab91c 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -67,7 +67,7 @@ function multi:newProxy(list) function c:init() local multi, thread = nil, nil - if THREAD_NAME then + if THREAD_ID>0 then local multi, thread = require("multi"):init() local function check() return self.send:pop() @@ -113,11 +113,19 @@ function multi:newProxy(list) self.Type = multi.PROXY for _,v in pairs(self.funcs) do if type(v) == "table" then + -- We have a connection v[2]:init() - self[v[1]] = v[2] + self["_"..v[1]] = v[2] v[2].Parent = self + setmetatable(v[2],getmetatable(multi:newConnection())) + self[v[1]] = multi:newConnection() + + thread:newThread(function() + while true do + self[v[1]]:Fire(thread.hold(alarm["_"..v[1]])) + end + end) else - lastObj = self self[v] = thread:newFunction(function(self,...) if self == me then me.send:push({v, true, ...}) @@ -146,8 +154,6 @@ function multi:newProxy(list) return c end -multi.PROXY = "proxy" - local targets = {} local nFunc = 0 @@ -416,6 +422,7 @@ end -- Modify thread.hold to handle proxies local thread_ref = thread.hold function thread.hold(n, opt) + --if type(n) == "table" then print(n.Type, n.isConnection()) end if type(n) == "table" and n.Type == multi.PROXY and n.isConnection() then local ready = false local args -- 2.43.0 From 6fe10b22ab995edacd7e5d3adae3430374d4c128 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 17 Jun 2023 21:33:34 -0400 Subject: [PATCH 048/117] Testing --- init.lua | 2 + integration/lanesManager/extensions.lua | 3 +- integration/lanesManager/init.lua | 1 - integration/sharedExtensions/init.lua | 190 +++++++++++++----------- 4 files changed, 107 insertions(+), 89 deletions(-) diff --git a/init.lua b/init.lua index 881f832..e434b3c 100644 --- a/init.lua +++ b/init.lua @@ -516,6 +516,8 @@ local function isolateFunction(func, env) end end +multi.isolateFunction = isolateFunction + function multi:Break() self:Pause() self.Active=nil diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 6a98121..073fe6d 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -344,4 +344,5 @@ function multi:newSystemThreadedConnection(name) end return c -end \ No newline at end of file +end +require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 547c7c6..e22431e 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -185,7 +185,6 @@ multi.integration = {} -- for module creators multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.lanesManager.extensions") -require("multi.integration.sharedExtensions") return { init = function() return GLOBAL, THREAD diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 33ab91c..8fc99a3 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -1,4 +1,4 @@ ---[[ +--[[ todo finish the targeted job! MIT License Copyright (c) 2023 Ryan Ward @@ -22,6 +22,26 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] +function copy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[copy(k)] = copy(v) end + return res +end + +function tprint (tbl, indent) + if not indent then indent = 0 end + for k, v in pairs(tbl) do + formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + print(formatting) + tprint(v, indent+1) + else + print(formatting .. tostring(v)) + end + end + end + local multi, thread = require("multi"):init() -- Returns a handler that allows a user to interact with an object on another thread! @@ -64,10 +84,12 @@ function multi:newProxy(list) local c = {} c.name = multi.randomString(12) + c.is_init = false - function c:init() + function c:init(proc_name) local multi, thread = nil, nil - if THREAD_ID>0 then + if not(c.is_init) then + c.is_init = true local multi, thread = require("multi"):init() local function check() return self.send:pop() @@ -75,7 +97,9 @@ function multi:newProxy(list) self.send = multi:newSystemThreadedQueue(self.name.."_S"):init() self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() self.funcs = list - self.conns = list[-1] + self._funcs = copy(list) + self.Type = multi.PROXY + self.TID = THREAD_ID thread:newThread(function() while true do local data = thread.hold(check) @@ -104,25 +128,31 @@ function multi:newProxy(list) end).OnError(print) return self else + print("INIT IN",THREAD_NAME) local multi, thread = require("multi"):init() local me = self - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD - self.send = THREAD.waitFor(self.name.."_S") - self.recv = THREAD.waitFor(self.name.."_R") + self.proc_name = proc_name + if multi.integration then + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD + end + self.send = THREAD.waitFor(self.name.."_S"):init() + self.recv = THREAD.waitFor(self.name.."_R"):init() self.Type = multi.PROXY for _,v in pairs(self.funcs) do if type(v) == "table" then -- We have a connection - v[2]:init() + print("Init Conn",v[1],THREAD_NAME) + v[2]:init(proc_name) self["_"..v[1]] = v[2] v[2].Parent = self setmetatable(v[2],getmetatable(multi:newConnection())) self[v[1]] = multi:newConnection() thread:newThread(function() + print("HOLD:","_"..v[1],self["_"..v[1]].Type) while true do - self[v[1]]:Fire(thread.hold(alarm["_"..v[1]])) + self[v[1]]:Fire(thread.hold(self["_"..v[1]])) end end) else @@ -151,13 +181,24 @@ function multi:newProxy(list) return self end end + function c:getTransferable() + local multi, thread = require("multi"):init() + local cp = {} + cp.name = self.name + cp.funcs = copy(self._funcs) + cp._funcs = copy(self._funcs) + cp.Type = self.Type + cp.init = self.init + return cp + end return c end local targets = {} +local references = {} local nFunc = 0 -function multi:newTargetedFunction(ID, proc, name, func, holup) -- This registers with the queue +function multi:newTargetedFunction(ID, name, func, holup) -- This registers with the queue if type(name)=="function" then holup = func func = name @@ -181,6 +222,12 @@ function multi:newTargetedFunction(ID, proc, name, func, holup) -- This register end) end, holup), name end +-- local qname = name .. "_tq_" .. THREAD_ID +-- local rqname = name .. "_rtq_" .. THREAD_ID + +local function getQueue(name) + return THREAD.waitFor(name):init() +end local jid = -1 function multi:newSystemThreadedProcessor(cores) @@ -202,27 +249,30 @@ function multi:newSystemThreadedProcessor(cores) c.OnObjectCreated = multi:newConnection() c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) - c.targetedQueue = multi:newSystemThreadedQueue(name.."_target"):init() c.jobqueue:registerFunction("STP_enable_targets",function(name) local multi, thread = require("multi"):init() - local qname = THREAD_NAME .. "_t_queue" - local targetedQueue = THREAD.waitFor(name):init() + local qname = name .. "_tq_" .. THREAD_ID + local rqname = name .. "_rtq_" .. THREAD_ID local tjq = multi:newSystemThreadedQueue(qname):init() - targetedQueue:push({tonumber(THREAD_ID), qname}) multi:newThread("TargetedJobHandler", function() - local queueReturn = _G["__QR"] + local th while true do local dat = thread.hold(function() return tjq:pop() end) if dat then - thread:newThread("JQ-TargetThread",function() + th = thread:newThread("JQ-TargetThread",function() local name = table.remove(dat, 1) local jid = table.remove(dat, 1) + local func = table.remove(dat, 1) local args = table.remove(dat, 1) - queueReturn:push{jid, _G[name](multi.unpack(args)), queue} - end).OnError(multi.error) + th.OnError(function(self,err) + -- We want to pass this to the other calling thread incase + rqname:push{jid, err} + end) + rqname:push{jid, func(multi.unpack(args))} + end) end end end).OnError(multi.error) @@ -248,16 +298,6 @@ function multi:newSystemThreadedProcessor(cores) _G["__TASKS"] = 0 end, name.."_target") - local count = 0 - while count < c.cores do - local dat = c.targetedQueue:pop() - if dat then - targets[dat[1]] = multi.integration.THREAD.waitFor(dat[2]):init() - table.insert(c.proc_list, dat[1]) -- Add thread_id to proc list - count = count + 1 - end - end - c.jobqueue:registerFunction("packObj",function(obj) local multi, thread = require("multi"):init() obj.getThreadID = function() -- Special functions we are adding @@ -290,46 +330,42 @@ function multi:newSystemThreadedProcessor(cores) return packObj(obj) end, true) - function c:newLoop(func, notime) - proxy = self.spawnTask("newLoop", func, notime):init() - proxy.__proc = self + local implement = { + "newLoop", + "newTLoop", + "newUpdater", + "newEvent", + "newAlarm", + "newStep", + "newTStep" + } + + for _, method in pairs(implement) do + c[method] = function(self, ...) + proxy = self.spawnTask(method, ...):init(self.Name) + references[proxy] = self + return proxy + end + end + + function c:newThread(name, func, ...) + proxy = self.spawnThread(name, func, ...):init(self.Name) + references[proxy] = self + table.insert(self.threads, proxy) return proxy end - function c:newTLoop(func, time) - proxy = self.spawnTask("newTLoop", func, time):init() - proxy.__proc = self - return proxy + function c:newFunction(func, holdme) + return c.jobqueue:newFunction(func, holdme) end - function c:newUpdater(skip, func) - proxy = self.spawnTask("newUpdater", func, notime):init() - proxy.__proc = self - return proxy - end - - function c:newEvent(task, func) - proxy = self.spawnTask("newEvent", task, func):init() - proxy.__proc = self - return proxy - end - - function c:newAlarm(set, func) - proxy = self.spawnTask("newAlarm", set, func):init() - proxy.__proc = self - return proxy - end - - function c:newStep(start, reset, count, skip) - proxy = self.spawnTask("newStep", start, reset, count, skip):init() - proxy.__proc = self - return proxy - end - - function c:newTStep(start ,reset, count, set) - proxy = self.spawnTask("newTStep", start, reset, count, set):init() - proxy.__proc = self - return proxy + function c:newSharedTable(name) + if not name then multi.error("You must provide a name when creating a table!") end + local tbl_name = "TABLE_"..multi.randomString(8) + c.jobqueue:doToAll(function(tbl_name, interaction) + _G[interaction] = THREAD.waitFor(tbl_name):init() + end, tbl_name, name) + return multi:newSystemThreadedTable(tbl_name):init() end function c:getHandler() @@ -348,26 +384,6 @@ function multi:newSystemThreadedProcessor(cores) return self.Name end - function c:newThread(name, func, ...) - proxy = self.spawnThread(name, func, ...):init() - proxy.__proc = self - table.insert(self.threads, proxy) - return proxy - end - - function c:newFunction(func, holdme) - return c.jobqueue:newFunction(func, holdme) - end - - function c:newSharedTable(name) - if not name then multi.error("You must provide a name when creating a table!") end - local tbl_name = "TABLE_"..multi.randomString(8) - c.jobqueue:doToAll(function(tbl_name, interaction) - _G[interaction] = THREAD.waitFor(tbl_name):init() - end, tbl_name, name) - return multi:newSystemThreadedTable(tbl_name):init() - end - function c.run() return self end @@ -428,7 +444,7 @@ function thread.hold(n, opt) local args local id = n.getThreadID() local name = n:getUniqueName() - local func = multi:newTargetedFunction(id, n.Parent.__proc, "conn_"..multi.randomString(8), function(_name) + local func = multi:newTargetedFunction(id, references[n.Parent], "conn_"..multi.randomString(8), function(_name) local multi, thread = require("multi"):init() local obj = _G[_name] local rets = {thread.hold(obj)} @@ -437,7 +453,7 @@ function thread.hold(n, opt) rets[i] = {_self_ref_ = "parent"} end end - return unpack(rets) + return multi.unpack(rets) end) local conn @@ -460,7 +476,7 @@ function thread.hold(n, opt) end end - return unpack(ret) + return multi.unpack(ret) else return thread_ref(n, opt) end -- 2.43.0 From 1b3e3303e9095345169a91d59f5239544ab2d48e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 18 Jun 2023 00:08:51 -0400 Subject: [PATCH 049/117] Really close to portable proxies, currently extreamly unstable! --- integration/sharedExtensions/init.lua | 141 +++++++++++++++++++------- 1 file changed, 103 insertions(+), 38 deletions(-) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 8fc99a3..bd623d5 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -128,7 +128,6 @@ function multi:newProxy(list) end).OnError(print) return self else - print("INIT IN",THREAD_NAME) local multi, thread = require("multi"):init() local me = self self.proc_name = proc_name @@ -142,7 +141,6 @@ function multi:newProxy(list) for _,v in pairs(self.funcs) do if type(v) == "table" then -- We have a connection - print("Init Conn",v[1],THREAD_NAME) v[2]:init(proc_name) self["_"..v[1]] = v[2] v[2].Parent = self @@ -150,11 +148,11 @@ function multi:newProxy(list) self[v[1]] = multi:newConnection() thread:newThread(function() - print("HOLD:","_"..v[1],self["_"..v[1]].Type) while true do - self[v[1]]:Fire(thread.hold(self["_"..v[1]])) + local data = thread.hold(self["_"..v[1]]) + self[v[1]]:Fire(data) end - end) + end).OnError(multi.error) else self[v] = thread:newFunction(function(self,...) if self == me then @@ -198,36 +196,72 @@ local targets = {} local references = {} local nFunc = 0 -function multi:newTargetedFunction(ID, name, func, holup) -- This registers with the queue +function multi:newTargetedFunction(ID, proxy, name, func, holup) -- This registers with the queue if type(name)=="function" then holup = func func = name name = "JQ_TFunc_"..nFunc end nFunc = nFunc + 1 - proc.jobqueue:registerFunction(name, func) + + multi:executeOnProcess(proxy.proc_name, function(proc, name, func) + proc.jobqueue:registerFunction(name, func) + end, name, func) + return thread:newFunction(function(...) - local id = proc:pushJob(ID, name, ...) - local link - local rets - link = proc.jobqueue.OnJobCompleted(function(jid,...) - if id==jid then - rets = {...} - end - end) - return thread.hold(function() - if rets then - return multi.unpack(rets) or multi.NIL - end - end) + return multi:executeOnProcess(proxy.proc_name, function(proc, name, ID, ...) + local multi, thread = require("multi"):init() + local id = proc:pushJob(ID, name, ...) + local rets + local tjq = THREAD.get(proc.Name .. "_target_rtq_" .. ID):init() + return thread.hold(function() + local data = tjq:peek() + if data then + print(data) + end + if data and data[1] == id then + print("Got it sigh") + tjq:pop() + table.remove(data, 1) + return multi.unpack(data) or multi.NIL + end + end) + -- proc.jobqueue.OnJobCompleted(function(jid, ...) + -- if id==jid then + -- rets = {...} + -- print("Got!") + -- end + -- end) + -- return thread.hold(function() + -- if rets then + -- return multi.unpack(rets) or multi.NIL + -- end + -- end) + end, name, ID, ...) end, holup), name end --- local qname = name .. "_tq_" .. THREAD_ID --- local rqname = name .. "_rtq_" .. THREAD_ID -local function getQueue(name) - return THREAD.waitFor(name):init() -end +multi.executeOnProcess = thread:newFunction(function(self, name, func, ...) + local queue = THREAD.get(name .. "_local_proc") + local queueR = THREAD.get(name .. "_local_return") + if queue and queueR then + local multi, thread = require("multi"):init() + local id = multi.randomString(8) + queue = queue:init() + queueR = queueR:init() + queue:push({func, id, ...}) + return thread.hold(function() + local data = queueR:peek() + if data and data[1] == id then + queueR:pop() + table.remove(data, 1) + return multi.unpack(data) or multi.NIL + end + end) + else + return nil, "Unable to find a process queue with name: '" .. name .. "'" + end +end, true) local jid = -1 function multi:newSystemThreadedProcessor(cores) @@ -249,12 +283,16 @@ function multi:newSystemThreadedProcessor(cores) c.OnObjectCreated = multi:newConnection() c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) + c.local_cmd = multi:newSystemThreadedQueue(name .. "_local_proc"):init() + c.local_cmd_return = multi:newSystemThreadedQueue(name .. "_local_return"):init() c.jobqueue:registerFunction("STP_enable_targets",function(name) local multi, thread = require("multi"):init() local qname = name .. "_tq_" .. THREAD_ID local rqname = name .. "_rtq_" .. THREAD_ID + local tjq = multi:newSystemThreadedQueue(qname):init() + local trq = multi:newSystemThreadedQueue(rqname):init() multi:newThread("TargetedJobHandler", function() local th while true do @@ -269,13 +307,13 @@ function multi:newSystemThreadedProcessor(cores) local args = table.remove(dat, 1) th.OnError(function(self,err) -- We want to pass this to the other calling thread incase - rqname:push{jid, err} + trq:push{jid, err} end) - rqname:push{jid, func(multi.unpack(args))} + trq:push{jid, func(multi.unpack(args))} end) end end - end).OnError(multi.error) + end).OnError(print) end) c.jobqueue:registerFunction("STP_GetThreadCount",function() @@ -287,7 +325,10 @@ function multi:newSystemThreadedProcessor(cores) end) function c:pushJob(ID, name, ...) - targets[ID]:push{name, jid, {...}} + print("pushing") + local tq = THREAD.waitFor(self.Name .. "_target_tq_" .. ID):init() + --targets[ID]:push{name, jid, {...}} + tq:push{name, jid, {...}} jid = jid - 1 return jid + 1 end @@ -432,37 +473,59 @@ function multi:newSystemThreadedProcessor(cores) return loads end, true) + local check = function() + return c.local_cmd:pop() + end + thread:newThread(function() + while true do + local data = thread.hold(check) + if data then + thread:newThread(function() + local func = table.remove(data, 1) + local id = table.remove(data, 1) + local ret = {id, func(c, multi.unpack(data))} + c.local_cmd_return:push(ret) + end).OnError(multi.error) + end + end + end).OnError(multi.error) + return c end -- Modify thread.hold to handle proxies local thread_ref = thread.hold function thread.hold(n, opt) - --if type(n) == "table" then print(n.Type, n.isConnection()) end if type(n) == "table" and n.Type == multi.PROXY and n.isConnection() then local ready = false local args local id = n.getThreadID() local name = n:getUniqueName() - local func = multi:newTargetedFunction(id, references[n.Parent], "conn_"..multi.randomString(8), function(_name) + print(id, name) + local func = multi:newTargetedFunction(id, n, "conn_"..multi.randomString(8), function(_name) local multi, thread = require("multi"):init() local obj = _G[_name] + print("Start") local rets = {thread.hold(obj)} + print("Ring ;)") for i,v in pairs(rets) do if v.Type then rets[i] = {_self_ref_ = "parent"} end end return multi.unpack(rets) - end) + end, true) local conn - local handle = func(name) - conn = handle.OnReturn(function(...) - ready = true - args = {...} - handle.OnReturn:Unconnect(conn) - end) + local args = {func(name)} + -- conn = handle.OnReturn(function(...) + -- ready = true + -- args = {...} + -- for i,v in pairs(args) do + -- print("DATA",i,v) + -- end + -- handle.OnReturn:Unconnect(conn) + -- end) local ret = {thread_ref(function() if ready then @@ -471,7 +534,9 @@ function thread.hold(n, opt) end, opt)} for i,v in pairs(ret) do + print("OBJECT",v.Type) if type(v) == "table" and v._self_ref_ == "parent" then + print("assign") ret[i] = n.Parent end end -- 2.43.0 From c80f44c68ec10cce64941a6c23bc68b4fd9927fc Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 19 Jun 2023 00:24:07 -0400 Subject: [PATCH 050/117] Debugging what is going on... --- init.lua | 13 ++++++++----- integration/lanesManager/extensions.lua | 8 ++++---- integration/lanesManager/init.lua | 2 +- integration/loveManager/extensions.lua | 4 ++-- integration/sharedExtensions/init.lua | 9 ++++++--- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/init.lua b/init.lua index e434b3c..1667819 100644 --- a/init.lua +++ b/init.lua @@ -1113,7 +1113,7 @@ function multi:newProcessor(name, nothread) function c:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return c:newThread("Threaded Function Handler", func, ...) + return c:newThread("Process Threaded Function Handler", func, ...) end, holdme)() end @@ -1451,7 +1451,9 @@ end function thread:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return thread:newThread("Threaded Function Handler", func, ...) + local th = thread:newThread("Free Threaded Function Handler", func, ...) + th.creator = debug.getinfo(2).name + return th end, holdme)() end @@ -1485,7 +1487,7 @@ function thread:newProcessor(name) function proc:newFunction(func, holdme) return thread:newFunctionBase(function(...) - return thread_proc:newThread("Threaded Function Handler", func, ...) + return thread_proc:newThread("TProc Threaded Function Handler", func, ...) end, holdme)() end @@ -1774,6 +1776,7 @@ co_status = { if _ then ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else + print("Thread: ", ref.Name, ref.creator, THREAD_NAME) 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 @@ -2346,8 +2349,8 @@ end function multi.error(self, err) if type(err) == "bool" then crash = err end if type(self) == "string" then err = self end - io.write("\x1b[91mERROR:\x1b[0m " .. err .. "\n") - error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type) + io.write("\x1b[91mERROR:\x1b[0m " .. err .. " " .. debug.getinfo(2).name .."\n") + error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") if multi.defaultSettings.error then os.exit(1) end diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 073fe6d..94b98c1 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -174,7 +174,7 @@ function multi:newSystemThreadedJobQueue(n) queueReturn:push{jid, funcs[name](multi.unpack(args)), queue} end).OnError(multi.error) end - end).OnError(print) + end).OnError(multi.error) thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() @@ -190,7 +190,7 @@ function multi:newSystemThreadedJobQueue(n) end end end - end).OnError(print) + end).OnError(multi.error) thread:newThread("IdleHandler",function() while true do thread.hold(function() @@ -198,9 +198,9 @@ function multi:newSystemThreadedJobQueue(n) end) THREAD.sleep(.01) end - end).OnError(print) + end).OnError(multi.error) multi:mainloop() - end,i).OnError(print) + end,i).OnError(multi.error) end return c end diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index e22431e..147488a 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -177,7 +177,7 @@ function multi.InitSystemThreadErrorHandler() end end end - end).OnError(print) + end).OnError(multi.error) end multi.print("Integrated Lanes Threading!") diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 8740018..992a10b 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -271,7 +271,7 @@ function multi:newSystemThreadedConnection(name) -- This shouldn't be the case end end - end).OnError(print) + end).OnError(multi.error) return self end @@ -346,7 +346,7 @@ function multi:newSystemThreadedConnection(name) c.proxy_conn:Fire(multi.unpack(item[2])) end end - end).OnError(print) + end).OnError(multi.error) --- ^^^ This will only exist in the init thread THREAD.package(name,c) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index bd623d5..48800f3 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -125,7 +125,7 @@ function multi:newProxy(list) self.recv:push(ret) end end - end).OnError(print) + end).OnError(multi.error) return self else local multi, thread = require("multi"):init() @@ -300,10 +300,13 @@ function multi:newSystemThreadedProcessor(cores) return tjq:pop() end) if dat then + for i,v in pairs(dat) do + print(i,v) + end th = thread:newThread("JQ-TargetThread",function() local name = table.remove(dat, 1) local jid = table.remove(dat, 1) - local func = table.remove(dat, 1) + local func = _G[name] local args = table.remove(dat, 1) th.OnError(function(self,err) -- We want to pass this to the other calling thread incase @@ -313,7 +316,7 @@ function multi:newSystemThreadedProcessor(cores) end) end end - end).OnError(print) + end).OnError(multi.error) end) c.jobqueue:registerFunction("STP_GetThreadCount",function() -- 2.43.0 From c39aa229f83616c2ea439974906bfffcb1190423 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 20 Jun 2023 00:05:12 -0400 Subject: [PATCH 051/117] Fixed critical issue with coroutine based threads --- docs/changes.md | 1 + init.lua | 23 ++++++++++------- integration/sharedExtensions/init.lua | 36 +++++++-------------------- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 44a30ca..f27f5fe 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -449,6 +449,7 @@ Removed Fixed --- +- Issue with coroutine based threads where they weren't all being scheduled due to a bad for loop. Replaced with a while to ensure all threads are consumed properly. If a thread created a thread that created a thread that may or may not be on the same process, things got messed up due to the original function not being built with these abstractions in mind. - Issue with thread:newFunction() where a threaded function will keep a record of their returns and pass them to future calls of the function. - Issue with multi:newTask(func) not properly handling tasks to be removed. Now uses a thread internally to manage things. - multi.isMainThread was not properly handled in each integration. This has been resolved. diff --git a/init.lua b/init.lua index 1667819..d67cdb2 100644 --- a/init.lua +++ b/init.lua @@ -352,7 +352,7 @@ function multi:newConnection(protect,func,kill) for i=1,#fast do local suc, err = pcall(fast[i], ...) if not suc then - print(err) + multi.error(err) end if kill then table.insert(kills,i) @@ -1393,7 +1393,6 @@ function thread:newFunctionBase(generator, holdme) else while not rets and not err do multi:getCurrentProcess():getHandler()() - multi:getHandler()() end local g = rets rets = nil @@ -1529,9 +1528,9 @@ end 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 func == name then - name = name or multi.randomString(16) + if type(name) == "function" then + func = name + name = "UnnamedThread_"..multi.randomString(16) end local c={nil,nil,nil,nil,nil,nil,nil} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} @@ -1776,7 +1775,6 @@ co_status = { if _ then ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) else - print("Thread: ", ref.Name, ref.creator, THREAD_NAME) 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 @@ -1799,9 +1797,16 @@ function multi:createHandler() return coroutine.wrap(function() local temp_start while true do - for start = #startme, 1, -1 do - temp_start = startme[start] - table.remove(startme) + -- 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, multi.unpack(temp_start.startArgs)) + -- co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) + -- table.insert(threads, temp_start) + -- yield() + -- end + while #startme>0 do + temp_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, multi.unpack(temp_start.startArgs)) co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) table.insert(threads, temp_start) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 48800f3..f5bb84b 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -180,7 +180,7 @@ function multi:newProxy(list) end end function c:getTransferable() - local multi, thread = require("multi"):init() + local multi, thread = nil, nil local cp = {} cp.name = self.name cp.funcs = copy(self._funcs) @@ -216,11 +216,7 @@ function multi:newTargetedFunction(ID, proxy, name, func, holup) -- This registe local tjq = THREAD.get(proc.Name .. "_target_rtq_" .. ID):init() return thread.hold(function() local data = tjq:peek() - if data then - print(data) - end if data and data[1] == id then - print("Got it sigh") tjq:pop() table.remove(data, 1) return multi.unpack(data) or multi.NIL @@ -300,9 +296,6 @@ function multi:newSystemThreadedProcessor(cores) return tjq:pop() end) if dat then - for i,v in pairs(dat) do - print(i,v) - end th = thread:newThread("JQ-TargetThread",function() local name = table.remove(dat, 1) local jid = table.remove(dat, 1) @@ -328,9 +321,7 @@ function multi:newSystemThreadedProcessor(cores) end) function c:pushJob(ID, name, ...) - print("pushing") local tq = THREAD.waitFor(self.Name .. "_target_tq_" .. ID):init() - --targets[ID]:push{name, jid, {...}} tq:push{name, jid, {...}} jid = jid - 1 return jid + 1 @@ -492,7 +483,6 @@ function multi:newSystemThreadedProcessor(cores) end end end).OnError(multi.error) - return c end @@ -504,31 +494,26 @@ function thread.hold(n, opt) local args local id = n.getThreadID() local name = n:getUniqueName() - print(id, name) local func = multi:newTargetedFunction(id, n, "conn_"..multi.randomString(8), function(_name) local multi, thread = require("multi"):init() local obj = _G[_name] - print("Start") local rets = {thread.hold(obj)} - print("Ring ;)") for i,v in pairs(rets) do if v.Type then rets[i] = {_self_ref_ = "parent"} end end return multi.unpack(rets) - end, true) + end) local conn - local args = {func(name)} - -- conn = handle.OnReturn(function(...) - -- ready = true - -- args = {...} - -- for i,v in pairs(args) do - -- print("DATA",i,v) - -- end - -- handle.OnReturn:Unconnect(conn) - -- end) + local args + handle = func(name) + conn = handle.OnReturn(function(...) + ready = true + args = {...} + handle.OnReturn:Unconnect(conn) + end) local ret = {thread_ref(function() if ready then @@ -537,13 +522,10 @@ function thread.hold(n, opt) end, opt)} for i,v in pairs(ret) do - print("OBJECT",v.Type) if type(v) == "table" and v._self_ref_ == "parent" then - print("assign") ret[i] = n.Parent end end - return multi.unpack(ret) else return thread_ref(n, opt) -- 2.43.0 From 660c10ec3b3ba175794fe8d7a3d052b4bc618472 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 25 Jun 2023 21:46:37 -0400 Subject: [PATCH 052/117] Removed extra bloat, proxies are portable now! --- docs/changes.md | 125 +++++---- init.lua | 155 ++++++----- integration/lanesManager/extensions.lua | 16 +- integration/lanesManager/init.lua | 20 ++ integration/lanesManager/threads.lua | 4 +- integration/loveManager/extensions.lua | 6 +- integration/loveManager/init.lua | 7 + integration/loveManager/threads.lua | 4 +- integration/lovrManager/extensions.lua | 2 +- integration/lovrManager/init.lua | 7 + integration/lovrManager/threads.lua | 2 +- integration/pseudoManager/extensions.lua | 4 +- integration/sharedExtensions/init.lua | 315 +++++------------------ 13 files changed, 293 insertions(+), 374 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index f27f5fe..9bb4824 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -74,6 +74,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. - shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. ```lua package.path = "?/init.lua;?.lua;"..package.path @@ -119,10 +120,6 @@ Added STJQ_cPXT8GOx We work! We work!!! ``` -- STP:getLoad(type) -- returns a table where the index is the threadID and the value is the number of objects[type] running on that thread. `type`: "threads" for coroutines running or nil for all other objects running. -- multi:newTargetedFunction(ID, proc, name, func, holup) -- This is used internally to handle thread.hold(proxy.conn) -- proxy.getThreadID() -- Returns the threadID of the thread that the proxy is running in -- proxy:getUniqueName() -- Gets the special name that identifies the object on the thread the proxy refers to - multi:chop(obj) -- We cannot directly interact with a local object on lanes, so we chop the object and set some globals on the thread side. Should use like: `mulit:newProxy(multi:chop(multi:newThread(function() ... end)))` - multi:newProxy(ChoppedObject) -- Creates a proxy object that allows you to interact with an object on a thread @@ -148,67 +145,105 @@ Added ``` Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. - Connection proxies break the rules a bit. Normally methods should always work on the thread side, however for connections in order to have actions work on the thread side you would call the connection using `obj._connName` instead of calling `obj.connName`. This allows you to have more control over connection events. See example below: + Proxies can also be shared between threads, just remember to use proxy:getTransferable() before transferring and proxy:init() on the other end. (We need to avoid copying over coroutines) + + The work done with proxies negates the usage of multi:newSystemThreadedConnection(), the only difference is you lose the metatables from connections. + + You cannot connect directly to a proxy connection on the non proxy thread, you can however use proxy_conn:Hold() or thread.hold(proxy_conn) to emulate this, see below. + ```lua package.path = "?/init.lua;?.lua;"..package.path - multi, thread = require("multi"):init({print=true}) + multi, thread = require("multi"):init({print=true, warn=true, error=true}) THREAD, GLOBAL = require("multi.integration.lanesManager"):init() stp = multi:newSystemThreadedProcessor(8) - alarm = stp:newAlarm(3) + tloop = stp:newTLoop(nil, 1) - -- This doesn't work since this event has already been subscribed to internally on the thread to get thread.hold(alarm.OnRing) to work. But as many events to alarm.OnRing can be made! - thread:newThread(function() - print("Hold on proxied connection", thread.hold(alarm._OnRing)) - end) + multi:newSystemThread("Testing proxy copy",function(tloop) + local function tprint (tbl, indent) + if not indent then indent = 0 end + for k, v in pairs(tbl) do + formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + print(formatting) + tprint(v, indent+1) + else + print(formatting .. tostring(v)) + end + end + end + local multi, thread = require("multi"):init() + tloop = tloop:init() + print("tloop type:",tloop.Type) + print("Testing proxies on other threads") + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") + end + end) + tloop.OnLoop(function(a) + print(THREAD_NAME, "Got loop...") + end) + multi:mainloop() + end, tloop:getTransferable()).OnError(multi.error) - alarm.OnRing(function(a) - print("OnRing",a, THREAD_NAME, THREAD_ID) - end) - - print("alarm.OnRing", alarm.OnRing.Type) - print("alarm._OnRing", alarm._OnRing.Type) + print("tloop", tloop.Type) thread:newThread(function() - print("Hold on proxied no proxy connection", thread.hold(alarm.OnRing)) + print("Holding...") + thread.hold(tloop.OnLoop) + print("Held on proxied no proxy connection 1") + end).OnError(print) + + thread:newThread(function() + tloop.OnLoop:Hold() + print("held on proxied no proxy connection 2") + end) + + tloop.OnLoop(function() + print("OnLoop",THREAD_NAME) end) thread:newThread(function() - print("Hold on proxied no proxy connection", thread.hold(alarm.OnRing)) - end) - - -- This doesn't work since this event has already been subscribed to internally on the thread to get thread.hold(alarm.OnRing) to work. But as many events to alarm.OnRing can be made! - thread:newThread(function() - print("Hold on proxied connection", thread.hold(alarm._OnRing)) - end) - - alarm._OnRing(function(a) - print("_OnRing",a, THREAD_NAME, THREAD_ID) - a:Reset(1) - end) + while true do + tloop.OnLoop:Hold() + print("OnLoop",THREAD_NAME) + end + end).OnError(multi.error) multi:mainloop() ``` Output: ``` - INFO: Integrated Lanes Threading! - alarm.OnRing connector - alarm._OnRing proxy - _OnRing table: 025EB128 STJQ_cjKsEZHg 1 <-- This can change each time you run this example! - OnRing table: 018BC0C0 MAIN_THREAD 0 - Hold on proxied no proxy connection table: 018BC0C0 nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil - Hold on proxied no proxy connection table: 018BC0C0 nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil - _OnRing table: 025EB128 STJQ_cjKsEZHg 1 - OnRing table: 018BC0C0 MAIN_THREAD 0 - _OnRing table: 025EB128 STJQ_cjKsEZHg 1 - OnRing table: 018BC0C0 MAIN_THREAD 0 + INFO: Integrated Lanes Threading! 1 + tloop proxy + Holding... + tloop type: proxy + Testing proxies on other threads + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + OnLoop MAIN_THREAD + Testing proxy copy Loopy + Held on proxied no proxy connection 1 + held on proxied no proxy connection 2 + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + Testing proxy copy Loopy + OnLoop MAIN_THREAD + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... - ... (Will repeat ever second now) - _OnRing table: 025EB128 STJQ_cjKsEZHg 1 + ... (Will repeat every second) - OnRing table: 018BC0C0 MAIN_THREAD 0 + Testing proxy copy Loopy + OnLoop MAIN_THREAD + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + + ... ``` The proxy version can only subscribe to events on the proxy thread, which means that connection metamethods will not work with the proxy version (`_OnRing` on the non proxy thread side), but the (`OnRing`) version will work. Cleverly handling the proxy thread and the non proxy thread will allow powerful connection logic. Also this is not a full system threaded connection. **Proxies should only be used between 2 threads!** To keep things fast I'm using simple queues to transfer data. There is no guarantee that things will work! @@ -222,6 +257,7 @@ Added - proxyStep = STP:newStep(...) - proxyTStep = STP:newTStep(...) - proxyThread = STP:newThread(...) + - proxyService = STP:newService(...) - threadedFunction = STP:newFunction(...) Unique: @@ -449,6 +485,7 @@ Removed Fixed --- +- Issue with luajit w/5.2 compat breaking with coroutine.running(), fixed the script to properly handle so thread.isThread() returns as expected! - Issue with coroutine based threads where they weren't all being scheduled due to a bad for loop. Replaced with a while to ensure all threads are consumed properly. If a thread created a thread that created a thread that may or may not be on the same process, things got messed up due to the original function not being built with these abstractions in mind. - Issue with thread:newFunction() where a threaded function will keep a record of their returns and pass them to future calls of the function. - Issue with multi:newTask(func) not properly handling tasks to be removed. Now uses a thread internally to manage things. diff --git a/init.lua b/init.lua index d67cdb2..7f3d0ec 100644 --- a/init.lua +++ b/init.lua @@ -33,6 +33,7 @@ local threadManager local __CurrentConnectionThread multi.unpack = table.unpack or unpack +multi.pack = table.pack or function(...) return {...} end if table.unpack then unpack = table.unpack end @@ -81,6 +82,7 @@ multi.TSTEP = "tstep" multi.THREAD = "thread" multi.SERVICE = "service" multi.PROXY = "proxy" +multi.THREADEDFUNCTION = "threaded_function" if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} @@ -133,9 +135,7 @@ function multi.Stop() mainloopActive = false end -local function pack(...) - return {...} -end +local pack = multi.pack --Processor local priorityTable = {[false]="Disabled",[true]="Enabled"} @@ -470,6 +470,10 @@ function multi:newConnection(protect,func,kill) return temp end + c.Hold = thread:newFunction(function(self) + return thread.hold(self) + end, true) + c.connect=c.Connect c.GetConnection=c.getConnection c.HasConnections = c.hasConnections @@ -487,24 +491,6 @@ function multi:newConnection(protect,func,kill) return c end -multi.enableOptimization = multi:newConnection() -multi.optConn = multi:newConnection(true) -multi.optConn(function(msg) - table.insert(optimization_stats, msg) -end) - -function multi:getOptimizationConnection() - return multi.optConn -end - -function multi:getOptimizationStats() - return optimization_stats -end - -function multi:isFindingOptimizing() - return find_optimization -end - -- Used with ISO Threads local function isolateFunction(func, env) if setfenv then @@ -637,6 +623,7 @@ function multi:isDone() end function multi:create(ref) + ref.UID = "U"..multi.randomString(12) self.OnObjectCreated:Fire(ref, self) return self end @@ -686,10 +673,6 @@ function multi:newBase(ins) return c end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() -multi.OnLoad = multi:newConnection(nil,nil,true) -ignoreconn = false function multi:newTimer() local c={} c.Type=multi.TIMER @@ -740,7 +723,7 @@ function multi:newEvent(task, func) task=func return self end - c.OnEvent = self:newConnection():fastMode() + c.OnEvent = self:newConnection() if func then c.OnEvent(func) end @@ -767,7 +750,7 @@ function multi:newUpdater(skip, func) skip=n return self end - c.OnUpdate = self:newConnection():fastMode() + c.OnUpdate = self:newConnection() c:setName(c.Type) if func then c.OnUpdate(func) @@ -803,7 +786,7 @@ function multi:newAlarm(set, func) t = clock() return self end - c.OnRing = self:newConnection():fastMode() + c.OnRing = self:newConnection() function c:Pause() count = clock() self.Parent.Pause(self) @@ -833,7 +816,7 @@ function multi:newLoop(func, notime) end end - c.OnLoop = self:newConnection():fastMode() + c.OnLoop = self:newConnection() if func then c.OnLoop(func) @@ -881,9 +864,9 @@ function multi:newStep(start,reset,count,skip) return true end c.Reset=c.Resume - c.OnStart = self:newConnection():fastMode() - c.OnStep = self:newConnection():fastMode() - c.OnEnd = self:newConnection():fastMode() + c.OnStart = self:newConnection() + c.OnStep = self:newConnection() + c.OnEnd = self:newConnection() function c:Break() self.Active=nil return self @@ -904,40 +887,49 @@ function multi:newStep(start,reset,count,skip) return c end -function multi:newTLoop(func,set) +function multi:newTLoop(func, set) local c=self:newBase() c.Type=multi.TLOOP c.set=set or 0 c.timer=self:newTimer() c.life=0 c:setPriority("Low") + function c:Act() - if self.timer:Get()>=self.set then + if self.timer:Get() >= self.set then self.life=self.life+1 self.timer:Reset() - self.OnLoop:Fire(self,self.life) + self.OnLoop:Fire(self, self.life) return true end end + function c:Set(set) self.set = set end + function c:Resume() self.Parent.Resume(self) self.timer:Resume() return self end + function c:Pause() self.timer:Pause() self.Parent.Pause(self) return self end - c.OnLoop = self:newConnection():fastMode() + + c.OnLoop = self:newConnection() + if func then c.OnLoop(func) end + c:setName(c.Type) + self:create(c) + return c end @@ -1144,6 +1136,7 @@ function multi:newProcessor(name, nothread) end table.insert(processes,c) + self:create(c) return c end @@ -1209,7 +1202,7 @@ function multi:getTasks() end function thread.request(t,cmd,...) - thread.requests[t.thread] = {cmd,{...}} + thread.requests[t.thread] = {cmd, multi.pack(...)} end function thread.getRunningThread() @@ -1248,14 +1241,18 @@ local function conn_test(conn) local args local func = function(...) ready = true - args = {...} + args = multi.pack(...) end local ref = conn(func) return function() if ready then conn:Unconnect(ref) - return multi.unpack(args) or multi.NIL + if #args==0 then + return multi.NIL + else + return multi.unpack(args) + end end end end @@ -1267,7 +1264,7 @@ function thread.chain(...) end end -function thread.hold(n,opt) +function thread.hold(n, opt) thread._Requests() local opt = opt or {} if type(opt)=="table" then @@ -1286,8 +1283,10 @@ function thread.hold(n,opt) return yield(CMD, t_sleep, n or 0, nil, interval) elseif type(n) == "table" and n.Type == multi.CONNECTOR then return yield(CMD, t_hold, conn_test(n), nil, interval) + elseif type(n) == "table" and n.Hold ~= nil then + return n:Hold(opt) elseif type(n) == "function" then - return yield(CMD, t_hold, n or dFunc, nil, interval) + return yield(CMD, t_hold, n, nil, interval) else multi.error("Invalid argument passed to thread.hold(...)!") end @@ -1318,11 +1317,12 @@ function thread.yield() end function thread.isThread() - if _VERSION~="Lua 5.1" then - local a,b = running() + local a,b = running() + if b then + -- We are dealing with luajit compat or 5.2+ return not(b) else - return running()~=nil + return a~=nil end end @@ -1345,7 +1345,7 @@ function thread.waitFor(name) end local function cleanReturns(...) - local returns = {...} + local returns = multi.pack(...) local rets = {} local ind = 0 for i=#returns,1,-1 do @@ -1392,7 +1392,7 @@ function thread:newFunctionBase(generator, holdme) end) else while not rets and not err do - multi:getCurrentProcess():getHandler()() + multi:uManager() end local g = rets rets = nil @@ -1416,7 +1416,7 @@ function thread:newFunctionBase(generator, holdme) } end local t = generator(...) - t.OnDeath(function(...) rets = {...} end) + t.OnDeath(function(...) rets = multi.pack(...) end) t.OnError(function(self,e) err = e end) if holdme then return wait() @@ -1444,15 +1444,14 @@ function thread:newFunctionBase(generator, holdme) return temp end setmetatable(tfunc, tfunc) + tfunc.Type = multi.THREADEDFUNCTION return tfunc end end function thread:newFunction(func, holdme) return thread:newFunctionBase(function(...) - local th = thread:newThread("Free Threaded Function Handler", func, ...) - th.creator = debug.getinfo(2).name - return th + return thread:newThread("Free Threaded Function Handler", func, ...) end, holdme)() end @@ -1492,12 +1491,12 @@ function thread:newProcessor(name) function proc.Start() Active = true - return c + return proc end function proc.Stop() Active = false - return c + return proc end function proc:Destroy() @@ -1515,6 +1514,8 @@ function thread:newProcessor(name) end end) end) + + self:create(proc) return proc end @@ -1534,7 +1535,7 @@ function thread:newThread(name, func, ...) end local c={nil,nil,nil,nil,nil,nil,nil} c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} - c.startArgs = {...} + c.startArgs = multi.pack(...) c.ref={} c.Name=name c.thread=create(func) @@ -1797,14 +1798,6 @@ function multi:createHandler() return coroutine.wrap(function() 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, multi.unpack(temp_start.startArgs)) - -- co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) - -- table.insert(threads, temp_start) - -- yield() - -- end while #startme>0 do temp_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, multi.unpack(temp_start.startArgs)) @@ -1929,7 +1922,7 @@ function multi:newService(func) -- Priority managed threads return c end - multi.create(multi,c) + self:create(c) return c end @@ -2338,7 +2331,7 @@ end function multi.print(...) if multi.defaultSettings.print then local t = {} - for i,v in pairs({...}) do t[#t+1] = tostring(v) end + for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[94mINFO:\x1b[0m " .. table.concat(t," ") .. "\n") end end @@ -2346,7 +2339,7 @@ end function multi.warn(...) if multi.defaultSettings.warn then local t = {} - for i,v in pairs({...}) do t[#t+1] = tostring(v) end + for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[93mWARNING:\x1b[0m " .. table.concat(t," ") .. "\n") end end @@ -2363,7 +2356,7 @@ end function multi.success(...) local t = {} - for i,v in pairs({...}) do t[#t+1] = tostring(v) end + for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") end @@ -2384,6 +2377,29 @@ function os.exit(n) _os(n) end +multi.OnObjectCreated=multi:newConnection() +ignoreconn = false +multi.OnObjectDestroyed=multi:newConnection() +multi.OnLoad = multi:newConnection(nil,nil,true) + +multi.enableOptimization = multi:newConnection() +multi.optConn = multi:newConnection(true) +multi.optConn(function(msg) + table.insert(optimization_stats, msg) +end) + +function multi:getOptimizationConnection() + return multi.optConn +end + +function multi:getOptimizationStats() + return optimization_stats +end + +function multi:isFindingOptimizing() + return find_optimization +end + multi.OnError=multi:newConnection() multi.OnPreLoad = multi:newConnection() multi.OnExit = multi:newConnection(nil,nil,true) @@ -2403,11 +2419,12 @@ function multi:getHandler() end multi:newThread("Task Handler", function() - local check = function() - return table.remove(tasks) - end while true do - thread.hold(check)() + if #tasks > 0 then + table.remove(tasks)() + else + thread.yield() + end end end).OnError(multi.error) diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 94b98c1..f76378e 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -113,7 +113,7 @@ function multi:newSystemThreadedJobQueue(n) return self end function c:pushJob(name,...) - queueJob:push{name,jid,{...}} + queueJob:push{name,jid,multi.pack(...)} jid = jid + 1 return jid-1 end @@ -132,12 +132,16 @@ function multi:newSystemThreadedJobQueue(n) local rets link = c.OnJobCompleted(function(jid,...) if id==jid then - rets = {...} + rets = multi.pack(...) end end) return thread.hold(function() if rets then - return multi.unpack(rets) or multi.NIL + if #rets == 0 then + return multi.NIL + else + return multi.unpack(rets) + end end end) end, holup), name @@ -171,7 +175,7 @@ function multi:newSystemThreadedJobQueue(n) local name = table.remove(dat, 1) local jid = table.remove(dat, 1) local args = table.remove(dat, 1) - queueReturn:push{jid, funcs[name](multi.unpack(args)), queue} + queueReturn:push{jid, funcs[name](args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8]), queue} end).OnError(multi.error) end end).OnError(multi.error) @@ -258,7 +262,7 @@ function multi:newSystemThreadedConnection(name) local function fire(...) for _, link in pairs(c.links) do - link:push {c.TRIG, {...}} + link:push {c.TRIG, multi.pack(...)} end end @@ -286,7 +290,7 @@ function multi:newSystemThreadedConnection(name) --- ^^^ This will only exist in the init thread function c:Fire(...) - local args = {...} + local args = multi.pack(...) if self.CID == THREAD_ID then -- Host Call for _, link in pairs(self.links) do link:push {self.TRIG, args} diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 147488a..8a8318c 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -98,6 +98,13 @@ function multi:newSystemThread(name, func, ...) globals = globe, priority = c.priority },function(...) + local profi + + if multi_settings.debug then + profi = require("proFI") + profi:start() + end + multi, thread = require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") require("multi.integration.sharedExtensions") @@ -105,6 +112,12 @@ function multi:newSystemThread(name, func, ...) returns = {pcall(func, ...)} return_linda:set("returns", returns) has_error = false + if profi then + multi.OnExit(function(...) + profi:stop() + profi:writeReport("Profiling Report [".. THREAD_NAME .."].txt") + end) + end end)(...) count = count + 1 function c:getName() @@ -118,6 +131,13 @@ function multi:newSystemThread(name, func, ...) c.OnDeath = multi:newConnection() c.OnError = multi:newConnection() GLOBAL["__THREADS__"] = livingThreads + + if self.isActor then + self:create(c) + else + multi.create(multi, c) + end + return c end diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index d8a3838..3d05ebf 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -66,7 +66,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) local c = {} c.queue = __Console function c.print(...) - c.queue:send("Q", {...}) + c.queue:send("Q", multi.pack(...)) end function c.error(err) c.queue:push("Q",{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}) @@ -90,7 +90,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end function THREAD.pushStatus(...) - local args = {...} + local args = multi.pack(...) __StatusLinda:send(nil,THREAD_ID, args) end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 992a10b..738f18f 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -129,7 +129,7 @@ function multi:newSystemThreadedJobQueue(n) local rets link = c.OnJobCompleted(function(jid,...) if id==jid then - rets = {...} + rets = multi.pack(...) end end) return thread.hold(function() @@ -230,7 +230,7 @@ function multi:newSystemThreadedConnection(name) self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) function self:Fire(...) - local args = {...} + local args = multi.pack(...) if self.CID == THREAD_ID then -- Host Call for _, link in pairs(self.links) do love.thread.getChannel(link):push{self.TRIG, args} @@ -321,7 +321,7 @@ function multi:newSystemThreadedConnection(name) local function fire(...) for _, link in pairs(c.links) do - love.thread.getChannel(link):push {c.TRIG, {...}} + love.thread.getChannel(link):push {c.TRIG, multi.pack(...)} end end diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 7c5289f..0c23b6b 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -103,6 +103,13 @@ function multi:newSystemThread(name, func, ...) c.stab.returns = nil end end) + + if self.isActor then + self:create(c) + else + multi.create(multi, c) + end + return c end diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 12f5471..777f811 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -98,7 +98,7 @@ end function threads.pushStatus(...) local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) - local args = {...} + local args = multi.pack(...) status_channel:push(args) end @@ -209,7 +209,7 @@ function threads.getConsole() local c = {} c.queue = love.thread.getChannel("__CONSOLE__") function c.print(...) - c.queue:push{...} + c.queue:push(multi.pack(...)) end function c.error(err) c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} diff --git a/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua index a4f231a..ddc66c6 100644 --- a/integration/lovrManager/extensions.lua +++ b/integration/lovrManager/extensions.lua @@ -118,7 +118,7 @@ function multi:newSystemThreadedJobQueue(n) local rets link = c.OnJobCompleted(function(jid,...) if id==jid then - rets = {...} + rets = multi.pack(...) link:Destroy() end end) diff --git a/integration/lovrManager/init.lua b/integration/lovrManager/init.lua index 6a64c98..bb86a8a 100644 --- a/integration/lovrManager/init.lua +++ b/integration/lovrManager/init.lua @@ -76,6 +76,13 @@ function multi:newSystemThread(name,func,...) GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} GLOBAL["__THREAD_COUNT"] = THREAD_ID THREAD_ID=THREAD_ID+1 + + if self.isActor then + self:create(c) + else + multi.create(multi, c) + end + return c end THREAD.newSystemThread = multi.newSystemThread diff --git a/integration/lovrManager/threads.lua b/integration/lovrManager/threads.lua index 12429c4..dc919ab 100644 --- a/integration/lovrManager/threads.lua +++ b/integration/lovrManager/threads.lua @@ -156,7 +156,7 @@ function threads.getConsole() local c = {} c.queue = lovr.thread.getChannel("__CONSOLE__") function c.print(...) - c.queue:push{...} + c.queue:push(multi.pack(...)) end function c.error(err) c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index bfdd982..7ad8d6c 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -96,7 +96,7 @@ function multi:newSystemThreadedJobQueue(n) end function c:pushJob(name,...) - table.insert(jobs,{name,jid,{...}}) + table.insert(jobs,{name,jid,multi.pack(...)}) jid = jid + 1 return jid-1 end @@ -121,7 +121,7 @@ function multi:newSystemThreadedJobQueue(n) local rets link = c.OnJobCompleted(function(jid,...) if id==jid then - rets = {...} + rets = multi.pack(...) end end) return thread.hold(function() diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index f5bb84b..ba5f00b 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -22,8 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -function copy(obj) - if type(obj) ~= 'table' then return obj end +local function copy(obj) + if type(obj) ~= 'table' then return obj end local res = {} for k, v in pairs(obj) do res[copy(k)] = copy(v) end return res @@ -48,34 +48,19 @@ local multi, thread = require("multi"):init() -- Create on the thread that you want to interact with, send over the handle function multi:chop(obj) + if not _G["UIDS"] then + _G["UIDS"] = {} + end local multi, thread = require("multi"):init() local list = {[0] = multi.randomString(12)} _G[list[0]] = obj for i,v in pairs(obj) do - if type(v) == "function" then + if type(v) == "function" or type(v) == "table" and v.Type == multi.THREADEDFUNCTION then table.insert(list, i) - elseif type(v) == "table" and v.Type == multi.CONNECTOR then - v.getThreadID = function() -- Special function we are adding - return THREAD_ID - end - - v.getUniqueName = function(self) - return self.__link_name - end - - local l = multi:chop(v) - v.__link_name = l[0] - v.__name = i - - table.insert(list, {i, multi:newProxy(l):init()}) + elseif type(v) == "table" and v.Type == multi.CONNECTOR then + table.insert(list, {i, multi:newProxy(multi:chop(v)):init()}) end end - table.insert(list, "isConnection") - if obj.Type == multi.CONNECTOR then - obj.isConnection = function() return true end - else - obj.isConnection = function() return false end - end return list end @@ -85,44 +70,63 @@ function multi:newProxy(list) c.name = multi.randomString(12) c.is_init = false - - function c:init(proc_name) + local multi, thread = nil, nil + function c:init() local multi, thread = nil, nil if not(c.is_init) then c.is_init = true local multi, thread = require("multi"):init() + c.proxy_link = "PL" .. multi.randomString(12) + + if multi.integration then + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD + end + + GLOBAL[c.proxy_link] = c + local function check() return self.send:pop() end + self.send = multi:newSystemThreadedQueue(self.name.."_S"):init() self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() self.funcs = list self._funcs = copy(list) self.Type = multi.PROXY self.TID = THREAD_ID - thread:newThread(function() + + thread:newThread("Proxy_Handler_" .. multi.randomString(4), function() while true do local data = thread.hold(check) if data then - local func = table.remove(data, 1) - local sref = table.remove(data, 1) - local ret - if sref then - ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} - else - ret = {_G[list[0]][func](multi.unpack(data))} - end - for i = 1,#ret do - if type(ret[i]) == "table" and getmetatable(ret[i]) then - setmetatable(ret[i],{}) -- remove that metatable, we do not need it on the other side! + -- Let's not hold the main threadloop + thread:newThread("Temp_Thread", function() + local func = table.remove(data, 1) + local sref = table.remove(data, 1) + local ret + + if sref then + ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} + else + ret = {_G[list[0]][func](multi.unpack(data))} end - if ret[i] == _G[list[0]] then - -- We cannot return itself, that return can contain bad values. - ret[i] = {_self_ref_ = true} + + for i = 1,#ret do + if type(ret[i]) == "table" and ret[i].Type ~= nil and ret[i].Type ~= multi.PROXY then + ret[i] = "\1PARENT_REF" + end + if type(ret[i]) == "table" and getmetatable(ret[i]) then + setmetatable(ret[i],nil) -- remove that metatable, we do not need it on the other side! + end + if ret[i] == _G[list[0]] then + -- We cannot return itself, that return can contain bad values. + ret[i] = "\1SELF_REF" + end end - end - table.insert(ret, 1, func) - self.recv:push(ret) + table.insert(ret, 1, func) + self.recv:push(ret) + end) end end end).OnError(multi.error) @@ -130,7 +134,7 @@ function multi:newProxy(list) else local multi, thread = require("multi"):init() local me = self - self.proc_name = proc_name + local funcs = copy(self.funcs) if multi.integration then GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD @@ -138,21 +142,13 @@ function multi:newProxy(list) self.send = THREAD.waitFor(self.name.."_S"):init() self.recv = THREAD.waitFor(self.name.."_R"):init() self.Type = multi.PROXY - for _,v in pairs(self.funcs) do + for _,v in pairs(funcs) do if type(v) == "table" then -- We have a connection v[2]:init(proc_name) - self["_"..v[1]] = v[2] + self[v[1]] = v[2] v[2].Parent = self setmetatable(v[2],getmetatable(multi:newConnection())) - self[v[1]] = multi:newConnection() - - thread:newThread(function() - while true do - local data = thread.hold(self["_"..v[1]]) - self[v[1]]:Fire(data) - end - end).OnError(multi.error) else self[v] = thread:newFunction(function(self,...) if self == me then @@ -166,8 +162,10 @@ function multi:newProxy(list) me.recv:pop() table.remove(data, 1) for i=1,#data do - if type(data[i]) == "table" and data[i]._self_ref_ then + if data[i] == "\1SELF_REF" then data[i] = me + elseif data[i] == "\1PARENT_REF" then + data[i] = me.Parent end end return multi.unpack(data) @@ -180,85 +178,31 @@ function multi:newProxy(list) end end function c:getTransferable() - local multi, thread = nil, nil local cp = {} + local multi, thread = require("multi"):init() + cp.is_init = true + cp.proxy_link = self.proxy_link cp.name = self.name cp.funcs = copy(self._funcs) - cp._funcs = copy(self._funcs) - cp.Type = self.Type - cp.init = self.init + cp.init = function(self) + local multi, thread = require("multi"):init() + if multi.integration then + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD + end + local proxy = THREAD.waitFor(self.proxy_link) + proxy.funcs = self.funcs + return proxy:init() + end return cp end + self:create(c) return c end local targets = {} local references = {} -local nFunc = 0 -function multi:newTargetedFunction(ID, proxy, name, func, holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_TFunc_"..nFunc - end - nFunc = nFunc + 1 - - multi:executeOnProcess(proxy.proc_name, function(proc, name, func) - proc.jobqueue:registerFunction(name, func) - end, name, func) - - return thread:newFunction(function(...) - return multi:executeOnProcess(proxy.proc_name, function(proc, name, ID, ...) - local multi, thread = require("multi"):init() - local id = proc:pushJob(ID, name, ...) - local rets - local tjq = THREAD.get(proc.Name .. "_target_rtq_" .. ID):init() - return thread.hold(function() - local data = tjq:peek() - if data and data[1] == id then - tjq:pop() - table.remove(data, 1) - return multi.unpack(data) or multi.NIL - end - end) - -- proc.jobqueue.OnJobCompleted(function(jid, ...) - -- if id==jid then - -- rets = {...} - -- print("Got!") - -- end - -- end) - -- return thread.hold(function() - -- if rets then - -- return multi.unpack(rets) or multi.NIL - -- end - -- end) - end, name, ID, ...) - end, holup), name -end - -multi.executeOnProcess = thread:newFunction(function(self, name, func, ...) - local queue = THREAD.get(name .. "_local_proc") - local queueR = THREAD.get(name .. "_local_return") - if queue and queueR then - local multi, thread = require("multi"):init() - local id = multi.randomString(8) - queue = queue:init() - queueR = queueR:init() - queue:push({func, id, ...}) - return thread.hold(function() - local data = queueR:peek() - if data and data[1] == id then - queueR:pop() - table.remove(data, 1) - return multi.unpack(data) or multi.NIL - end - end) - else - return nil, "Unable to find a process queue with name: '" .. name .. "'" - end -end, true) - local jid = -1 function multi:newSystemThreadedProcessor(cores) @@ -279,69 +223,16 @@ function multi:newSystemThreadedProcessor(cores) c.OnObjectCreated = multi:newConnection() c.parent = self c.jobqueue = multi:newSystemThreadedJobQueue(c.cores) - c.local_cmd = multi:newSystemThreadedQueue(name .. "_local_proc"):init() - c.local_cmd_return = multi:newSystemThreadedQueue(name .. "_local_return"):init() - - c.jobqueue:registerFunction("STP_enable_targets",function(name) - local multi, thread = require("multi"):init() - local qname = name .. "_tq_" .. THREAD_ID - local rqname = name .. "_rtq_" .. THREAD_ID - - local tjq = multi:newSystemThreadedQueue(qname):init() - local trq = multi:newSystemThreadedQueue(rqname):init() - multi:newThread("TargetedJobHandler", function() - local th - while true do - local dat = thread.hold(function() - return tjq:pop() - end) - if dat then - th = thread:newThread("JQ-TargetThread",function() - local name = table.remove(dat, 1) - local jid = table.remove(dat, 1) - local func = _G[name] - local args = table.remove(dat, 1) - th.OnError(function(self,err) - -- We want to pass this to the other calling thread incase - trq:push{jid, err} - end) - trq:push{jid, func(multi.unpack(args))} - end) - end - end - end).OnError(multi.error) - end) - - c.jobqueue:registerFunction("STP_GetThreadCount",function() - return _G["__THREADS"] - end) - - c.jobqueue:registerFunction("STP_GetTaskCount",function() - return _G["__TASKS"] - end) function c:pushJob(ID, name, ...) local tq = THREAD.waitFor(self.Name .. "_target_tq_" .. ID):init() - tq:push{name, jid, {...}} + tq:push{name, jid, multi.pack(...)} jid = jid - 1 return jid + 1 end - c.jobqueue:doToAll(function(name) - STP_enable_targets(name) - _G["__THREADS"] = 0 - _G["__TASKS"] = 0 - end, name.."_target") - c.jobqueue:registerFunction("packObj",function(obj) local multi, thread = require("multi"):init() - obj.getThreadID = function() -- Special functions we are adding - return THREAD_ID - end - - obj.getUniqueName = function(self) - return self.__link_name - end local list = multi:chop(obj) obj.__link_name = list[0] @@ -354,14 +245,12 @@ function multi:newSystemThreadedProcessor(cores) c.spawnThread = c.jobqueue:newFunction("__spawnThread__", function(name, func, ...) local multi, thread = require("multi"):init() local obj = thread:newThread(name, func, ...) - _G["__THREADS"] = _G["__THREADS"] + 1 return packObj(obj) end, true) c.spawnTask = c.jobqueue:newFunction("__spawnTask__", function(obj, func, ...) local multi, thread = require("multi"):init() local obj = multi[obj](multi, func, ...) - _G["__TASKS"] = _G["__TASKS"] + 1 return packObj(obj) end, true) @@ -372,12 +261,13 @@ function multi:newSystemThreadedProcessor(cores) "newEvent", "newAlarm", "newStep", - "newTStep" + "newTStep", + "newService" } for _, method in pairs(implement) do c[method] = function(self, ...) - proxy = self.spawnTask(method, ...):init(self.Name) + proxy = self.spawnTask(method, ...):init() references[proxy] = self return proxy end @@ -467,68 +357,5 @@ function multi:newSystemThreadedProcessor(cores) return loads end, true) - local check = function() - return c.local_cmd:pop() - end - thread:newThread(function() - while true do - local data = thread.hold(check) - if data then - thread:newThread(function() - local func = table.remove(data, 1) - local id = table.remove(data, 1) - local ret = {id, func(c, multi.unpack(data))} - c.local_cmd_return:push(ret) - end).OnError(multi.error) - end - end - end).OnError(multi.error) return c end - --- Modify thread.hold to handle proxies -local thread_ref = thread.hold -function thread.hold(n, opt) - if type(n) == "table" and n.Type == multi.PROXY and n.isConnection() then - local ready = false - local args - local id = n.getThreadID() - local name = n:getUniqueName() - local func = multi:newTargetedFunction(id, n, "conn_"..multi.randomString(8), function(_name) - local multi, thread = require("multi"):init() - local obj = _G[_name] - local rets = {thread.hold(obj)} - for i,v in pairs(rets) do - if v.Type then - rets[i] = {_self_ref_ = "parent"} - end - end - return multi.unpack(rets) - end) - - local conn - local args - handle = func(name) - conn = handle.OnReturn(function(...) - ready = true - args = {...} - handle.OnReturn:Unconnect(conn) - end) - - local ret = {thread_ref(function() - if ready then - return multi.unpack(args) or multi.NIL - end - end, opt)} - - for i,v in pairs(ret) do - if type(v) == "table" and v._self_ref_ == "parent" then - ret[i] = n.Parent - end - end - return multi.unpack(ret) - else - return thread_ref(n, opt) - end -end - -- 2.43.0 From b2569118a2df18a2c2d97f2f564efea496b40d32 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Fri, 30 Jun 2023 16:14:21 -0400 Subject: [PATCH 053/117] Started work on the debugManager --- init.lua | 98 +++++++++++++++++++------ integration/debugManager/init.lua | 37 ++++++++++ integration/lanesManager/extensions.lua | 1 + integration/lanesManager/init.lua | 4 +- integration/sharedExtensions/init.lua | 28 ------- 5 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 integration/debugManager/init.lua diff --git a/init.lua b/init.lua index 7f3d0ec..e26d4f7 100644 --- a/init.lua +++ b/init.lua @@ -217,7 +217,7 @@ function multi:newConnection(protect,func,kill) end end, __mod = function(obj1, obj2) -- % - local cn = multi:newConnection() + local cn = self:newConnection() if type(obj1) == "function" and type(obj2) == "table" then obj2(function(...) cn:Fire(obj1(...)) @@ -228,7 +228,7 @@ function multi:newConnection(protect,func,kill) return cn end, __concat = function(obj1, obj2) -- .. - local cn = multi:newConnection() + local cn = self:newConnection() local ref if type(obj1) == "function" and type(obj2) == "table" then cn(function(...) @@ -260,7 +260,7 @@ function multi:newConnection(protect,func,kill) return cn end, __add = function(c1,c2) -- Or - local cn = multi:newConnection() + local cn = self:newConnection() c1(function(...) cn:Fire(...) end) @@ -270,7 +270,7 @@ function multi:newConnection(protect,func,kill) return cn end, __mul = function(c1,c2) -- And - local cn = multi:newConnection() + local cn = self:newConnection() local ref1, ref2 if c1.__hasInstances == nil then cn.__hasInstances = {2} @@ -531,8 +531,8 @@ function multi:SetTime(n) c.timer:Start() c.set=n c.link=self - c.OnTimedOut = multi:newConnection() - c.OnTimerResolved = multi:newConnection() + c.OnTimedOut = self:newConnection() + c.OnTimerResolved = self:newConnection() self._timer=c.timer function c:Act() if self.timer:Get()>=self.set then @@ -647,8 +647,8 @@ function multi:newBase(ins) c.func={} c.funcTM={} c.funcTMR={} - c.OnBreak = multi:newConnection() - c.OnPriorityChanged = multi:newConnection() + c.OnBreak = self:newConnection() + c.OnPriorityChanged = self:newConnection() c.TID = _tid c.Act=function() end c.Parent=self @@ -1050,7 +1050,7 @@ end local sandcount = 1 -function multi:newProcessor(name, nothread) +function multi:newProcessor(name, nothread, priority) local c = {} setmetatable(c,{__index = multi}) local name = name or "Processor_" .. sandcount @@ -1062,9 +1062,14 @@ function multi:newProcessor(name, nothread) c.threads = {} c.startme = {} c.parent = self - c.OnObjectCreated = multi:newConnection() + c.OnObjectCreated = self:newConnection() - local handler = c:createHandler(c) + local handler + if priority then + handler = c:createPriorityHandler(c) + else + handler = c:createHandler(c) + end if not nothread then -- Don't create a loop if we are triggering this manually c.process = self:newLoop(function() @@ -1078,7 +1083,7 @@ function multi:newProcessor(name, nothread) c.process.PID = sandcount c.OnError = c.process.OnError else - c.OnError = multi:newConnection() + c.OnError = self:newConnection() end c.OnError(multi.error) @@ -1422,16 +1427,16 @@ function thread:newFunctionBase(generator, holdme) return wait() end local temp = { - OnStatus = multi:newConnection(true), - OnError = multi:newConnection(true), - OnReturn = multi:newConnection(true), + OnStatus = multi:getCurrentProcess():newConnection(true), + OnError = multi:getCurrentProcess():newConnection(true), + OnReturn = multi:getCurrentProcess():newConnection(true), isTFunc = true, wait = wait, getReturns = function() return multi.unpack(rets) end, connect = function(f) - local tempConn = multi:newConnection(true) + local tempConn = multi:getCurrentProcess():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 @@ -1455,13 +1460,18 @@ function thread:newFunction(func, holdme) end, holdme)() end -function thread:newProcessor(name) +function thread:newProcessor(name, nothread, priority) -- Inactive proxy proc local proc = multi:getCurrentProcess():newProcessor(name, true) local thread_proc = multi:getCurrentProcess():newProcessor(name).Start() local Active = true - local handler = thread_proc:getHandler() + local handler + if priority then + handler = thread_proc:createPriorityHandler(c) + else + handler = thread_proc:createHandler(c) + end function proc:getThreads() return thread_proc.threads @@ -1545,9 +1555,16 @@ function thread:newThread(name, func, ...) 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) + c.isError = false + + if self.Type == multi.PROCESS then + c.OnError = self:newConnection(true,nil,true) + c.OnDeath = self:newConnection(true,nil,true) + else + c.OnError = threadManager:newConnection(true,nil,true) + c.OnDeath = threadManager:newConnection(true,nil,true) + end + c.OnError(multi.error) function c:getName() @@ -1619,7 +1636,11 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 - multi:getCurrentProcess():create(c) + if self.Type == multi.PROCESS then + self:create(c) + else + threadManager:create(c) + end c.creationTime = clock() return c end @@ -1820,6 +1841,31 @@ function multi:createHandler() end) end +function multi:createPriorityHandler() + local threads, startme = self.threads, self.startme + return coroutine.wrap(function() + local temp_start + while true do + while #startme>0 do + temp_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, multi.unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) + table.insert(threads, temp_start) + 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 + end + yield() + end + end) +end + function multi:newService(func) -- Priority managed threads local c = {} c.Type = multi.SERVICE @@ -2247,7 +2293,7 @@ function multi:benchMark(sec,p,pt) c=c+1 end end) - temp.OnBench = multi:newConnection() + temp.OnBench = self:newConnection() temp:setPriority(p or 1) return temp end @@ -2412,7 +2458,11 @@ else getmetatable(multi.m.sentinel).__gc = multi.m.onexit end -threadManager = multi:newProcessor("Global_Thread_Manager").Start() +threadManager = multi:newProcessor("Global_Thread_Manager", nil, true).Start() + +function multi:getThreadManagerProcess() + return threadManager +end function multi:getHandler() return threadManager:getHandler() diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua new file mode 100644 index 0000000..eae78be --- /dev/null +++ b/integration/debugManager/init.lua @@ -0,0 +1,37 @@ +local multi, thread = require("multi"):init() + +local dbg = {} + +local creation_hook + +creation_hook = function(obj, process) + print("Created: ",obj.Type, "in", process.Type, process:getFullName()) + if obj.Type == multi.PROCESS then + obj.OnObjectCreated(creation_hook) + end +end + +local tmulti = multi:getThreadManagerProcess() +multi.OnObjectCreated(creation_hook) +tmulti.OnObjectCreated(creation_hook) + +--[[ + multi.ROOTPROCESS = "rootprocess" + multi.CONNECTOR = "connector" + multi.TIMEMASTER = "timemaster" + multi.PROCESS = "process" + multi.TIMER = "timer" + multi.EVENT = "event" + multi.UPDATER = "updater" + multi.ALARM = "alarm" + multi.LOOP = "loop" + multi.TLOOP = "tloop" + multi.STEP = "step" + multi.TSTEP = "tstep" + multi.THREAD = "thread" + multi.SERVICE = "service" + multi.PROXY = "proxy" + multi.THREADEDFUNCTION = "threaded_function" +]] + +return dbg \ No newline at end of file diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index f76378e..26cc459 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -133,6 +133,7 @@ function multi:newSystemThreadedJobQueue(n) link = c.OnJobCompleted(function(jid,...) if id==jid then rets = multi.pack(...) + c.OnJobCompleted:Unconnect(link) end end) return thread.hold(function() diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 8a8318c..0892741 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -32,7 +32,9 @@ if multi.integration then -- This allows us to call the lanes manager from suppo } end -- Step 1 get lanes -lanes = require("lanes").configure() +lanes = require("lanes").configure{ + nb_keepers = 4, +} multi.SystemThreads = {} multi.isMainThread = true diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index ba5f00b..806ce69 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -329,33 +329,5 @@ function multi:newSystemThreadedProcessor(cores) return false end - c.getLoad = thread:newFunction(function(self, tp) - local loads = {} - local func - - if tp then - func = "STP_GetThreadCount" - else - func = "STP_GetTaskCount" - end - - for i,v in pairs(self.proc_list) do - local conn - local jid = self:pushJob(v, func) - - conn = self.jobqueue.OnJobCompleted(function(id, data) - if id == jid then - table.insert(loads, {v, data}) - multi:newTask(function() - self.jobqueue.OnJobCompleted:Unconnect(conn) - end) - end - end) - end - - thread.hold(function() return #loads == c.cores end) - return loads - end, true) - return c end -- 2.43.0 From 614e032aa5d2d7597c90d808cdd85603e334e351 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:05:07 -0400 Subject: [PATCH 054/117] Testing actions, fixing bugs with lanes --- .github/workflows/nix_ci.yml | 42 ++++++++++++++++++++++ .github/workflows/win_ci.yml | 48 +++++++++++++++++++++++++ docs/changes.md | 15 ++++++++ init.lua | 26 ++++++++++---- integration/effilManager/extensions.lua | 0 integration/effilManager/init.lua | 46 ++++++++++++++++++++++++ integration/effilManager/threads.lua | 0 integration/lanesManager/extensions.lua | 14 +++++++- integration/lanesManager/init.lua | 10 +++--- integration/loveManager/extensions.lua | 32 +++++++++++++++-- integration/loveManager/init.lua | 6 ++-- integration/sharedExtensions/init.lua | 1 + tests/multi | 1 + tests/runtests.lua | 10 +++++- tests/threadtests.lua | 16 +++++---- 15 files changed, 242 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/nix_ci.yml create mode 100644 .github/workflows/win_ci.yml create mode 100644 integration/effilManager/extensions.lua create mode 100644 integration/effilManager/init.lua create mode 100644 integration/effilManager/threads.lua create mode 120000 tests/multi diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml new file mode 100644 index 0000000..ce8c1de --- /dev/null +++ b/.github/workflows/nix_ci.yml @@ -0,0 +1,42 @@ +name: build & run tests (NIX) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + build-type: [Release] # Debug + lua: ["lua 5.1", "lua 5.2", "lua 5.3", "luajit 2.1.0-beta3"] + os: ["macos-latest", "ubuntu-latest", "windows-2019"] + include: + - os: macos-latest + macos_build_target: 10.0 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Setup env + env: + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_build_target }} + run: | + pip install hererocks + hererocks lua-pkg --${{ matrix.lua }} -rlatest + source ${{github.workspace}}/lua-pkg/bin/activate + + - name: Install lanes + run: | + luarocks insatll lanes + + - name: Run Tests + run: | + lua tests/runtests.lua diff --git a/.github/workflows/win_ci.yml b/.github/workflows/win_ci.yml new file mode 100644 index 0000000..800c9fb --- /dev/null +++ b/.github/workflows/win_ci.yml @@ -0,0 +1,48 @@ +name: build & run tests (Win) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + build-type: [Release] # Debug + lua: ["lua 5.1", "lua 5.2", "lua 5.3", "luajit 2.0"] + os: ["windows-2019"] + platform: [ + {"forLua": "vs_32", "forCMake": "Win32"}, + {"forLua": "vs_64", "forCMake": "x64"}, + ] + exclude: + - lua: "luajit 2.0" + platform: {"forLua": "vs_32", "forCMake": "Win32"} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Setup env + run: | + pip install hererocks + hererocks lua-pkg --${{ matrix.lua }} -rlatest --target ${{matrix.platform.forLua}} + cmd ${{github.workspace}}\lua-pkg\bin\activate + + - name: Configure CMake & build + run: | + cmake -A ${{matrix.platform.forCMake}} -B ${{github.workspace}}\build -DLUA_INCLUDE_DIR="${{github.workspace}}/lua-pkg/include" -DLUA_LIBRARY="${{github.workspace}}/lua-pkg/lib/lua*.lib" + cmake --build ${{github.workspace}}/build --config ${{matrix.build-type}} + + - name: Test + working-directory: ${{github.workspace}}/build + env: + STRESS: 1 + run: | + ${{github.workspace}}/lua-pkg/bin/lua ../tests/lua/run_tests diff --git a/docs/changes.md b/docs/changes.md index 9bb4824..e2c9f88 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -67,6 +67,21 @@ multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() ``` +## Added New Integration: **effilManager** + +Another option for multithreading support, works just like all the other threading integrations, but uses the internals of effil and it's unique features. +- Refer to this [doc](https://www.lua.org/wshop18/Kupriyanov.pdf) to read more about it. +- Project github [page](https://github.com/effil/effil/tree/master). + +```lua +package.path = "?/init.lua;?.lua;"..package.path + +local multi, thread = require("multi"):init({print=true, warn=true, error=true}) +local THREAD, GLOBAL = require("multi.integration.effilManager"):init() + +-- Code as you would +``` + ## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! diff --git a/init.lua b/init.lua index e26d4f7..beb866d 100644 --- a/init.lua +++ b/init.lua @@ -60,6 +60,7 @@ function multi.setType(obj,t) }) end end + setmetatable(multi.DestroyedObj, { __index = function(t,k) return setmetatable({},{__index = uni,__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}) @@ -68,7 +69,8 @@ setmetatable(multi.DestroyedObj, { multi.DESTROYED = multi.DestroyedObj multi.ROOTPROCESS = "rootprocess" -multi.CONNECTOR = "connector" +multi.CONNECTOR = "connector" -- To be deprecated +multi.CONNECTION = "connector" -- To be changed to connection and replace connector (v17.x,x) multi.TIMEMASTER = "timemaster" multi.PROCESS = "process" multi.TIMER = "timer" @@ -81,8 +83,18 @@ multi.STEP = "step" multi.TSTEP = "tstep" multi.THREAD = "thread" multi.SERVICE = "service" +multi.THREADEDFUNCTION = "threaded_function" -- To be deprecated +multi.FUNCTION = "threaded_function" -- To be changed to connection and replace connector (v17.x,x) + +-- Extensions multi.PROXY = "proxy" -multi.THREADEDFUNCTION = "threaded_function" +multi.STHREAD = "s_thread" +multi.SQUEUE = "s_queue" +multi.STABLE = "s_table" +multi.SJOBQUEUE = "s_jobqueue" +multi.SCONNECTION = "s_connection" +multi.SPROCESS = "s_process" +multi.SFUNCTION = "s_function" if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} @@ -305,7 +317,7 @@ function multi:newConnection(protect,func,kill) return cn end}) - c.Type=multi.CONNECTOR + c.Type=multi.CONNECTION c.func={} c.ID=0 local protect=protect or false @@ -1286,7 +1298,7 @@ function thread.hold(n, opt) 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 == multi.CONNECTOR then + elseif type(n) == "table" and n.Type == multi.CONNECTION then return yield(CMD, t_hold, conn_test(n), nil, interval) elseif type(n) == "table" and n.Hold ~= nil then return n:Hold(opt) @@ -1367,7 +1379,7 @@ function thread.pushStatus(...) t.statusconnector:Fire(...) end -function thread:newFunctionBase(generator, holdme) +function thread:newFunctionBase(generator, holdme, TYPE) return function() local tfunc = {} tfunc.Active = true @@ -1449,7 +1461,7 @@ function thread:newFunctionBase(generator, holdme) return temp end setmetatable(tfunc, tfunc) - tfunc.Type = multi.THREADEDFUNCTION + tfunc.Type = TYPE or multi.FUNCTION return tfunc end end @@ -2043,7 +2055,7 @@ local function doOpt() 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 == multi.CONNECTOR then + elseif type(n) == "table" and n.Type == multi.CONNECTION then local rdy = function() return false end diff --git a/integration/effilManager/extensions.lua b/integration/effilManager/extensions.lua new file mode 100644 index 0000000..e69de29 diff --git a/integration/effilManager/init.lua b/integration/effilManager/init.lua new file mode 100644 index 0000000..53818e6 --- /dev/null +++ b/integration/effilManager/init.lua @@ -0,0 +1,46 @@ +local multi, thread = require("multi"):init{error=true} +multi.error("Currntly not supported!") +os.exit() +local effil = require("effil") + +-- I like some of the things that this library offers. +-- Current limitations prevent me from being able to use effil, +-- but I might fork and work on it myself. + +-- Configs +effil.allow_table_upvalues(false) + +local GLOBAL,THREAD = require("multi.integration.effilManager.threads").init() +local count = 1 +local started = false +local livingThreads = {} + +function multi:newSystemThread(name, func, ...) + local name = name or multi.randomString(16) + local rand = math.random(1, 10000000) + c = {} + c.name = name + c.Name = name + c.Id = count +end + +function THREAD:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("TempSystemThread",func,...) + end, holdme, multi.SFUNCTION)() +end + +THREAD.newSystemThread = function(...) + multi:newSystemThread(...) +end + +multi.print("Integrated Effil Threading!") +multi.integration = {} -- for module creators +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +require("multi.integration.effilManager.extensions") +return { + init = function() + return GLOBAL, THREAD + end +} \ No newline at end of file diff --git a/integration/effilManager/threads.lua b/integration/effilManager/threads.lua new file mode 100644 index 0000000..e69de29 diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 26cc459..343dd8b 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -34,6 +34,7 @@ function multi:newSystemThreadedQueue(name) local c = {} c.Name = name c.linda = lanes.linda() + c.Type = multi.SQUEUE function c:push(v) self.linda:send("Q", v) @@ -57,6 +58,8 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name] = c end + self:create(c) + return c end @@ -65,6 +68,7 @@ function multi:newSystemThreadedTable(name) local c = {} c.link = lanes.linda() c.Name = name + c.Type = multi.STABLE function c:init() return self @@ -85,12 +89,15 @@ function multi:newSystemThreadedTable(name) GLOBAL[name] = c end + self:create(c) + return c end function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 + c.Type = multi.SJOBQUEUE c.OnJobCompleted = multi:newConnection() local funcs = multi:newSystemThreadedTable():init() local queueJob = multi:newSystemThreadedQueue():init() @@ -133,7 +140,6 @@ function multi:newSystemThreadedJobQueue(n) link = c.OnJobCompleted(function(jid,...) if id==jid then rets = multi.pack(...) - c.OnJobCompleted:Unconnect(link) end end) return thread.hold(function() @@ -207,12 +213,16 @@ function multi:newSystemThreadedJobQueue(n) multi:mainloop() end,i).OnError(multi.error) end + + self:create(c) + return c end function multi:newSystemThreadedConnection(name) local name = name or multi.randomString(16) local c = {} + c.Type = multi.SCONNECTION c.CONN = 0x00 c.TRIG = 0x01 c.PING = 0x02 @@ -348,6 +358,8 @@ function multi:newSystemThreadedConnection(name) GLOBAL[name] = c end + self:create(c) + return c end require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 0892741..b33b7ba 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -32,9 +32,7 @@ if multi.integration then -- This allows us to call the lanes manager from suppo } end -- Step 1 get lanes -lanes = require("lanes").configure{ - nb_keepers = 4, -} +lanes = require("lanes").configure() multi.SystemThreads = {} multi.isMainThread = true @@ -63,7 +61,7 @@ local livingThreads = {} function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) return multi:newSystemThread("TempSystemThread",func,...) - end, holdme)() + end, holdme, multi.SFUNCTION)() end function multi:newSystemThread(name, func, ...) @@ -143,7 +141,9 @@ function multi:newSystemThread(name, func, ...) return c end -THREAD.newSystemThread = multi.newSystemThread +THREAD.newSystemThread = function(...) + multi:newSystemThread(...) +end function multi.InitSystemThreadErrorHandler() if started == true then diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 738f18f..1dd7295 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -35,6 +35,7 @@ function multi:newSystemThreadedQueue(name) local name = name or multi.randomString(16) local c = {} c.Name = name + c.Type = multi.SQUEUE local fRef = {"func",nil} function c:init() local q = {} @@ -66,33 +67,49 @@ function multi:newSystemThreadedQueue(name) end return q end + THREAD.package(name,c) + + self:create(c) + return c end function multi:newSystemThreadedTable(name) local name = name or multi.randomString(16) - local c = {} + + local c = {} + c.Name = name + c.Type = multi.STABLE + function c:init() return THREAD.createTable(self.Name) end + THREAD.package(name,c) + + self:create(c) + return c end local jqc = 1 function multi:newSystemThreadedJobQueue(n) local c = {} + c.cores = n or THREAD.getCores() c.registerQueue = {} + c.Type = multi.SJOBQUEUE c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") c.id = 0 c.OnJobCompleted = multi:newConnection() + local allfunc = 0 + function c:doToAll(func) local f = THREAD.dump(func) for i = 1, self.cores do @@ -211,13 +228,20 @@ function multi:newSystemThreadedJobQueue(n) multi:mainloop() end,jqc) end + jqc = jqc + 1 + + self:create(c) + return c end function multi:newSystemThreadedConnection(name) local name = name or multi.randomString(16) + local c = {} + + c.Type = multi.SCONNECTION c.CONN = 0x00 c.TRIG = 0x01 c.PING = 0x02 @@ -284,9 +308,11 @@ function multi:newSystemThreadedConnection(name) end return r end + c.CID = THREAD_ID c.Name = name c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + -- Locals will only live in the thread that creates the original object local ping local pong = function(link, links) @@ -317,7 +343,7 @@ function multi:newSystemThreadedConnection(name) thread.sleep(3) ping:Resume() - end,false) + end, false) local function fire(...) for _, link in pairs(c.links) do @@ -351,5 +377,7 @@ function multi:newSystemThreadedConnection(name) THREAD.package(name,c) + self:create(c) + return c end \ No newline at end of file diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 0c23b6b..a2a5d5f 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -116,10 +116,12 @@ end function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) return multi:newSystemThread("SystemThreaded Function Handler", func, ...) - end, holdme)() + end, holdme, multi.SFUNCTION)() end -THREAD.newSystemThread = multi.newSystemThread +THREAD.newSystemThread = function(...) + multi:newSystemThread(...) +end function love.threaderror(thread, errorstr) multi.print("Thread error!\n" .. errorstr) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 806ce69..c1bcfea 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -213,6 +213,7 @@ function multi:newSystemThreadedProcessor(cores) setmetatable(c,{__index = multi}) + c.Type = multi.SPROCESS c.threads = {} c.cores = cores or 8 c.Name = name diff --git a/tests/multi b/tests/multi new file mode 120000 index 0000000..5ddae4f --- /dev/null +++ b/tests/multi @@ -0,0 +1 @@ +D:/VSCWorkspace/discord-lua/multi \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index 049c26b..1a89df6 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -182,11 +182,19 @@ runTest = thread:newFunction(function() end end) -runTest().OnError(function(...) +local handle = runTest() + +handle.OnError(function(...) multi.error("Something went wrong with the test!") print(...) end) if not love then multi:mainloop() +else + local hold = thread:newFunction(function() + thread.hold(handle.OnError + handle.OnReturn) + end, true) + hold() + multi.print("Starting Threading tests!") end \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index b67a7e1..e859f22 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -109,12 +109,10 @@ multi:newThread("Scheduler Thread",function() local ready = false jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads - func = jq:newFunction("test-thread",function(a,b) THREAD.sleep(.2) return a+b end) - local count = 0 for i = 1,10 do func(i, i*3).OnReturn(function(data) @@ -134,6 +132,7 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedJobQueues: Ok") queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() + print(1) multi:newSystemThread("Test_Thread_2",function() queue2 = THREAD.waitFor("Test_Queue2"):init() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -142,7 +141,7 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end).OnError(multi.error) - + print(2) multi:newSystemThread("Test_Thread_3",function() queue2 = THREAD.waitFor("Test_Queue2"):init() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -151,32 +150,35 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end).OnError(multi.error) - + print(3) connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() a=0 connOut(function(arg) queue2:push("Main") end) + print(4) for i=1,3 do thread.sleep(.1) connOut:Fire("Test From Main Thread: "..i.."\n") end + print(5) thread.sleep(2) local count = 0 multi:newThread(function() while count < 9 do if queue2:pop() then count = count + 1 + print("Popped", count) end end end).OnError(multi.error) - + print(6) _, err = thread.hold(function() return count == 9 end,{sleep=.3}) - + print(7) if err == multi.TIMEOUT then multi.error("SystemThreadedConnections: Failed") end - + print(8) multi.success("SystemThreadedConnections: Ok") we_good = true -- 2.43.0 From cd4cc5fd2d12621c0f0c8f04c5e8ac1197c5c9e3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:10:12 -0400 Subject: [PATCH 055/117] Testing... --- .github/workflows/nix_ci.yml | 5 +++-- .github/workflows/win_ci.yml => win_ci.yml | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename .github/workflows/win_ci.yml => win_ci.yml (100%) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index ce8c1de..2bd6f16 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -12,8 +12,8 @@ jobs: fail-fast: false matrix: build-type: [Release] # Debug - lua: ["lua 5.1", "lua 5.2", "lua 5.3", "luajit 2.1.0-beta3"] - os: ["macos-latest", "ubuntu-latest", "windows-2019"] + lua: ["lua 5.1", "lua 5.2", "lua 5.3", "lua 5.4", "luajit 2.1.0-beta3"] + os: ["ubuntu-latest"] include: - os: macos-latest macos_build_target: 10.0 @@ -35,6 +35,7 @@ jobs: - name: Install lanes run: | + lua -v luarocks insatll lanes - name: Run Tests diff --git a/.github/workflows/win_ci.yml b/win_ci.yml similarity index 100% rename from .github/workflows/win_ci.yml rename to win_ci.yml -- 2.43.0 From 1064d8a1cc3e1205bfc5b2ae74c1f999f8d74eaf Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:12:23 -0400 Subject: [PATCH 056/117] fixing actions --- .github/workflows/nix_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index 2bd6f16..b3e998c 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -31,10 +31,10 @@ jobs: run: | pip install hererocks hererocks lua-pkg --${{ matrix.lua }} -rlatest - source ${{github.workspace}}/lua-pkg/bin/activate - name: Install lanes run: | + source ${{github.workspace}}/lua-pkg/bin/activate lua -v luarocks insatll lanes -- 2.43.0 From 8c987b81abf5003451192048c88bf00d5a74ca0b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:20:23 -0400 Subject: [PATCH 057/117] typo fixed --- .github/workflows/nix_ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index b3e998c..02f911d 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -35,9 +35,9 @@ jobs: - name: Install lanes run: | source ${{github.workspace}}/lua-pkg/bin/activate - lua -v - luarocks insatll lanes + luarocks install lanes - name: Run Tests run: | + source ${{github.workspace}}/lua-pkg/bin/activate lua tests/runtests.lua -- 2.43.0 From 96cc41effbfda7e810d3035341642bc842da36fe Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:25:18 -0400 Subject: [PATCH 058/117] Throw an error when things break --- .github/workflows/nix_ci.yml | 5 ----- tests/runtests.lua | 2 +- tests/threadtests.lua | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index 02f911d..d39bb2f 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -14,9 +14,6 @@ jobs: build-type: [Release] # Debug lua: ["lua 5.1", "lua 5.2", "lua 5.3", "lua 5.4", "luajit 2.1.0-beta3"] os: ["ubuntu-latest"] - include: - - os: macos-latest - macos_build_target: 10.0 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -26,8 +23,6 @@ jobs: with: python-version: '3.8' - name: Setup env - env: - MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_build_target }} run: | pip install hererocks hererocks lua-pkg --${{ matrix.lua }} -rlatest diff --git a/tests/runtests.lua b/tests/runtests.lua index 1a89df6..a9811c3 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -20,7 +20,7 @@ end The expected and actual should "match" (Might be impossible when playing with threads) This will be pushed directly to the master as tests start existing. ]] -local multi, thread = require("multi"):init{print=true,warn=true,error=false}--{priority=true} +local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true} local good = false local proc = multi:newProcessor("Test") diff --git a/tests/threadtests.lua b/tests/threadtests.lua index e859f22..79539ac 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,5 +1,5 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{}--{priority=true} +multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 local env, we_good -- 2.43.0 From 03a2f686a896015126e2bc65b5c15847847cbfb4 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:32:38 -0400 Subject: [PATCH 059/117] fixing stuff --- tests/threadtests.lua | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 79539ac..1c2bd47 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -132,7 +132,6 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedJobQueues: Ok") queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() - print(1) multi:newSystemThread("Test_Thread_2",function() queue2 = THREAD.waitFor("Test_Queue2"):init() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -141,7 +140,6 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end).OnError(multi.error) - print(2) multi:newSystemThread("Test_Thread_3",function() queue2 = THREAD.waitFor("Test_Queue2"):init() connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() @@ -150,35 +148,28 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end).OnError(multi.error) - print(3) connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() a=0 connOut(function(arg) queue2:push("Main") end) - print(4) for i=1,3 do thread.sleep(.1) connOut:Fire("Test From Main Thread: "..i.."\n") end - print(5) thread.sleep(2) local count = 0 multi:newThread(function() while count < 9 do if queue2:pop() then count = count + 1 - print("Popped", count) end end end).OnError(multi.error) - print(6) _, err = thread.hold(function() return count == 9 end,{sleep=.3}) - print(7) if err == multi.TIMEOUT then multi.error("SystemThreadedConnections: Failed") end - print(8) multi.success("SystemThreadedConnections: Ok") we_good = true @@ -188,6 +179,7 @@ end).OnError(multi.error) multi.OnExit(function(err) if not we_good then multi.error("There was an error running some tests!") + os.exit(1) else multi.success("Tests complete!") end -- 2.43.0 From 81fc7b95c922912a8c0cbd11a67590d6829f8bcd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 13:46:25 -0400 Subject: [PATCH 060/117] Fixed issue with errors not going through --- error.lua | 1 + init.lua | 1 + integration/effilManager/init.lua | 2 +- tests/threadtests.lua | 15 ++++++++------- 4 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 error.lua diff --git a/error.lua b/error.lua new file mode 100644 index 0000000..84c9b9f --- /dev/null +++ b/error.lua @@ -0,0 +1 @@ +os.exit(2) \ No newline at end of file diff --git a/init.lua b/init.lua index beb866d..4135359 100644 --- a/init.lua +++ b/init.lua @@ -2431,6 +2431,7 @@ multi.SetName = multi.setName local _os = os.exit function os.exit(n) + print("ERROR_"..n) multi.OnExit:Fire(n or 0) _os(n) end diff --git a/integration/effilManager/init.lua b/integration/effilManager/init.lua index 53818e6..f5c1277 100644 --- a/integration/effilManager/init.lua +++ b/integration/effilManager/init.lua @@ -1,6 +1,6 @@ local multi, thread = require("multi"):init{error=true} multi.error("Currntly not supported!") -os.exit() +os.exit(1) local effil = require("effil") -- I like some of the things that this library offers. diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 1c2bd47..8a96c27 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -52,7 +52,7 @@ multi:newThread("Scheduler Thread",function() queue:push("done") end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) multi.error(err) - os.exit() + os.exit(1) end) if thread.hold(function() @@ -101,7 +101,7 @@ multi:newThread("Scheduler Thread",function() if val == multi.TIMEOUT then multi.error("SystemThreadedTables: Failed") - os.exit() + os.exit(1) end multi.success("SystemThreadedTables: Ok") @@ -126,7 +126,7 @@ multi:newThread("Scheduler Thread",function() if val == multi.TIMEOUT then multi.error("SystemThreadedJobQueues: Failed") - os.exit() + os.exit(1) end multi.success("SystemThreadedJobQueues: Ok") @@ -173,13 +173,14 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedConnections: Ok") we_good = true - os.exit() + os.exit(1) end).OnError(multi.error) -multi.OnExit(function(err) +multi.OnExit(function(err_or_errorcode) + print("Final status!",err_or_errorcode) if not we_good then - multi.error("There was an error running some tests!") - os.exit(1) + multi.info("There was an error running some tests!") + return else multi.success("Tests complete!") end -- 2.43.0 From acc94ea17eef0502011b53435431e2620f80f8c1 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 16:00:37 -0400 Subject: [PATCH 061/117] Removed system threaded connections, soon to be replaced by proxies --- init.lua | 7 +- integration/loveManager/extensions.lua | 3 +- integration/pseudoManager/extensions.lua | 3 +- integration/sharedExtensions/init.lua | 38 +++---- tests/threadtests.lua | 134 ++++++++++++++++------- 5 files changed, 121 insertions(+), 64 deletions(-) diff --git a/init.lua b/init.lua index 4135359..cb24772 100644 --- a/init.lua +++ b/init.lua @@ -2389,7 +2389,7 @@ end function multi.print(...) if multi.defaultSettings.print then local t = {} - for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end + for i,v in ipairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[94mINFO:\x1b[0m " .. table.concat(t," ") .. "\n") end end @@ -2397,7 +2397,7 @@ end function multi.warn(...) if multi.defaultSettings.warn then local t = {} - for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end + for i,v in ipairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[93mWARNING:\x1b[0m " .. table.concat(t," ") .. "\n") end end @@ -2414,7 +2414,7 @@ end function multi.success(...) local t = {} - for i,v in pairs(multi.pack(...)) do t[#t+1] = tostring(v) end + for i,v in ipairs(multi.pack(...)) do t[#t+1] = tostring(v) end io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") end @@ -2431,7 +2431,6 @@ multi.SetName = multi.setName local _os = os.exit function os.exit(n) - print("ERROR_"..n) multi.OnExit:Fire(n or 0) _os(n) end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 1dd7295..32a1215 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -380,4 +380,5 @@ function multi:newSystemThreadedConnection(name) self:create(c) return c -end \ No newline at end of file +end +require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 7ad8d6c..b6a4884 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -152,4 +152,5 @@ function multi:newSystemThreadedConnection(name) conn.init = function(self) return self end GLOBAL[name or "_"] = conn return conn -end \ No newline at end of file +end +require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index c1bcfea..a4b16ef 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -22,26 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local function copy(obj) - if type(obj) ~= 'table' then return obj end - local res = {} - for k, v in pairs(obj) do res[copy(k)] = copy(v) end - return res -end - -function tprint (tbl, indent) - if not indent then indent = 0 end - for k, v in pairs(tbl) do - formatting = string.rep(" ", indent) .. k .. ": " - if type(v) == "table" then - print(formatting) - tprint(v, indent+1) - else - print(formatting .. tostring(v)) - end - end - end - local multi, thread = require("multi"):init() -- Returns a handler that allows a user to interact with an object on another thread! @@ -73,6 +53,12 @@ function multi:newProxy(list) local multi, thread = nil, nil function c:init() local multi, thread = nil, nil + local function copy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[copy(k)] = copy(v) end + return res + end if not(c.is_init) then c.is_init = true local multi, thread = require("multi"):init() @@ -132,6 +118,12 @@ function multi:newProxy(list) end).OnError(multi.error) return self else + local function copy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[copy(k)] = copy(v) end + return res + end local multi, thread = require("multi"):init() local me = self local funcs = copy(self.funcs) @@ -180,6 +172,12 @@ function multi:newProxy(list) function c:getTransferable() local cp = {} local multi, thread = require("multi"):init() + local function copy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[copy(k)] = copy(v) end + return res + end cp.is_init = true cp.proxy_link = self.proxy_link cp.name = self.name diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 8a96c27..daf75d7 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -131,53 +131,111 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedJobQueues: Ok") - queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() - multi:newSystemThread("Test_Thread_2",function() - queue2 = THREAD.waitFor("Test_Queue2"):init() - connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - connOut(function(arg) - queue2:push("Test_Thread_2") - end) - multi:mainloop() - end).OnError(multi.error) - multi:newSystemThread("Test_Thread_3",function() - queue2 = THREAD.waitFor("Test_Queue2"):init() - connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - connOut(function(arg) - queue2:push("Test_Thread_3") - end) - multi:mainloop() - end).OnError(multi.error) - connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() - a=0 - connOut(function(arg) - queue2:push("Main") - end) - for i=1,3 do - thread.sleep(.1) - connOut:Fire("Test From Main Thread: "..i.."\n") - end - thread.sleep(2) - local count = 0 - multi:newThread(function() - while count < 9 do - if queue2:pop() then - count = count + 1 + -- queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() + -- multi:newSystemThread("Test_Thread_2",function() + -- queue2 = THREAD.waitFor("Test_Queue2"):init() + -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + -- connOut(function(arg) + -- queue2:push("Test_Thread_2") + -- end) + -- multi:mainloop() + -- end).OnError(multi.error) + -- multi:newSystemThread("Test_Thread_3",function() + -- queue2 = THREAD.waitFor("Test_Queue2"):init() + -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + -- connOut(function(arg) + -- queue2:push("Test_Thread_3") + -- end) + -- multi:mainloop() + -- end).OnError(multi.error) + -- connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() + -- a=0 + -- connOut(function(arg) + -- queue2:push("Main") + -- end) + -- for i=1,3 do + -- thread.sleep(.1) + -- connOut:Fire("Test From Main Thread: "..i.."\n") + -- end + -- thread.sleep(2) + -- local count = 0 + -- multi:newThread(function() + -- while count < 9 do + -- if queue2:pop() then + -- count = count + 1 + -- end + -- end + -- end).OnError(multi.error) + -- _, err = thread.hold(function() return count == 9 end,{sleep=.3}) + -- if err == multi.TIMEOUT then + -- multi.error("SystemThreadedConnections: Failed") + -- end + -- multi.success("SystemThreadedConnections: Ok") + + local stp = multi:newSystemThreadedProcessor(8) + + local tloop = stp:newTLoop(nil, 1) + + local proxy_test = false + + multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + local multi, thread = require("multi"):init() + tloop = tloop:init() + multi.print("tloop type:",tloop.Type) + multi.print("Testing proxies on other threads") + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") end + end) + tloop.OnLoop(function(a) + print(THREAD_NAME, "Got loop...") + end) + multi:mainloop() + end, tloop:getTransferable()).OnError(multi.error) + + multi.print("tloop", tloop.Type) + multi.print("tloop.OnLoop", tloop.OnLoop.Type) + + thread:newThread(function() + multi.print("Testing holding on a proxy connection!") + thread.hold(tloop.OnLoop) + multi.print("Held on proxy connection... once") + thread.hold(tloop.OnLoop) + multi.print("Held on proxy connection... twice") + proxy_test = true + end).OnError(print) + + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") end - end).OnError(multi.error) - _, err = thread.hold(function() return count == 9 end,{sleep=.3}) - if err == multi.TIMEOUT then - multi.error("SystemThreadedConnections: Failed") + end) + + tloop.OnLoop(function() + print("OnLoop",THREAD_NAME) + end) + + t, val = thread.hold(function() + return count == 10 + end,{sleep=5}) + + if val == multi.TIMEOUT then + multi.error("SystemThreadedProcessor/Proxies: Failed") + os.exit(1) end - multi.success("SystemThreadedConnections: Ok") + + thread.sleep(2) + + multi.success("SystemThreadedProcessor: OK") we_good = true os.exit(1) end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) - print("Final status!",err_or_errorcode) if not we_good then multi.info("There was an error running some tests!") return -- 2.43.0 From 7ba642342dbd05acefe6de77e2f61feb2b952217 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 22:49:06 -0400 Subject: [PATCH 062/117] Testing love2d tests --- .github/workflows/nix_ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index d39bb2f..fdfc909 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -19,9 +19,11 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive + - uses: actions/setup-python@v2 with: python-version: '3.8' + - name: Setup env run: | pip install hererocks @@ -36,3 +38,10 @@ jobs: run: | source ${{github.workspace}}/lua-pkg/bin/activate lua tests/runtests.lua + + - name: Run Tests using LÖVE + run: | + nix-shell -p love + love tests + + -- 2.43.0 From a6fac9d1d4612576428b75a7f6534b2da3e27e87 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:00:48 -0400 Subject: [PATCH 063/117] Test love2d --- .github/workflows/love.yml | 23 +++++++++++++++++++++++ .github/workflows/nix_ci.yml | 7 ------- 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/love.yml diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml new file mode 100644 index 0000000..2415523 --- /dev/null +++ b/.github/workflows/love.yml @@ -0,0 +1,23 @@ +name: build & run tests (Win) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + fail-fast: false + runs-on: "windows-2019" + steps: + - uses: actions/checkout@v2 + - uses: nhartland/love-build@v1-beta2 + with: + app_name: 'love_test' + love_version: '11.4' + source_dir: 'test' + - name: Run Tests + run: | + ./release/love_tests.exe \ No newline at end of file diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index fdfc909..a36c4d3 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -38,10 +38,3 @@ jobs: run: | source ${{github.workspace}}/lua-pkg/bin/activate lua tests/runtests.lua - - - name: Run Tests using LÖVE - run: | - nix-shell -p love - love tests - - -- 2.43.0 From 7c95b2e8ca40574a2d608a92aee36d8f7867546b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:04:06 -0400 Subject: [PATCH 064/117] Use later love-build --- .github/workflows/love.yml | 4 ++-- .github/workflows/nix_ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 2415523..3de4397 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -1,4 +1,4 @@ -name: build & run tests (Win) +name: Build & Run tests (Windows/Love2d) on: push: @@ -13,7 +13,7 @@ jobs: runs-on: "windows-2019" steps: - uses: actions/checkout@v2 - - uses: nhartland/love-build@v1-beta2 + - uses: nhartland/love-build@v1-beta4 with: app_name: 'love_test' love_version: '11.4' diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index a36c4d3..386cd50 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -1,4 +1,4 @@ -name: build & run tests (NIX) +name: Build & Run tests Ubuntu on: push: -- 2.43.0 From 721571d1a46731a2808cd5e3cae3b76edd203e6b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:05:37 -0400 Subject: [PATCH 065/117] Use ubuntu for build --- .github/workflows/love.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 3de4397..86b63e8 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -1,4 +1,4 @@ -name: Build & Run tests (Windows/Love2d) +name: Build & Run tests (Ubuntu/Love2d) on: push: @@ -10,7 +10,7 @@ jobs: build: strategy: fail-fast: false - runs-on: "windows-2019" + runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v2 - uses: nhartland/love-build@v1-beta4 @@ -20,4 +20,4 @@ jobs: source_dir: 'test' - name: Run Tests run: | - ./release/love_tests.exe \ No newline at end of file + ./release/love_tests \ No newline at end of file -- 2.43.0 From 0a72f16e68e86080d6fbdfb21234c3f19e503488 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:07:30 -0400 Subject: [PATCH 066/117] Fixed path --- .github/workflows/love.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 86b63e8..fe93cd8 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -17,7 +17,7 @@ jobs: with: app_name: 'love_test' love_version: '11.4' - source_dir: 'test' + source_dir: './tests' - name: Run Tests run: | ./release/love_tests \ No newline at end of file -- 2.43.0 From 06580e0bfa0dd0692c187832fd15995e89bb31f8 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:14:24 -0400 Subject: [PATCH 067/117] Use appimage --- .github/workflows/love.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index fe93cd8..c36221f 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -1,4 +1,4 @@ -name: Build & Run tests (Ubuntu/Love2d) +name: Build & Run tests Ubuntu - Love2d on: push: @@ -12,12 +12,11 @@ jobs: fail-fast: false runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v2 - - uses: nhartland/love-build@v1-beta4 - with: - app_name: 'love_test' - love_version: '11.4' - source_dir: './tests' + - name: Install love2d + run: | + apt install fuse + wget https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage -O love.AppImage + chmod +x love.AppImage - name: Run Tests run: | - ./release/love_tests \ No newline at end of file + ./love.AppImage tests \ No newline at end of file -- 2.43.0 From 8a83c617fa3ead441fe060e980c45f08489c51ba Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:16:03 -0400 Subject: [PATCH 068/117] use sudo --- .github/workflows/love.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index c36221f..a59560a 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -1,4 +1,4 @@ -name: Build & Run tests Ubuntu - Love2d +name: Build & Run tests Love2d on: push: @@ -14,9 +14,9 @@ jobs: steps: - name: Install love2d run: | - apt install fuse + sudo apt install fuse wget https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage -O love.AppImage - chmod +x love.AppImage + sudo chmod +x love.AppImage - name: Run Tests run: | ./love.AppImage tests \ No newline at end of file -- 2.43.0 From 883bdb9830bb69fecc4b9b8c50cd81b47216e6d0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:20:00 -0400 Subject: [PATCH 069/117] No window for love2d --- tests/conf.lua | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/tests/conf.lua b/tests/conf.lua index 5e75c12..3dcc3c6 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -3,23 +3,7 @@ function love.conf(t) t.version = "12.0" -- The LOVE version this game was made for (string) t.console = true -- Attach a console (boolean, Windows only) - t.window.title = "MultiThreadTest" -- The window title (string) - t.window.icon = nil -- Filepath to an image to use as the window's icon (string) - t.window.width = 1280 -- The window width (number) - t.window.height = 720 -- The window height (number) - t.window.borderless = false -- Remove all border visuals from the window (boolean) - t.window.resizable = true -- Let the window be user-resizable (boolean) - t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) - t.window.minheight = 1 -- Minimum window height if the window is resizable (number) - t.window.fullscreen = false -- Enable fullscreen (boolean) - t.window.fullscreentype = "desktop" -- Standard fullscreen or desktop fullscreen mode (string) - t.window.vsync = false -- Enable vertical sync (boolean) - t.window.fsaa = 2 -- The number of samples to use with multi-sampled antialiasing (number) - t.window.display = 1 -- Index of the monitor to show the window in (number) - t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) - t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean) - t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) - t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + t.window = false t.modules.audio = false -- Enable the audio module (boolean) t.modules.event = false -- Enable the event module (boolean) -- 2.43.0 From 49a1b944d0c486a19030376427ee02bfe42fb6b9 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:25:08 -0400 Subject: [PATCH 070/117] Fixed love2d tests --- .github/workflows/love.yml | 2 +- tests/conf.lua | 2 +- tests/threadtests.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index a59560a..7c8455f 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -19,4 +19,4 @@ jobs: sudo chmod +x love.AppImage - name: Run Tests run: | - ./love.AppImage tests \ No newline at end of file + cd tests && ./love.AppImage . \ No newline at end of file diff --git a/tests/conf.lua b/tests/conf.lua index 3dcc3c6..dc85a78 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -1,6 +1,6 @@ function love.conf(t) t.identity = nil -- The name of the save directory (string) - t.version = "12.0" -- The LOVE version this game was made for (string) + t.version = "11.4" -- The LOVE version this game was made for (string) t.console = true -- Attach a console (boolean, Windows only) t.window = false diff --git a/tests/threadtests.lua b/tests/threadtests.lua index daf75d7..e7d8e3a 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -232,7 +232,7 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedProcessor: OK") we_good = true - os.exit(1) + os.exit() end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) -- 2.43.0 From 755f207554b9b5c79459b69fb1ea76a896c1843e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:26:37 -0400 Subject: [PATCH 071/117] Testing love2d --- .github/workflows/love.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 7c8455f..a59560a 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -19,4 +19,4 @@ jobs: sudo chmod +x love.AppImage - name: Run Tests run: | - cd tests && ./love.AppImage . \ No newline at end of file + ./love.AppImage tests \ No newline at end of file -- 2.43.0 From 0b0f9718029694042946bf7376b9ad409f0b036a Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:28:54 -0400 Subject: [PATCH 072/117] Use workspace --- .github/workflows/love.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index a59560a..0b037fe 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -19,4 +19,5 @@ jobs: sudo chmod +x love.AppImage - name: Run Tests run: | - ./love.AppImage tests \ No newline at end of file + ls -l + ./love.AppImage ${{github.workspace}}/tests \ No newline at end of file -- 2.43.0 From a9673385d98ebe5b6c29570e4ba1a1dcf52794dc Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:30:15 -0400 Subject: [PATCH 073/117] Moved other tests while testing --- .github/workflows/love.yml | 1 + .github/workflows/nix_ci.yml => nix_ci.yml | 0 2 files changed, 1 insertion(+) rename .github/workflows/nix_ci.yml => nix_ci.yml (100%) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 0b037fe..96ec141 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -20,4 +20,5 @@ jobs: - name: Run Tests run: | ls -l + ls -l ${{github.workspace}} ./love.AppImage ${{github.workspace}}/tests \ No newline at end of file diff --git a/.github/workflows/nix_ci.yml b/nix_ci.yml similarity index 100% rename from .github/workflows/nix_ci.yml rename to nix_ci.yml -- 2.43.0 From 761e739e18e56460f50282d2d60dc7b2c1d5f2fb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:32:03 -0400 Subject: [PATCH 074/117] actually pull the repo --- .github/workflows/love.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index 96ec141..d38888d 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -12,6 +12,9 @@ jobs: fail-fast: false runs-on: "ubuntu-latest" steps: + - uses: actions/checkout@v2 + with: + submodules: recursive - name: Install love2d run: | sudo apt install fuse @@ -19,6 +22,4 @@ jobs: sudo chmod +x love.AppImage - name: Run Tests run: | - ls -l - ls -l ${{github.workspace}} - ./love.AppImage ${{github.workspace}}/tests \ No newline at end of file + ./love.AppImage tests \ No newline at end of file -- 2.43.0 From befe8638467915699c364c396523d01636f44be0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:38:30 -0400 Subject: [PATCH 075/117] packagepath set --- .github/workflows/love.yml | 6 +++--- tests/main.lua | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index d38888d..b0cefa0 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -12,14 +12,14 @@ jobs: fail-fast: false runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - name: Install love2d run: | sudo apt install fuse wget https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage -O love.AppImage sudo chmod +x love.AppImage + - uses: actions/checkout@v2 + with: + submodules: recursive - name: Run Tests run: | ./love.AppImage tests \ No newline at end of file diff --git a/tests/main.lua b/tests/main.lua index 232899c..2e50375 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,3 +1,4 @@ +package.path = "../?/init.lua;../?.lua;"..package.path require("runtests") require("threadtests") -- Allows you to run "love tests" which runs the tests -- 2.43.0 From 15bbec9379d5477e46d32d7a54700ea99fddd211 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:39:40 -0400 Subject: [PATCH 076/117] Fixed pull --- .github/workflows/love.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index b0cefa0..d38888d 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -12,14 +12,14 @@ jobs: fail-fast: false runs-on: "ubuntu-latest" steps: + - uses: actions/checkout@v2 + with: + submodules: recursive - name: Install love2d run: | sudo apt install fuse wget https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage -O love.AppImage sudo chmod +x love.AppImage - - uses: actions/checkout@v2 - with: - submodules: recursive - name: Run Tests run: | ./love.AppImage tests \ No newline at end of file -- 2.43.0 From c8abadd8d68dd97c4afd252970ee8bd42a9a1f4c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:41:17 -0400 Subject: [PATCH 077/117] Update multi --- tests/multi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/multi b/tests/multi index 5ddae4f..eb4d905 120000 --- a/tests/multi +++ b/tests/multi @@ -1 +1 @@ -D:/VSCWorkspace/discord-lua/multi \ No newline at end of file +../ -- 2.43.0 From 6be948f0683373629c5042dfcbcf0a6a9a26faca Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:43:40 -0400 Subject: [PATCH 078/117] Removed link --- tests/multi | 1 - 1 file changed, 1 deletion(-) delete mode 120000 tests/multi diff --git a/tests/multi b/tests/multi deleted file mode 120000 index eb4d905..0000000 --- a/tests/multi +++ /dev/null @@ -1 +0,0 @@ -../ -- 2.43.0 From d57dc7dc22f0ab7b8401a026077148da7d555120 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:45:43 -0400 Subject: [PATCH 079/117] Edited symlink --- tests/conf.lua | 46 ++-- tests/main.lua | 14 +- tests/multi | 1 + tests/runtests.lua | 398 +++++++++++++++++----------------- tests/test.lua | 408 +++++++++++++++++------------------ tests/threadtests.lua | 492 +++++++++++++++++++++--------------------- 6 files changed, 680 insertions(+), 679 deletions(-) create mode 120000 tests/multi diff --git a/tests/conf.lua b/tests/conf.lua index dc85a78..d88343f 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -1,23 +1,23 @@ -function love.conf(t) - t.identity = nil -- The name of the save directory (string) - t.version = "11.4" -- The LOVE version this game was made for (string) - t.console = true -- Attach a console (boolean, Windows only) - - t.window = false - - t.modules.audio = false -- Enable the audio module (boolean) - t.modules.event = false -- Enable the event module (boolean) - t.modules.graphics = false -- Enable the graphics module (boolean) - t.modules.image = false -- Enable the image module (boolean) - t.modules.joystick = false -- Enable the joystick module (boolean) - t.modules.keyboard = false -- Enable the keyboard module (boolean) - t.modules.math = false -- Enable the math module (boolean) - t.modules.mouse = false -- Enable the mouse module (boolean) - t.modules.physics = false -- Enable the physics module (boolean) - t.modules.sound = false -- Enable the sound module (boolean) - t.modules.system = false -- Enable the system module (boolean) - t.modules.timer = false -- Enable the timer module (boolean) - t.modules.window = false -- Enable the window module (boolean) - t.modules.thread = true -- Enable the thread module (boolean) -end ---1440 x 2560 +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.version = "11.4" -- The LOVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + + t.window = false + + t.modules.audio = false -- Enable the audio module (boolean) + t.modules.event = false -- Enable the event module (boolean) + t.modules.graphics = false -- Enable the graphics module (boolean) + t.modules.image = false -- Enable the image module (boolean) + t.modules.joystick = false -- Enable the joystick module (boolean) + t.modules.keyboard = false -- Enable the keyboard module (boolean) + t.modules.math = false -- Enable the math module (boolean) + t.modules.mouse = false -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = false -- Enable the sound module (boolean) + t.modules.system = false -- Enable the system module (boolean) + t.modules.timer = false -- Enable the timer module (boolean) + t.modules.window = false -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) +end +--1440 x 2560 diff --git a/tests/main.lua b/tests/main.lua index 2e50375..fb4e2f9 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,8 +1,8 @@ -package.path = "../?/init.lua;../?.lua;"..package.path -require("runtests") -require("threadtests") --- Allows you to run "love tests" which runs the tests - -function love.update() - multi:uManager() +package.path = "../?/init.lua;../?.lua;"..package.path +require("runtests") +require("threadtests") +-- Allows you to run "love tests" which runs the tests + +function love.update() + multi:uManager() end \ No newline at end of file diff --git a/tests/multi b/tests/multi new file mode 120000 index 0000000..b870225 --- /dev/null +++ b/tests/multi @@ -0,0 +1 @@ +../ \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index a9811c3..988c978 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,200 +1,200 @@ -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 = "../?/init.lua;../?.lua;"..package.path -end ---[[ - This file runs all tests. - Format: - Expected: - ... - ... - ... - Actual: - ... - ... - ... - - Each test that is ran should have a 5 second pause after the test is complete - The expected and actual should "match" (Might be impossible when playing with threads) - This will be pushed directly to the master as tests start existing. -]] -local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true} -local good = false -local proc = multi:newProcessor("Test") - -proc.Start() - -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 - multi.print("Testing Basic Features. If this fails most other features will probably not work!") - proc:newAlarm(2):OnRing(function(a) - alarms = true - a:Destroy() - 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 - multi.success("Alarms: Ok") - multi.success("Events: Ok") - if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end - if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end - if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end - if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end - if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end - end) - thread.hold(event.OnEvent) - multi.print("Starting Connection and Thread tests!") - func = thread:newFunction(function(count) - multi.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", true, math.random(1,10000) - end) - local ret = func(10) - local ret2 = func(15) - local ret3 = func(20) - local s1,s2,s3 = 0,0,0 - ret.OnError(function(...) - multi.error("Func 1:",...) - end) - ret2.OnError(function(...) - multi.error("Func 2:",...) - end) - ret3.OnError(function(...) - multi.error("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(...) - multi.success("Done 1",...) - end) - ret2.OnReturn(function(...) - multi.success("Done 2",...) - end) - ret3.OnReturn(function(...) - multi.success("Done 3",...) - end) - - local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) - - if s1 == 100 and s2 == 100 and s3 == 100 then - multi.success("Threads: All tests Ok") - else - if s1>0 and s2>0 and s3 > 0 then - multi.success("Thread OnStatus: Ok") - else - multi.error("Threads OnStatus or thread.hold(conn) Error!") - end - if timeout then - multi.error("Connection Error!") - else - multi.success("Connection Test 1: Ok") - end - multi.error("Connection holding Error!") - 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 - multi.success("Connection Test 2: Ok") - else - multi.error("Connection Test 2: Error") - end - c3 = false - c4 = false - conn3:Unconnect(d) - conn3:Fire() - if c3 and not(c4) then - multi.success("Connection Test 3: Ok") - else - multi.error("Connection Test 3: Error removing connection") - end - if not love then - multi.print("Testing pseudo threading") - os.execute("lua tests/threadtests.lua p") - multi.print("Testing lanes threading") - os.execute("lua tests/threadtests.lua l") - os.exit() - end -end) - -local handle = runTest() - -handle.OnError(function(...) - multi.error("Something went wrong with the test!") - print(...) -end) - -if not love then - multi:mainloop() -else - local hold = thread:newFunction(function() - thread.hold(handle.OnError + handle.OnReturn) - end, true) - hold() - multi.print("Starting Threading tests!") +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 = "../?/init.lua;../?.lua;"..package.path +end +--[[ + This file runs all tests. + Format: + Expected: + ... + ... + ... + Actual: + ... + ... + ... + + Each test that is ran should have a 5 second pause after the test is complete + The expected and actual should "match" (Might be impossible when playing with threads) + This will be pushed directly to the master as tests start existing. +]] +local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true} +local good = false +local proc = multi:newProcessor("Test") + +proc.Start() + +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 + multi.print("Testing Basic Features. If this fails most other features will probably not work!") + proc:newAlarm(2):OnRing(function(a) + alarms = true + a:Destroy() + 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 + multi.success("Alarms: Ok") + multi.success("Events: Ok") + if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end + if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end + if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end + if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end + if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end + end) + thread.hold(event.OnEvent) + multi.print("Starting Connection and Thread tests!") + func = thread:newFunction(function(count) + multi.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", true, math.random(1,10000) + end) + local ret = func(10) + local ret2 = func(15) + local ret3 = func(20) + local s1,s2,s3 = 0,0,0 + ret.OnError(function(...) + multi.error("Func 1:",...) + end) + ret2.OnError(function(...) + multi.error("Func 2:",...) + end) + ret3.OnError(function(...) + multi.error("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(...) + multi.success("Done 1",...) + end) + ret2.OnReturn(function(...) + multi.success("Done 2",...) + end) + ret3.OnReturn(function(...) + multi.success("Done 3",...) + end) + + local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) + + if s1 == 100 and s2 == 100 and s3 == 100 then + multi.success("Threads: All tests Ok") + else + if s1>0 and s2>0 and s3 > 0 then + multi.success("Thread OnStatus: Ok") + else + multi.error("Threads OnStatus or thread.hold(conn) Error!") + end + if timeout then + multi.error("Connection Error!") + else + multi.success("Connection Test 1: Ok") + end + multi.error("Connection holding Error!") + 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 + multi.success("Connection Test 2: Ok") + else + multi.error("Connection Test 2: Error") + end + c3 = false + c4 = false + conn3:Unconnect(d) + conn3:Fire() + if c3 and not(c4) then + multi.success("Connection Test 3: Ok") + else + multi.error("Connection Test 3: Error removing connection") + end + if not love then + multi.print("Testing pseudo threading") + os.execute("lua tests/threadtests.lua p") + multi.print("Testing lanes threading") + os.execute("lua tests/threadtests.lua l") + os.exit() + end +end) + +local handle = runTest() + +handle.OnError(function(...) + multi.error("Something went wrong with the test!") + print(...) +end) + +if not love then + multi:mainloop() +else + local hold = thread:newFunction(function() + thread.hold(handle.OnError + handle.OnReturn) + end, true) + hold() + multi.print("Starting Threading tests!") end \ No newline at end of file diff --git a/tests/test.lua b/tests/test.lua index 0d326f2..806f58a 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,205 +1,205 @@ -package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{print=true,warn=true,error=true} -require("multi.integration.priorityManager") - --- test = multi:newProcessor("Test") --- test:setPriorityScheme(multi.priorityScheme.TimeBased) - --- test:newUpdater(10000000):OnUpdate(function() --- print("Print is slowish") --- end) - --- print("Running...") - --- local conn1, conn2 = multi:newConnection(), multi:newConnection() --- conn3 = conn1 + conn2 - --- conn1(function() --- print("Hi 1") --- end) - --- conn2(function() --- print("Hi 2") --- end) - --- conn3(function() --- print("Hi 3") --- end) - --- function test(a,b,c) --- print("I run before all and control if execution should continue!") --- return a>b --- end - --- conn4 = test .. conn1 - --- conn5 = conn2 .. function() print("I run after it all!") end - --- conn4:Fire(3,2,3) --- -- This second one won't trigger the Hi's --- conn4:Fire(1,2,3) - --- conn5(function() --- print("Test 1") --- end) - --- conn5(function() --- print("Test 2") --- end) - --- conn5(function() --- print("Test 3") --- end) - --- conn5:Fire() -multi.print("Testing thread:newProcessor()") - -proc = thread:newProcessor("Test") - -proc:newLoop(function() - multi.print("Running...") - thread.sleep(1) -end) - -proc:newThread(function() - while true do - multi.warn("Everything is a thread in this proc!") - thread.sleep(1) - end -end) - -multi:mainloop() - - --- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() - --- local link = conn1(function() --- print("Conn1, first") --- end) - --- local link2 = conn1(function() --- print("Conn1, second") --- end) - --- local link3 = conn1(function() --- print("Conn1, third") --- end) - --- local link4 = conn2(function() --- print("Conn2, first") --- end) - --- local link5 = conn2(function() --- print("Conn2, second") --- end) - --- local link6 = conn2(function() --- print("Conn2, third") --- end) - --- print("Links 1-6",link,link2,link3,link4,link5,link6) --- conn1:Lock(link) --- print("All conns\n-------------") --- conn1:Fire() --- conn2:Fire() - --- conn1:Unlock(link) - --- conn1:Unconnect(link3) --- conn2:Unconnect(link6) --- print("All conns Edit\n---------------------") --- conn1:Fire() --- conn2:Fire() - --- thread:newThread(function() --- print("Awaiting status") --- thread.hold(conn1 + (conn2 * conn3)) --- print("Conn or Conn2 and Conn3") --- end) - --- multi:newAlarm(1):OnRing(function() --- print("Conn") --- conn1:Fire() --- end) --- multi:newAlarm(2):OnRing(function() --- print("Conn2") --- conn2:Fire() --- end) --- multi:newAlarm(3):OnRing(function() --- print("Conn3") --- conn3:Fire() --- os.exit() --- end) - - --- local conn = multi:newSystemThreadedConnection("conn"):init() - --- multi:newSystemThread("Thread_Test_1", function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:mainloop() --- end) - --- multi:newSystemThread("Thread_Test_2", function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(2):OnRing(function() --- console.print("Fire 2!!!") --- conn:Fire(4,5,6) --- THREAD.kill() --- end) - --- multi:mainloop() --- end) --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print("Mainloop conn got triggered!",a,b,c) --- end) - --- alarm = multi:newAlarm(1) --- alarm:OnRing(function() --- console.print("Fire 1!!!") --- conn:Fire(1,2,3) --- end) - --- alarm = multi:newAlarm(3):OnRing(function() --- multi:newSystemThread("Thread_Test_3",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local console = THREAD.getConsole() --- conn(function(a,b,c) --- console.print(THREAD:getName().." was triggered!",a,b,c) --- end) --- multi:newAlarm(4):OnRing(function() --- console.print("Fire 3!!!") --- conn:Fire(7,8,9) --- end) --- multi:mainloop() --- end) --- end) - --- multi:newSystemThread("Thread_Test_4",function() --- local multi, thread = require("multi"):init() --- local conn = GLOBAL["conn"]:init() --- local conn2 = multi:newConnection() --- local console = THREAD.getConsole() --- multi:newAlarm(2):OnRing(function() --- conn2:Fire() --- end) --- multi:newThread(function() --- console.print("Conn Test!") --- thread.hold(conn + conn2) --- console.print("It held!") --- end) --- multi:mainloop() --- end) - +package.path = "../?/init.lua;../?.lua;"..package.path +multi, thread = require("multi"):init{print=true,warn=true,error=true} +require("multi.integration.priorityManager") + +-- test = multi:newProcessor("Test") +-- test:setPriorityScheme(multi.priorityScheme.TimeBased) + +-- test:newUpdater(10000000):OnUpdate(function() +-- print("Print is slowish") +-- end) + +-- print("Running...") + +-- local conn1, conn2 = multi:newConnection(), multi:newConnection() +-- conn3 = conn1 + conn2 + +-- conn1(function() +-- print("Hi 1") +-- end) + +-- conn2(function() +-- print("Hi 2") +-- end) + +-- conn3(function() +-- print("Hi 3") +-- end) + +-- function test(a,b,c) +-- print("I run before all and control if execution should continue!") +-- return a>b +-- end + +-- conn4 = test .. conn1 + +-- conn5 = conn2 .. function() print("I run after it all!") end + +-- conn4:Fire(3,2,3) +-- -- This second one won't trigger the Hi's +-- conn4:Fire(1,2,3) + +-- conn5(function() +-- print("Test 1") +-- end) + +-- conn5(function() +-- print("Test 2") +-- end) + +-- conn5(function() +-- print("Test 3") +-- end) + +-- conn5:Fire() +multi.print("Testing thread:newProcessor()") + +proc = thread:newProcessor("Test") + +proc:newLoop(function() + multi.print("Running...") + thread.sleep(1) +end) + +proc:newThread(function() + while true do + multi.warn("Everything is a thread in this proc!") + thread.sleep(1) + end +end) + +multi:mainloop() + + +-- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() + +-- local link = conn1(function() +-- print("Conn1, first") +-- end) + +-- local link2 = conn1(function() +-- print("Conn1, second") +-- end) + +-- local link3 = conn1(function() +-- print("Conn1, third") +-- end) + +-- local link4 = conn2(function() +-- print("Conn2, first") +-- end) + +-- local link5 = conn2(function() +-- print("Conn2, second") +-- end) + +-- local link6 = conn2(function() +-- print("Conn2, third") +-- end) + +-- print("Links 1-6",link,link2,link3,link4,link5,link6) +-- conn1:Lock(link) +-- print("All conns\n-------------") +-- conn1:Fire() +-- conn2:Fire() + +-- conn1:Unlock(link) + +-- conn1:Unconnect(link3) +-- conn2:Unconnect(link6) +-- print("All conns Edit\n---------------------") +-- conn1:Fire() +-- conn2:Fire() + +-- thread:newThread(function() +-- print("Awaiting status") +-- thread.hold(conn1 + (conn2 * conn3)) +-- print("Conn or Conn2 and Conn3") +-- end) + +-- multi:newAlarm(1):OnRing(function() +-- print("Conn") +-- conn1:Fire() +-- end) +-- multi:newAlarm(2):OnRing(function() +-- print("Conn2") +-- conn2:Fire() +-- end) +-- multi:newAlarm(3):OnRing(function() +-- print("Conn3") +-- conn3:Fire() +-- os.exit() +-- end) + + +-- local conn = multi:newSystemThreadedConnection("conn"):init() + +-- multi:newSystemThread("Thread_Test_1", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:mainloop() +-- end) + +-- multi:newSystemThread("Thread_Test_2", function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(2):OnRing(function() +-- console.print("Fire 2!!!") +-- conn:Fire(4,5,6) +-- THREAD.kill() +-- end) + +-- multi:mainloop() +-- end) +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print("Mainloop conn got triggered!",a,b,c) +-- end) + +-- alarm = multi:newAlarm(1) +-- alarm:OnRing(function() +-- console.print("Fire 1!!!") +-- conn:Fire(1,2,3) +-- end) + +-- alarm = multi:newAlarm(3):OnRing(function() +-- multi:newSystemThread("Thread_Test_3",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local console = THREAD.getConsole() +-- conn(function(a,b,c) +-- console.print(THREAD:getName().." was triggered!",a,b,c) +-- end) +-- multi:newAlarm(4):OnRing(function() +-- console.print("Fire 3!!!") +-- conn:Fire(7,8,9) +-- end) +-- multi:mainloop() +-- end) +-- end) + +-- multi:newSystemThread("Thread_Test_4",function() +-- local multi, thread = require("multi"):init() +-- local conn = GLOBAL["conn"]:init() +-- local conn2 = multi:newConnection() +-- local console = THREAD.getConsole() +-- multi:newAlarm(2):OnRing(function() +-- conn2:Fire() +-- end) +-- multi:newThread(function() +-- console.print("Conn Test!") +-- thread.hold(conn + conn2) +-- console.print("It held!") +-- end) +-- multi:mainloop() +-- end) + -- multi:mainloop() \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index e7d8e3a..2ca047c 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,247 +1,247 @@ -package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} -proc = multi:newProcessor("Thread Test",true) -local LANES, LOVE, PSEUDO = 1, 2, 3 -local env, we_good - -if love then - GLOBAL, THREAD = require("multi.integration.loveManager"):init() - env = LOVE -elseif arg[1] == "l" then - GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - env = LANES -elseif arg[1] == "p" then - GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() - env = PSEUDO -else - io.write("Test Pseudo(p), Lanes(l), or love(Run in love environment) Threading: ") - choice = io.read() - if choice == "p" then - GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() - env = PSEUDO - elseif choice == "l" then - GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - env = LANES - else - error("Invalid threading choice") - end -end - -multi.print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") -THREAD.setENV({ - multi_assert = function(expected, actual, s) - if expected ~= actual then - multi.error(s .. " Expected: '".. tostring(expected) .."' Actual: '".. tostring(actual) .."'") - end - end -}) - -multi:newThread("Scheduler Thread",function() - queue = multi:newSystemThreadedQueue("Test_Queue"):init() - - multi:newSystemThread("Test_Thread_0", function() - print("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) - end) - - th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) - queue = THREAD.waitFor("Test_Queue"):init() - multi_assert("Test_Thread_1", THREAD_NAME, "Thread name does not match!") - multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") - multi_assert(true, e, "Argument e is not true!") - multi_assert("table", type(f), "Argument f is not a table!") - queue:push("done") - end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) - multi.error(err) - os.exit(1) - end) - - if thread.hold(function() - return queue:pop() == "done" - end,{sleep=1}) == nil then - thread.kill() - end - - multi.success("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") - - func = THREAD:newFunction(function(a,b,c) - assert(a == 3, "First argument expected '3' got '".. a .."'!") - assert(b == 2, "Second argument expected '2' got '".. a .."'!") - assert(c == 1, "Third argument expected '1' got '".. a .."'!") - return 1, 2, 3, {"a table"} - end, true) -- Hold this - - a, b, c, d = func(3,2,1) - - assert(a == 1, "First return was not '1'!") - assert(b == 2, "Second return was not '2'!") - assert(c == 3, "Third return was not '3'!") - assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") - - multi.success("Threaded Functions, arg passing, return passing, holding: Ok") - - test=multi:newSystemThreadedTable("YO"):init() - test["test1"]="tabletest" - local worked = false - - multi:newSystemThread("testing tables",function() - tab=THREAD.waitFor("YO"):init() - THREAD.hold(function() return tab["test1"] end) - THREAD.sleep(.1) - tab["test2"] = "Whats so funny?" - end).OnError(multi.error) - - multi:newThread("test2",function() - thread.hold(function() return test["test2"] end) - worked = true - end) - - t, val = thread.hold(function() - return worked - end,{sleep=1}) - - if val == multi.TIMEOUT then - multi.error("SystemThreadedTables: Failed") - os.exit(1) - end - - multi.success("SystemThreadedTables: Ok") - - local ready = false - - jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads - func = jq:newFunction("test-thread",function(a,b) - THREAD.sleep(.2) - return a+b - end) - local count = 0 - for i = 1,10 do - func(i, i*3).OnReturn(function(data) - count = count + 1 - end) - end - - t, val = thread.hold(function() - return count == 10 - end,{sleep=2}) - - if val == multi.TIMEOUT then - multi.error("SystemThreadedJobQueues: Failed") - os.exit(1) - end - - multi.success("SystemThreadedJobQueues: Ok") - - -- queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() - -- multi:newSystemThread("Test_Thread_2",function() - -- queue2 = THREAD.waitFor("Test_Queue2"):init() - -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - -- connOut(function(arg) - -- queue2:push("Test_Thread_2") - -- end) - -- multi:mainloop() - -- end).OnError(multi.error) - -- multi:newSystemThread("Test_Thread_3",function() - -- queue2 = THREAD.waitFor("Test_Queue2"):init() - -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - -- connOut(function(arg) - -- queue2:push("Test_Thread_3") - -- end) - -- multi:mainloop() - -- end).OnError(multi.error) - -- connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() - -- a=0 - -- connOut(function(arg) - -- queue2:push("Main") - -- end) - -- for i=1,3 do - -- thread.sleep(.1) - -- connOut:Fire("Test From Main Thread: "..i.."\n") - -- end - -- thread.sleep(2) - -- local count = 0 - -- multi:newThread(function() - -- while count < 9 do - -- if queue2:pop() then - -- count = count + 1 - -- end - -- end - -- end).OnError(multi.error) - -- _, err = thread.hold(function() return count == 9 end,{sleep=.3}) - -- if err == multi.TIMEOUT then - -- multi.error("SystemThreadedConnections: Failed") - -- end - -- multi.success("SystemThreadedConnections: Ok") - - local stp = multi:newSystemThreadedProcessor(8) - - local tloop = stp:newTLoop(nil, 1) - - local proxy_test = false - - multi:newSystemThread("Testing proxy copy THREAD",function(tloop) - local multi, thread = require("multi"):init() - tloop = tloop:init() - multi.print("tloop type:",tloop.Type) - multi.print("Testing proxies on other threads") - thread:newThread(function() - while true do - thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") - end - end) - tloop.OnLoop(function(a) - print(THREAD_NAME, "Got loop...") - end) - multi:mainloop() - end, tloop:getTransferable()).OnError(multi.error) - - multi.print("tloop", tloop.Type) - multi.print("tloop.OnLoop", tloop.OnLoop.Type) - - thread:newThread(function() - multi.print("Testing holding on a proxy connection!") - thread.hold(tloop.OnLoop) - multi.print("Held on proxy connection... once") - thread.hold(tloop.OnLoop) - multi.print("Held on proxy connection... twice") - proxy_test = true - end).OnError(print) - - thread:newThread(function() - while true do - thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") - end - end) - - tloop.OnLoop(function() - print("OnLoop",THREAD_NAME) - end) - - t, val = thread.hold(function() - return count == 10 - end,{sleep=5}) - - if val == multi.TIMEOUT then - multi.error("SystemThreadedProcessor/Proxies: Failed") - os.exit(1) - end - - thread.sleep(2) - - multi.success("SystemThreadedProcessor: OK") - - we_good = true - os.exit() -end).OnError(multi.error) - -multi.OnExit(function(err_or_errorcode) - if not we_good then - multi.info("There was an error running some tests!") - return - else - multi.success("Tests complete!") - end -end) - +package.path = "../?/init.lua;../?.lua;"..package.path +multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} +proc = multi:newProcessor("Thread Test",true) +local LANES, LOVE, PSEUDO = 1, 2, 3 +local env, we_good + +if love then + GLOBAL, THREAD = require("multi.integration.loveManager"):init() + env = LOVE +elseif arg[1] == "l" then + GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + env = LANES +elseif arg[1] == "p" then + GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() + env = PSEUDO +else + io.write("Test Pseudo(p), Lanes(l), or love(Run in love environment) Threading: ") + choice = io.read() + if choice == "p" then + GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() + env = PSEUDO + elseif choice == "l" then + GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + env = LANES + else + error("Invalid threading choice") + end +end + +multi.print("Testing THREAD.setENV() if the multi_assert is not found then there is a problem") +THREAD.setENV({ + multi_assert = function(expected, actual, s) + if expected ~= actual then + multi.error(s .. " Expected: '".. tostring(expected) .."' Actual: '".. tostring(actual) .."'") + end + end +}) + +multi:newThread("Scheduler Thread",function() + queue = multi:newSystemThreadedQueue("Test_Queue"):init() + + multi:newSystemThread("Test_Thread_0", function() + print("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) + end) + + th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) + queue = THREAD.waitFor("Test_Queue"):init() + multi_assert("Test_Thread_1", THREAD_NAME, "Thread name does not match!") + multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") + multi_assert(true, e, "Argument e is not true!") + multi_assert("table", type(f), "Argument f is not a table!") + queue:push("done") + end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) + multi.error(err) + os.exit(1) + end) + + if thread.hold(function() + return queue:pop() == "done" + end,{sleep=1}) == nil then + thread.kill() + end + + multi.success("Thread Spawning, THREAD namaspace in threads, global's working, and queues for passing data: Ok") + + func = THREAD:newFunction(function(a,b,c) + assert(a == 3, "First argument expected '3' got '".. a .."'!") + assert(b == 2, "Second argument expected '2' got '".. a .."'!") + assert(c == 1, "Third argument expected '1' got '".. a .."'!") + return 1, 2, 3, {"a table"} + end, true) -- Hold this + + a, b, c, d = func(3,2,1) + + assert(a == 1, "First return was not '1'!") + assert(b == 2, "Second return was not '2'!") + assert(c == 3, "Third return was not '3'!") + assert(d[1] == "a table", "Fourth return is not table, or doesn't contain 'a table'!") + + multi.success("Threaded Functions, arg passing, return passing, holding: Ok") + + test=multi:newSystemThreadedTable("YO"):init() + test["test1"]="tabletest" + local worked = false + + multi:newSystemThread("testing tables",function() + tab=THREAD.waitFor("YO"):init() + THREAD.hold(function() return tab["test1"] end) + THREAD.sleep(.1) + tab["test2"] = "Whats so funny?" + end).OnError(multi.error) + + multi:newThread("test2",function() + thread.hold(function() return test["test2"] end) + worked = true + end) + + t, val = thread.hold(function() + return worked + end,{sleep=1}) + + if val == multi.TIMEOUT then + multi.error("SystemThreadedTables: Failed") + os.exit(1) + end + + multi.success("SystemThreadedTables: Ok") + + local ready = false + + jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads + func = jq:newFunction("test-thread",function(a,b) + THREAD.sleep(.2) + return a+b + end) + local count = 0 + for i = 1,10 do + func(i, i*3).OnReturn(function(data) + count = count + 1 + end) + end + + t, val = thread.hold(function() + return count == 10 + end,{sleep=2}) + + if val == multi.TIMEOUT then + multi.error("SystemThreadedJobQueues: Failed") + os.exit(1) + end + + multi.success("SystemThreadedJobQueues: Ok") + + -- queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() + -- multi:newSystemThread("Test_Thread_2",function() + -- queue2 = THREAD.waitFor("Test_Queue2"):init() + -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + -- connOut(function(arg) + -- queue2:push("Test_Thread_2") + -- end) + -- multi:mainloop() + -- end).OnError(multi.error) + -- multi:newSystemThread("Test_Thread_3",function() + -- queue2 = THREAD.waitFor("Test_Queue2"):init() + -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + -- connOut(function(arg) + -- queue2:push("Test_Thread_3") + -- end) + -- multi:mainloop() + -- end).OnError(multi.error) + -- connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() + -- a=0 + -- connOut(function(arg) + -- queue2:push("Main") + -- end) + -- for i=1,3 do + -- thread.sleep(.1) + -- connOut:Fire("Test From Main Thread: "..i.."\n") + -- end + -- thread.sleep(2) + -- local count = 0 + -- multi:newThread(function() + -- while count < 9 do + -- if queue2:pop() then + -- count = count + 1 + -- end + -- end + -- end).OnError(multi.error) + -- _, err = thread.hold(function() return count == 9 end,{sleep=.3}) + -- if err == multi.TIMEOUT then + -- multi.error("SystemThreadedConnections: Failed") + -- end + -- multi.success("SystemThreadedConnections: Ok") + + local stp = multi:newSystemThreadedProcessor(8) + + local tloop = stp:newTLoop(nil, 1) + + local proxy_test = false + + multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + local multi, thread = require("multi"):init() + tloop = tloop:init() + multi.print("tloop type:",tloop.Type) + multi.print("Testing proxies on other threads") + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") + end + end) + tloop.OnLoop(function(a) + print(THREAD_NAME, "Got loop...") + end) + multi:mainloop() + end, tloop:getTransferable()).OnError(multi.error) + + multi.print("tloop", tloop.Type) + multi.print("tloop.OnLoop", tloop.OnLoop.Type) + + thread:newThread(function() + multi.print("Testing holding on a proxy connection!") + thread.hold(tloop.OnLoop) + multi.print("Held on proxy connection... once") + thread.hold(tloop.OnLoop) + multi.print("Held on proxy connection... twice") + proxy_test = true + end).OnError(print) + + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") + end + end) + + tloop.OnLoop(function() + print("OnLoop",THREAD_NAME) + end) + + t, val = thread.hold(function() + return count == 10 + end,{sleep=5}) + + if val == multi.TIMEOUT then + multi.error("SystemThreadedProcessor/Proxies: Failed") + os.exit(1) + end + + thread.sleep(2) + + multi.success("SystemThreadedProcessor: OK") + + we_good = true + os.exit() +end).OnError(multi.error) + +multi.OnExit(function(err_or_errorcode) + if not we_good then + multi.info("There was an error running some tests!") + return + else + multi.success("Tests complete!") + end +end) + multi:mainloop() \ No newline at end of file -- 2.43.0 From cec53f6f4eabc440916ca1da75a5eb0f1197d32e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 4 Jul 2023 23:56:56 -0400 Subject: [PATCH 080/117] Added timeout to build --- nix_ci.yml => .github/workflows/nix_ci.yml | 0 tests/conf.lua | 5 +-- tests/threadtests.lua | 20 ++++++--- win_ci.yml | 48 ---------------------- 4 files changed, 16 insertions(+), 57 deletions(-) rename nix_ci.yml => .github/workflows/nix_ci.yml (100%) delete mode 100644 win_ci.yml diff --git a/nix_ci.yml b/.github/workflows/nix_ci.yml similarity index 100% rename from nix_ci.yml rename to .github/workflows/nix_ci.yml diff --git a/tests/conf.lua b/tests/conf.lua index d88343f..6b728c5 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -15,9 +15,8 @@ function love.conf(t) t.modules.mouse = false -- Enable the mouse module (boolean) t.modules.physics = false -- Enable the physics module (boolean) t.modules.sound = false -- Enable the sound module (boolean) - t.modules.system = false -- Enable the system module (boolean) - t.modules.timer = false -- Enable the timer module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.timer = true -- Enable the timer module (boolean) t.modules.window = false -- Enable the window module (boolean) t.modules.thread = true -- Enable the thread module (boolean) end ---1440 x 2560 diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 2ca047c..615f85d 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -37,6 +37,12 @@ THREAD.setENV({ }) multi:newThread("Scheduler Thread",function() + multi:newThread(function() + thread.sleep(30) + print("Timeout tests took longer than 30 seconds") + multi:Stop() + os.exit(1) + end) queue = multi:newSystemThreadedQueue("Test_Queue"):init() multi:newSystemThread("Test_Thread_0", function() @@ -172,12 +178,12 @@ multi:newThread("Scheduler Thread",function() -- end -- multi.success("SystemThreadedConnections: Ok") - local stp = multi:newSystemThreadedProcessor(8) - + local stp = multi:newSystemThreadedProcessor(1) + print(stp) local tloop = stp:newTLoop(nil, 1) - + print(2) local proxy_test = false - + print(3) multi:newSystemThread("Testing proxy copy THREAD",function(tloop) local multi, thread = require("multi"):init() tloop = tloop:init() @@ -194,10 +200,10 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end, tloop:getTransferable()).OnError(multi.error) - + print(4) multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) - + print(5) thread:newThread(function() multi.print("Testing holding on a proxy connection!") thread.hold(tloop.OnLoop) @@ -206,6 +212,7 @@ multi:newThread("Scheduler Thread",function() multi.print("Held on proxy connection... twice") proxy_test = true end).OnError(print) + print(6) thread:newThread(function() while true do @@ -232,6 +239,7 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedProcessor: OK") we_good = true + multi:Stop() os.exit() end).OnError(multi.error) diff --git a/win_ci.yml b/win_ci.yml deleted file mode 100644 index 800c9fb..0000000 --- a/win_ci.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: build & run tests (Win) - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - strategy: - fail-fast: false - matrix: - build-type: [Release] # Debug - lua: ["lua 5.1", "lua 5.2", "lua 5.3", "luajit 2.0"] - os: ["windows-2019"] - platform: [ - {"forLua": "vs_32", "forCMake": "Win32"}, - {"forLua": "vs_64", "forCMake": "x64"}, - ] - exclude: - - lua: "luajit 2.0" - platform: {"forLua": "vs_32", "forCMake": "Win32"} - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - name: Setup env - run: | - pip install hererocks - hererocks lua-pkg --${{ matrix.lua }} -rlatest --target ${{matrix.platform.forLua}} - cmd ${{github.workspace}}\lua-pkg\bin\activate - - - name: Configure CMake & build - run: | - cmake -A ${{matrix.platform.forCMake}} -B ${{github.workspace}}\build -DLUA_INCLUDE_DIR="${{github.workspace}}/lua-pkg/include" -DLUA_LIBRARY="${{github.workspace}}/lua-pkg/lib/lua*.lib" - cmake --build ${{github.workspace}}/build --config ${{matrix.build-type}} - - - name: Test - working-directory: ${{github.workspace}}/build - env: - STRESS: 1 - run: | - ${{github.workspace}}/lua-pkg/bin/lua ../tests/lua/run_tests -- 2.43.0 From f4cdde6040273cf72e32b836334d7e2f9e27f65c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 9 Jul 2023 21:54:01 -0400 Subject: [PATCH 081/117] Rewriting loveManager, too much outdated code --- lovethreads/conf.lua | 20 ++++++++++++++++++++ lovethreads/main.lua | 28 ++++++++++++++++++++++++++++ lovethreads/multi | 1 + 3 files changed, 49 insertions(+) create mode 100644 lovethreads/conf.lua create mode 100644 lovethreads/main.lua create mode 120000 lovethreads/multi diff --git a/lovethreads/conf.lua b/lovethreads/conf.lua new file mode 100644 index 0000000..c202fa9 --- /dev/null +++ b/lovethreads/conf.lua @@ -0,0 +1,20 @@ +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.version = "12.0" -- The LOVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + + -- t.modules.audio = false -- Enable the audio module (boolean) + -- t.modules.event = false -- Enable the event module (boolean) + -- t.modules.graphics = false -- Enable the graphics module (boolean) + -- t.modules.image = false -- Enable the image module (boolean) + -- t.modules.joystick = false -- Enable the joystick module (boolean) + -- t.modules.keyboard = false -- Enable the keyboard module (boolean) + -- t.modules.math = false -- Enable the math module (boolean) + -- t.modules.mouse = false -- Enable the mouse module (boolean) + -- t.modules.physics = false -- Enable the physics module (boolean) + -- t.modules.sound = false -- Enable the sound module (boolean) + -- t.modules.system = true -- Enable the system module (boolean) + -- t.modules.timer = true -- Enable the timer module (boolean) + -- t.modules.window = false -- Enable the window module (boolean) + -- t.modules.thread = true -- Enable the thread module (boolean) +end diff --git a/lovethreads/main.lua b/lovethreads/main.lua new file mode 100644 index 0000000..5016548 --- /dev/null +++ b/lovethreads/main.lua @@ -0,0 +1,28 @@ +package.path = "../?/init.lua;../?.lua;"..package.path +local multi, thread = require("multi"):init() + +local GLOBAL, THREAD = require("multi.integration.loveManager"):init() + +GLOBAL["Test"] = {1,2,3, function() print("HI") end} + +for i,v in pairs(GLOBAL["Test"]) do + print(i,v) + if type(v) == "function" then v() end +end + +multi:newAlarm(3):OnRing(function() + GLOBAL["Test2"] = "We got a value!" +end) + +thread:newThread(function() + print("Waiting...") + print(THREAD.waitFor("Test2")) +end) + +function love.draw() + -- +end + +function love.update() + multi:uManager() +end \ No newline at end of file diff --git a/lovethreads/multi b/lovethreads/multi new file mode 120000 index 0000000..b870225 --- /dev/null +++ b/lovethreads/multi @@ -0,0 +1 @@ +../ \ No newline at end of file -- 2.43.0 From e43fe34a7cb4a60651dfc31c5881da0b6b24202d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 10 Jul 2023 15:54:36 -0400 Subject: [PATCH 082/117] Still implementing new love2d threading code --- init.lua | 7 +- integration/lanesManager/init.lua | 2 +- integration/lanesManager/threads.lua | 6 +- integration/loveManager/extensions.lua | 384 --------------------- integration/loveManager/init.lua | 137 +------- integration/loveManager/serpent.lua | 176 ++++++++++ integration/loveManager/threads.lua | 344 ++++++++----------- integration/loveManagerold/extensions.lua | 389 ++++++++++++++++++++++ integration/loveManagerold/init.lua | 136 ++++++++ integration/loveManagerold/threads.lua | 258 ++++++++++++++ integration/luvitManager.lua | 2 +- tests/threadtests.lua | 26 +- 12 files changed, 1126 insertions(+), 741 deletions(-) create mode 100644 integration/loveManager/serpent.lua create mode 100644 integration/loveManagerold/extensions.lua create mode 100644 integration/loveManagerold/init.lua create mode 100644 integration/loveManagerold/threads.lua diff --git a/init.lua b/init.lua index cb24772..7c79a4a 100644 --- a/init.lua +++ b/init.lua @@ -2405,7 +2405,12 @@ end function multi.error(self, err) if type(err) == "bool" then crash = err end if type(self) == "string" then err = self end - io.write("\x1b[91mERROR:\x1b[0m " .. err .. " " .. debug.getinfo(2).name .."\n") + local name = debug.getinfo(2).name + if name then + io.write("\x1b[91mERROR:\x1b[0m " .. err .. " " .. name .."\n") + else + io.write("\x1b[91mERROR:\x1b[0m " .. err .. " ?\n") + end error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") if multi.defaultSettings.error then os.exit(1) diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index b33b7ba..014294e 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -76,7 +76,7 @@ function multi:newSystemThread(name, func, ...) c.loadString = {"base","package","os","io","math","table","string","coroutine"} livingThreads[count] = {true, name} c.returns = return_linda - c.Type = "sthread" + c.Type = multi.STHREAD c.creationTime = os.clock() c.alive = true c.priority = THREAD.Priority_Normal diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 3d05ebf..6db9b79 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -66,11 +66,11 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) local c = {} c.queue = __Console function c.print(...) - c.queue:send("Q", multi.pack(...)) + c.queue:push("Q", table.concat(multi.pack(...), "\t")) end function c.error(err) - c.queue:push("Q",{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}) - error(err) + c.queue:push("Q", "Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err) + multi.error(err) end return c end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 32a1215..e69de29 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -1,384 +0,0 @@ ---[[ -MIT License - -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 -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 not ISTHREAD then - multi, thread = require("multi").init() - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -else - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -end - -function multi:newSystemThreadedQueue(name) - local name = name or multi.randomString(16) - local c = {} - c.Name = name - c.Type = multi.SQUEUE - local fRef = {"func",nil} - function c:init() - local q = {} - q.chan = love.thread.getChannel(self.Name) - function q:push(dat) - if type(dat) == "function" then - fRef[2] = THREAD.dump(dat) - self.chan:push(fRef) - return - else - self.chan:push(dat) - end - end - function q:pop() - local dat = self.chan:pop() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - function q:peek() - local dat = self.chan:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - return q - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -function multi:newSystemThreadedTable(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Name = name - c.Type = multi.STABLE - - function c:init() - return THREAD.createTable(self.Name) - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -local jqc = 1 -function multi:newSystemThreadedJobQueue(n) - local c = {} - - c.cores = n or THREAD.getCores() - c.registerQueue = {} - c.Type = multi.SJOBQUEUE - c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - c.id = 0 - c.OnJobCompleted = multi:newConnection() - - local allfunc = 0 - - function c:doToAll(func) - local f = THREAD.dump(func) - for i = 1, self.cores do - self.queueAll:push({allfunc,f}) - end - allfunc = allfunc + 1 - end - function c:registerFunction(name,func) - if self.funcs[name] then - error("A function by the name "..name.." has already been registered!") - end - self.funcs[name] = func - end - function c:pushJob(name,...) - self.id = self.id + 1 - self.queue:push{name,self.id,...} - return self.id - end - function c:isEmpty() - return queueJob:peek()==nil - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = multi.pack(...) - end - end) - return thread.hold(function() - if rets then - return multi.unpack(rets) or multi.NIL - end - end) - end,holup),name - end - thread:newThread("jobManager",function() - while true do - thread.yield() - local dat = c.queueReturn:pop() - if dat then - c.OnJobCompleted:Fire(multi.unpack(dat)) - end - end - end) - for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) - local multi, thread = require("multi"):init() - require("love.timer") - local function atomic(channel) - return channel:pop() - end - local clock = os.clock - local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() - local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - local registry = {} - _G["__QR"] = queueReturn - setmetatable(_G,{__index = funcs}) - thread:newThread("startUp",function() - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - end - end) - thread:newThread("runner",function() - thread.sleep(.1) - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - local dat = queue:performAtomic(atomic) - if dat then - multi:newThread("Test",function() - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](multi.unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) - end) - end - end - end):OnError(function(...) - error(...) - end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) - multi:mainloop() - end,jqc) - end - - jqc = jqc + 1 - - self:create(c) - - return c -end - -function multi:newSystemThreadedConnection(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Type = multi.SCONNECTION - c.CONN = 0x00 - c.TRIG = 0x01 - c.PING = 0x02 - c.PONG = 0x03 - - local subscribe = love.thread.getChannel("SUB_STC_" .. name) - - function c:init() - - self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) - - function self:Fire(...) - local args = multi.pack(...) - if self.CID == THREAD_ID then -- Host Call - for _, link in pairs(self.links) do - love.thread.getChannel(link):push{self.TRIG, args} - end - self.proxy_conn:Fire(...) - else - self.subscribe:push{self.TRIG, args} - end - end - - local multi, thread = require("multi"):init() - self.links = {} - self.proxy_conn = multi:newConnection() - local mt = getmetatable(self.proxy_conn) - setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) - if self.CID == THREAD_ID then return self end - thread:newThread("STC_CONN_MAN" .. self.Name,function() - local item - local string_self_ref = "LSF_" .. multi.randomString(16) - local link_self_ref = love.thread.getChannel(string_self_ref) - self.subscribe:push{self.CONN, string_self_ref} - while true do - item = thread.hold(function() - return link_self_ref:peek() - end) - if item[1] == self.PING then - link_self_ref:push{self.PONG} - link_self_ref:pop() - elseif item[1] == self.CONN then - if string_self_ref ~= item[2] then - table.insert(self.links, love.thread.getChannel(item[2])) - end - link_self_ref:pop() - elseif item[1] == self.TRIG then - self.proxy_conn:Fire(multi.unpack(item[2])) - link_self_ref:pop() - else - -- This shouldn't be the case - end - end - end).OnError(multi.error) - return self - end - - local function remove(a, b) - local ai = {} - local r = {} - for k,v in pairs(a) do ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(r,a[k]) end - end - return r - end - - c.CID = THREAD_ID - c.Name = name - c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. - - -- Locals will only live in the thread that creates the original object - local ping - local pong = function(link, links) - local res = thread.hold(function() - return love.thread.getChannel(link):peek()[1] == c.PONG - end,{sleep=3}) - - if not res then - for i=1,#links do - if links[i] == link then - table.remove(links,i,link) - break - end - end - else - love.thread.getChannel(link):pop() - end - end - - ping = thread:newFunction(function(self) - ping:Pause() - - multi.ForEach(self.links, function(link) -- Sync new connections - love.thread.getChannel(link):push{self.PING} - multi:newThread("pong Thread", pong, link, self.links) - end) - - thread.sleep(3) - - ping:Resume() - end, false) - - local function fire(...) - for _, link in pairs(c.links) do - love.thread.getChannel(link):push {c.TRIG, multi.pack(...)} - end - end - - thread:newThread("STC_SUB_MAN"..name,function() - local item - while true do - thread.yield() - -- We need to check on broken connections - ping(c) -- Should return instantlly and process this in another thread - item = thread.hold(function() -- This will keep things held up until there is something to process - return c.subscribe:pop() - end) - if item[1] == c.CONN then - - multi.ForEach(c.links, function(link) -- Sync new connections - love.thread.getChannel(item[2]):push{c.CONN, link} - end) - c.links[#c.links+1] = item[2] - - elseif item[1] == c.TRIG then - fire(multi.unpack(item[2])) - c.proxy_conn:Fire(multi.unpack(item[2])) - end - end - end).OnError(multi.error) - --- ^^^ This will only exist in the init thread - - THREAD.package(name,c) - - self:create(c) - - return c -end -require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index a2a5d5f..3177adc 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -1,135 +1,8 @@ ---[[ -MIT License +local GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() -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 -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 ISTHREAD then - error("You cannot require the loveManager from within a thread!") -end - -local ThreadFileData = [[ -ISTHREAD = true -THREAD = require("multi.integration.loveManager.threads") -sThread = THREAD -__IMPORTS = {...} -__FUNC__=table.remove(__IMPORTS,1) -THREAD_ID=table.remove(__IMPORTS,1) -THREAD_NAME=table.remove(__IMPORTS,1) -math.randomseed(THREAD_ID) -math.random() -math.random() -math.random() -stab = THREAD.createStaticTable(THREAD_NAME .. THREAD_ID) -GLOBAL = THREAD.getGlobal() -if GLOBAL["__env"] then - local env = THREAD.unpackENV(GLOBAL["__env"]) - for i,v in pairs(env) do - _G[i] = v +return { + init = function(global_channel, console_channel, status_channel) + return GLOBAL, THREAD end -end -multi, thread = require("multi").init() -multi.integration={} -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -pcall(require,"multi.integration.loveManager.extensions") -pcall(require,"multi.integration.sharedExtensions") -stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} -]] - -local multi, thread = require("multi"):init() - -local THREAD = {} -_G.THREAD_NAME = "MAIN_THREAD" -_G.THREAD_ID = 0 -multi.integration = {} -local THREAD = require("multi.integration.loveManager.threads") -local GLOBAL = THREAD.getGlobal() -multi.isMainThread = true - -function multi:newSystemThread(name, func, ...) - THREAD_ID = THREAD_ID + 1 - local c = {} - c.name = name - c.ID = THREAD_ID - c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(THREAD.dump(func), c.ID, c.name, ...) - c.stab = THREAD.createStaticTable(name .. c.ID) - 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 - - function c:getName() return c.name end - thread:newThread(name .. "_System_Thread_Handler",function() - if name == "SystemThreaded Function Handler" then - local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) - thread.hold(function() - -- While the thread is running we might as well do something in the loop - if status_channel:peek() ~= nil then - c.statusconnector:Fire(multi.unpack(status_channel:pop())) - 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(multi.unpack(c.stab.returns)) - c.stab.returns = nil - end - end) - - if self.isActor then - self:create(c) - else - multi.create(multi, c) - end - - return c -end - -function THREAD:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return multi:newSystemThread("SystemThreaded Function Handler", func, ...) - end, holdme, multi.SFUNCTION)() -end - -THREAD.newSystemThread = function(...) - multi:newSystemThread(...) -end - -function love.threaderror(thread, errorstr) - multi.print("Thread error!\n" .. errorstr) -end - -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -require("multi.integration.loveManager.extensions") -require("multi.integration.sharedExtensions") -multi.print("Integrated Love Threading!") -return {init = function() return GLOBAL, THREAD end} +} diff --git a/integration/loveManager/serpent.lua b/integration/loveManager/serpent.lua new file mode 100644 index 0000000..5063f3a --- /dev/null +++ b/integration/loveManager/serpent.lua @@ -0,0 +1,176 @@ +--[[ +Serpent source is released under the MIT License + +Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com) + +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, sublicense, 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. +]] + +local n, v = "serpent", "0.303" -- (C) 2012-18 Paul Kulchenko; MIT License +local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" +local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} +local badtype = {thread = true, userdata = true, cdata = true} +local getmetatable = debug and debug.getmetatable or getmetatable +local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ +local keyword, globals, G = {}, {}, (_G or _ENV) +for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', + 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end +for k,v in pairs(G) do globals[v] = k end -- build func to name mapping +for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do + for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end + +local function s(t, opts) + local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum + local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge + local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) + local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring + local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) + local numformat = opts.numformat or "%.17g" + local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 + local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", + -- tostring(val) is needed because __tostring may return a non-string value + function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end + local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or numformat:format(s)) + or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 + or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end + -- handle radix changes in some locales + if opts.fixradix and (".1f"):format(1.2) ~= "1.2" then + local origsafestr = safestr + safestr = function(s) return type(s) == "number" + and (nohuge and snum[tostring(s)] or numformat:format(s):gsub(",",".")) or origsafestr(s) + end + end + local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end + local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal + and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end + local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] + local n = name == nil and '' or name + local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] + local safe = plain and n or '['..safestr(n)..']' + return (path or '')..(plain and path and '.' or '')..safe, safe end + local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding + local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} + local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end + table.sort(k, function(a,b) + -- sort numeric keys first: k[key] is not nil for numerical keys + return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) + < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end + local function val2str(t, name, indent, insref, path, plainindex, level) + local ttype, level, mt = type(t), (level or 0), getmetatable(t) + local spath, sname = safename(path, name) + local tag = plainindex and + ((type(name) == "number") and '' or name..space..'='..space) or + (name ~= nil and sname..space..'='..space or '') + if seen[t] then -- already seen this element + sref[#sref+1] = spath..space..'='..space..seen[t] + return tag..'nil'..comment('ref', level) + end + -- protect from those cases where __tostring may fail + if type(mt) == 'table' and metatostring ~= false then + local to, tr = pcall(function() return mt.__tostring(t) end) + local so, sr = pcall(function() return mt.__serialize(t) end) + if (to or so) then -- knows how to serialize itself + seen[t] = insref or spath + t = so and sr or tr + ttype = type(t) + end -- new value falls through to be serialized + end + if ttype == "table" then + if level >= maxl then return tag..'{}'..comment('maxlvl', level) end + seen[t] = insref or spath + if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty + if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end + local maxn, o, out = math.min(#t, maxnum or #t), {}, {} + for key = 1, maxn do o[key] = key end + if not maxnum or #o < maxnum then + local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables + for key in pairs(t) do + if o[key] ~= key then n = n + 1; o[n] = key end + end + end + if maxnum and #o > maxnum then o[maxnum+1] = nil end + if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end + local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) + for n, key in ipairs(o) do + local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse + if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing + or opts.keyallow and not opts.keyallow[key] + or opts.keyignore and opts.keyignore[key] + or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types + or sparse and value == nil then -- skipping nils; do nothing + elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then + if not seen[key] and not globals[key] then + sref[#sref+1] = 'placeholder' + local sname = safename(iname, gensym(key)) -- iname is table for local variables + sref[#sref] = val2str(key,sname,indent,sname,iname,true) + end + sref[#sref+1] = 'placeholder' + local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']' + sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path)) + else + out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1) + if maxlen then + maxlen = maxlen - #out[#out] + if maxlen < 0 then break end + end + end + end + local prefix = string.rep(indent or '', level) + local head = indent and '{\n'..prefix..indent or '{' + local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) + local tail = indent and "\n"..prefix..'}' or '}' + return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level) + elseif badtype[ttype] then + seen[t] = insref or spath + return tag..globerr(t, level) + elseif ttype == 'function' then + seen[t] = insref or spath + if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end + local ok, res = pcall(string.dump, t) + local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level) + return tag..(func or globerr(t, level)) + else return tag..safestr(t) end -- handle all other types + end + local sepr = indent and "\n" or ";"..space + local body = val2str(t, name, indent) -- this call also populates sref + local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' + local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' + return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" +end + +local function deserialize(data, opts) + local env = (opts and opts.safe == false) and G + or setmetatable({}, { + __index = function(t,k) return t end, + __call = function(t,...) error("cannot call functions") end + }) + local f, res = (loadstring or load)('return '..data, nil, nil, env) + if not f then f, res = (loadstring or load)(data, nil, nil, env) end + if not f then return f, res end + if setfenv then setfenv(f, env) end + return pcall(f) +end + +local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end +return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, + load = deserialize, + dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, + line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, + block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } \ No newline at end of file diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 777f811..eedcc02 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,228 +25,69 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") +local serpent = require("multi.integration.loveManager.serpent") local multi, thread = require("multi"):init() -local threads = {} -function threads.loadDump(d) - return loadstring(d:getString()) +local function ltype(data) + local tp = type(data) + if tp == "userdata" then + return data:type() + end + return tp end -function threads.dump(func) - return love.data.newByteData(string.dump(func)) -end +local NIL = love.data.newByteData("\3") -local fRef = {"func",nil} -local function manage(channel, value) - channel:clear() - if type(value) == "function" then - fRef[2] = THREAD.dump(value) - channel:push(fRef) - return +-- If a non table/function is supplied we just return it +local function packValue(t) + local tp = type(t) + if tp == "table" then + return love.data.newByteData("\1"..serpent.dump(t,{safe = true})) + elseif tp == "function" then + return love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) else - channel:push(value) + return t end 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() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) +-- If a non table/function is supplied we just return it +local function unpackValue(d) + if ltype(d) == "ByteData" then + local data = d:getString() + if data:sub(1, 1) == "\1" then + local status, data = serpent.load(data:sub(2,-1),{safe = false}) + if not status then + multi.error(data) + end + return data + elseif data:sub(1, 1) =="\2" then + local status, data = serpent.load(data:sub(2,-1),{safe = false}) + if not status then + multi.error(data) + end + return serpent.load(data:sub(2,-1))[1] + else + return d + end else - return dat + return d end end -function threads.waitFor(name) - if thread.isThread() then - return thread.hold(function() - return threads.get(name) - end) +local function createTable(n) + if not n then + n = "STAB"..multi.randomString(8) end - while threads.get(name)==nil do - love.timer.sleep(.001) - end - local dat = threads.get(name) - if type(dat) == "table" and dat.init then - dat.init = threads.loadDump(dat.init) - 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 threads.pushStatus(...) - local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) - local args = multi.pack(...) - status_channel:push(args) -end - -function threads.getThreads() - local t = {} - for i=1,GLOBAL["__THREAD_COUNT"] do - t[#t+1]=GLOBAL["__THREAD_"..i] - end - return t -end - -function threads.getThread(n) - return GLOBAL["__THREAD_"..n] -end - -function threads.sleep(n) - love.timer.sleep(n) -end - -function threads.getGlobal() - return setmetatable({}, - { - __index = function(t, k) - return THREAD.get(k) - end, - __newindex = function(t, k, v) - THREAD.set(k,v) - end - } - ) -end - -function threads.packENV(env) - local e = {} - for i,v in pairs(env) do - if type(v) == "function" then - e["$f"..i] = string.dump(v) - elseif type(v) == "table" then - e["$t"..i] = threads.packENV(v) - else - e[i] = v - end - end - return e -end - -function threads.unpackENV(env) - local e = {} - for i,v in pairs(env) do - if type(i) == "string" and i:sub(1,2) == "$f" then - e[i:sub(3,-1)] = loadstring(v) - elseif type(i) == "string" and i:sub(1,2) == "$t" then - e[i:sub(3,-1)] = threads.unpackENV(v) - else - e[i] = v - end - end - return e -end - - -function threads.setENV(env, name) - name = name or "__env" - (threads.getGlobal())[name] = threads.packENV(env) -end - -function threads.getENV(name) - name = name or "__env" - return threads.unpackENV((threads.getGlobal())[name]) -end - -function threads.exposeENV(name) - name = name or "__env" - local env = threads.getENV(name) - for i,v in pairs(env) do - _G[i] = v - end -end - -function threads.createTable(n) - local _proxy = {} - local function set(name,val) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - _proxy[name]:performAtomic(manage, val) - end - local function get(name) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - local dat = _proxy[name]:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end - -function threads.getConsole() - local c = {} - c.queue = love.thread.getChannel("__CONSOLE__") - function c.print(...) - c.queue:push(multi.pack(...)) - end - function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} - error(err) - end - return c -end - -if not ISTHREAD then - local queue = love.thread.getChannel("__CONSOLE__") - multi:newLoop(function(loop) - dat = queue:pop() - if dat then - print(multi.unpack(dat)) - end - end) -end - -function threads.createStaticTable(n) local __proxy = {} - local function set(name,val) - if __proxy[name] then return end - local chan = love.thread.getChannel(n..name) - if chan:getCount()>0 then return end - chan:performAtomic(manage, val) - __proxy[name] = val + local function set(name, val) + local chan = love.thread.getChannel(n .. name) + if chan:getCount() == 1 then chan:pop() end + __proxy[name] = true + chan:push(packValue(val)) end local function get(name) - if __proxy[name] then return __proxy[name] end - local dat = love.thread.getChannel(n..name):peek() - if type(dat)=="table" and dat[1]=="func" then - __proxy[name] = THREAD.loadDump(dat[2]) - return __proxy[name] - else - __proxy[name] = dat - return __proxy[name] - end + local dat = love.thread.getChannel(n .. name):peek() + return unpackValue(dat) end return setmetatable({}, { @@ -260,11 +101,94 @@ function threads.createStaticTable(n) ) end -function threads.hold(n) - local dat - while not(dat) do - dat = n() +function INIT(global_channel, console_channel, status_channel) + local GLOBAL, THREAD = createTable("GLOBAL"), {} + + -- Non portable methods, shouldn't be used unless you know what you are doing + THREAD.packValue = packValue + THREAD.unpackValue = unpackValue + THREAD.createTable = createTable + + function THREAD.set(name, val) + GLOBAL[name] = val end + + function THREAD.get(name, val) + return GLOBAL[name] + end + + function THREAD.waitFor(name) + local function wait() + math.randomseed(os.time()) + love.timer.sleep(.001) + end + repeat + wait() + until GLOBAL[name] + return GLOBAL[name] + end + + function THREAD.getCores() + return love.system.getProcessorCount() + end + + function THREAD.getConsole() + local c = {} + c.queue = console_channel + function c.print(...) + c.queue:push(table.concat(multi.pack(...), "\t")) + end + function c.error(err) + c.queue:push("Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err) + multi.error(err) + end + return c + end + + function THREAD.getThreads() + -- + end + + function THREAD.kill() -- trigger the lane destruction + error("Thread was killed!\1") + end + + function THREAD.pushStatus(...) + status_channel:push({THREAD_ID, multi.pack(...)}) + end + + _G.THREAD_ID = 0 + + function THREAD.sleep(n) + love.timer.sleep(n) + end + + function THREAD.hold(n) + -- + end + + function THREAD.setENV(env, name) + GLOBAL[name or "__env"] = env + end + + function THREAD.getENV(name) + return GLOBAL[name or "__env"] + end + + function THREAD.exposeENV(name) + name = name or "__env" + local env = THREAD.getENV(name) + for i,v in pairs(env) do + _G[i] = v + end + end + + return GLOBAL, THREAD end -return threads \ No newline at end of file +return { + -- These are the acutal channels + init = function(global_channel, console_channel, status_channel) + return INIT(global_channel, console_channel, status_channel) + end +} \ No newline at end of file diff --git a/integration/loveManagerold/extensions.lua b/integration/loveManagerold/extensions.lua new file mode 100644 index 0000000..36931b3 --- /dev/null +++ b/integration/loveManagerold/extensions.lua @@ -0,0 +1,389 @@ +--[[ +MIT License + +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 +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 not ISTHREAD then + multi, thread = require("multi").init() + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD +else + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD +end + +function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) + local c = {} + c.Name = name + c.Type = multi.SQUEUE + local fRef = {"\2",nil} + function c:init() + local q = {} + q.chan = love.thread.getChannel(self.Name) + function q:push(dat) + if type(dat) == "table" then + self.chan:push{"DATA",THREAD.packTable(dat)} + else + self.chan:push(dat) + end + -- if type(dat) == "function" then + -- fRef[2] = THREAD.dump(dat) + -- self.chan:push(fRef) + -- return + -- else + -- self.chan:push(dat) + -- end + end + function q:pop() + local dat = self.chan:pop() + if type(dat)=="table" and dat[1]=="DATA" then + return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) + else + return dat + end + end + function q:peek() + local dat = self.chan:peek() + if type(dat)=="table" and dat[1]=="DATA" then + return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) + else + return dat + end + end + return q + end + + THREAD.package(name,c) + + self:create(c) + + return c +end + +function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) + + local c = {} + + c.Name = name + c.Type = multi.STABLE + + function c:init() + return THREAD.createTable(self.Name) + end + + THREAD.package(name,c) + + self:create(c) + + return c +end + +local jqc = 1 +function multi:newSystemThreadedJobQueue(n) + local c = {} + + c.cores = n or THREAD.getCores() + c.registerQueue = {} + c.Type = multi.SJOBQUEUE + c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") + c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") + c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") + c.id = 0 + c.OnJobCompleted = multi:newConnection() + + local allfunc = 0 + + function c:doToAll(func) + local f = THREAD.dump(func) + for i = 1, self.cores do + self.queueAll:push({allfunc,f}) + end + allfunc = allfunc + 1 + end + function c:registerFunction(name,func) + if self.funcs[name] then + error("A function by the name "..name.." has already been registered!") + end + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + function c:isEmpty() + return queueJob:peek()==nil + end + local nFunc = 0 + function c:newFunction(name,func,holup) -- This registers with the queue + if type(name)=="function" then + holup = func + func = name + name = "JQ_Function_"..nFunc + end + nFunc = nFunc + 1 + c:registerFunction(name,func) + return thread:newFunction(function(...) + local id = c:pushJob(name,...) + local link + local rets + link = c.OnJobCompleted(function(jid,...) + if id==jid then + rets = multi.pack(...) + end + end) + return thread.hold(function() + if rets then + return multi.unpack(rets) or multi.NIL + end + end) + end,holup),name + end + thread:newThread("jobManager",function() + while true do + thread.yield() + local dat = c.queueReturn:pop() + if dat then + c.OnJobCompleted:Fire(multi.unpack(dat)) + end + end + end) + for i=1,c.cores do + multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + local multi, thread = require("multi"):init() + require("love.timer") + local function atomic(channel) + return channel:pop() + end + local clock = os.clock + local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") + local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") + local lastProc = clock() + local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") + local registry = {} + _G["__QR"] = queueReturn + setmetatable(_G,{__index = funcs}) + thread:newThread("startUp",function() + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + end + end) + thread:newThread("runner",function() + thread.sleep(.1) + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + local dat = queue:performAtomic(atomic) + if dat then + multi:newThread("Test",function() + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](multi.unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end) + end + end + end):OnError(function(...) + error(...) + end) + thread:newThread("Idler",function() + while true do + thread.yield() + if clock()-lastProc> 2 then + THREAD.sleep(.05) + else + THREAD.sleep(.001) + end + end + end) + multi:mainloop() + end,jqc) + end + + jqc = jqc + 1 + + self:create(c) + + return c +end + +function multi:newSystemThreadedConnection(name) + local name = name or multi.randomString(16) + + local c = {} + + c.Type = multi.SCONNECTION + c.CONN = 0x00 + c.TRIG = 0x01 + c.PING = 0x02 + c.PONG = 0x03 + + local subscribe = love.thread.getChannel("SUB_STC_" .. name) + + function c:init() + + self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) + + function self:Fire(...) + local args = multi.pack(...) + if self.CID == THREAD_ID then -- Host Call + for _, link in pairs(self.links) do + love.thread.getChannel(link):push{self.TRIG, args} + end + self.proxy_conn:Fire(...) + else + self.subscribe:push{self.TRIG, args} + end + end + + local multi, thread = require("multi"):init() + self.links = {} + self.proxy_conn = multi:newConnection() + local mt = getmetatable(self.proxy_conn) + setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) + if self.CID == THREAD_ID then return self end + thread:newThread("STC_CONN_MAN" .. self.Name,function() + local item + local string_self_ref = "LSF_" .. multi.randomString(16) + local link_self_ref = love.thread.getChannel(string_self_ref) + self.subscribe:push{self.CONN, string_self_ref} + while true do + item = thread.hold(function() + return link_self_ref:peek() + end) + if item[1] == self.PING then + link_self_ref:push{self.PONG} + link_self_ref:pop() + elseif item[1] == self.CONN then + if string_self_ref ~= item[2] then + table.insert(self.links, love.thread.getChannel(item[2])) + end + link_self_ref:pop() + elseif item[1] == self.TRIG then + self.proxy_conn:Fire(multi.unpack(item[2])) + link_self_ref:pop() + else + -- This shouldn't be the case + end + end + end).OnError(multi.error) + return self + end + + local function remove(a, b) + local ai = {} + local r = {} + for k,v in pairs(a) do ai[v]=true end + for k,v in pairs(b) do + if ai[v]==nil then table.insert(r,a[k]) end + end + return r + end + + c.CID = THREAD_ID + c.Name = name + c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. + + -- Locals will only live in the thread that creates the original object + local ping + local pong = function(link, links) + local res = thread.hold(function() + return love.thread.getChannel(link):peek()[1] == c.PONG + end,{sleep=3}) + + if not res then + for i=1,#links do + if links[i] == link then + table.remove(links,i,link) + break + end + end + else + love.thread.getChannel(link):pop() + end + end + + ping = thread:newFunction(function(self) + ping:Pause() + + multi.ForEach(self.links, function(link) -- Sync new connections + love.thread.getChannel(link):push{self.PING} + multi:newThread("pong Thread", pong, link, self.links) + end) + + thread.sleep(3) + + ping:Resume() + end, false) + + local function fire(...) + for _, link in pairs(c.links) do + love.thread.getChannel(link):push {c.TRIG, multi.pack(...)} + end + end + + thread:newThread("STC_SUB_MAN"..name,function() + local item + while true do + thread.yield() + -- We need to check on broken connections + ping(c) -- Should return instantlly and process this in another thread + item = thread.hold(function() -- This will keep things held up until there is something to process + return c.subscribe:pop() + end) + if item[1] == c.CONN then + + multi.ForEach(c.links, function(link) -- Sync new connections + love.thread.getChannel(item[2]):push{c.CONN, link} + end) + c.links[#c.links+1] = item[2] + + elseif item[1] == c.TRIG then + fire(multi.unpack(item[2])) + c.proxy_conn:Fire(multi.unpack(item[2])) + end + end + end).OnError(multi.error) + --- ^^^ This will only exist in the init thread + + THREAD.package(name,c) + + self:create(c) + + return c +end +require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/loveManagerold/init.lua b/integration/loveManagerold/init.lua new file mode 100644 index 0000000..f913103 --- /dev/null +++ b/integration/loveManagerold/init.lua @@ -0,0 +1,136 @@ +--[[ +MIT License + +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 +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 ISTHREAD then + error("You cannot require the loveManager from within a thread!") +end + +local ThreadFileData = [[ +ISTHREAD = true +THREAD = require("multi.integration.loveManager.threads") +sThread = THREAD +__IMPORTS = {...} +__FUNC__=table.remove(__IMPORTS,1) +THREAD_ID=table.remove(__IMPORTS,1) +THREAD_NAME=table.remove(__IMPORTS,1) +math.randomseed(THREAD_ID) +math.random() +math.random() +math.random() +stab = THREAD.createStaticTable(THREAD_NAME .. THREAD_ID) +GLOBAL = THREAD.getGlobal() +if GLOBAL["__env"] then + local env = THREAD.unpackENV(GLOBAL["__env"]) + for i,v in pairs(env) do + _G[i] = v + end +end +multi, thread = require("multi").init() +multi.integration={} +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +pcall(require,"multi.integration.loveManager.extensions") +pcall(require,"multi.integration.sharedExtensions") +stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} +]] + +local multi, thread = require("multi"):init() + +local THREAD = {} +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 +multi.integration = {} +local THREAD = require("multi.integration.loveManager.threads") +local GLOBAL = THREAD.getGlobal() +multi.isMainThread = true + +function multi:newSystemThread(name, func, ...) + THREAD_ID = THREAD_ID + 1 + local c = {} + c.Type = multi.STHREAD + c.name = name + c.ID = THREAD_ID + c.thread = love.thread.newThread(ThreadFileData) + c.thread:start(THREAD.dump(func), c.ID, c.name, ...) + c.stab = THREAD.createStaticTable(name .. c.ID) + 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 + + function c:getName() return c.name end + thread:newThread(name .. "_System_Thread_Handler",function() + if name == "SystemThreaded Function Handler" then + local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) + thread.hold(function() + -- While the thread is running we might as well do something in the loop + if status_channel:peek() ~= nil then + c.statusconnector:Fire(multi.unpack(status_channel:pop())) + 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(multi.unpack(c.stab.returns)) + c.stab.returns = nil + end + end) + + if self.isActor then + self:create(c) + else + multi.create(multi, c) + end + + return c +end + +function THREAD:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("SystemThreaded Function Handler", func, ...) + end, holdme, multi.SFUNCTION)() +end + +THREAD.newSystemThread = function(...) + multi:newSystemThread(...) +end + +function love.threaderror(thread, errorstr) + multi.print("Thread error!\n" .. errorstr) +end + +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +require("multi.integration.loveManager.extensions") +require("multi.integration.sharedExtensions") +multi.print("Integrated Love Threading!") +return {init = function() return GLOBAL, THREAD end} diff --git a/integration/loveManagerold/threads.lua b/integration/loveManagerold/threads.lua new file mode 100644 index 0000000..6a2f25a --- /dev/null +++ b/integration/loveManagerold/threads.lua @@ -0,0 +1,258 @@ +--[[ +MIT License + +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 +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. +]] +require("love.timer") +require("love.system") +require("love.data") +require("love.thread") +local serpent = require("multi.integration.loveManager.serpent") +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 + +function threads.packTable(table) + return love.data.newByteData(serpent.dump(table)) +end + +function threads.unpackTable(data) + return serpent.load(data:getString()) +end + +local fRef = {"func",nil} +local function manage(channel, value) + channel:clear() + print("pushing",value) + if type(value) == "table" then + channel:push{"DATA",threads.packTable(value)} + else + channel:push(value) + end +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() + if type(dat)=="table" and dat[1]=="DATA" then + return threads.unpackTable(dat[2]) + else + return dat + end +end + +function threads.waitFor(name) + if thread.isThread() then + return thread.hold(function() + return threads.get(name) + end) + end + while threads.get(name)==nil do + love.timer.sleep(.001) + end + local dat = threads.get(name) + if type(dat) == "table" and dat.init then + dat.init = threads.loadDump(dat.init) + 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 threads.pushStatus(...) + local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) + local args = multi.pack(...) + status_channel:push(args) +end + +function threads.getThreads() + local t = {} + for i=1,GLOBAL["__THREAD_COUNT"] do + t[#t+1]=GLOBAL["__THREAD_"..i] + end + return t +end + +function threads.getThread(n) + return GLOBAL["__THREAD_"..n] +end + +function threads.sleep(n) + love.timer.sleep(n) +end + +function threads.getGlobal() + return setmetatable({}, + { + __index = function(t, k) + return THREAD.get(k) + end, + __newindex = function(t, k, v) + THREAD.set(k,v) + end + } + ) +end + +function threads.packENV(env) + return threads.packTable(env) +end + +function threads.unpackENV(env) + return threads.unpackTable(env) +end + + +function threads.setENV(env, name) + name = name or "__env" + (threads.getGlobal())[name] = threads.packTable(env) +end + +function threads.getENV(name) + name = name or "__env" + return threads.unpackTable((threads.getGlobal())[name]) +end + +function threads.exposeENV(name) + name = name or "__env" + local env = threads.getENV(name) + for i,v in pairs(env) do + _G[i] = v + end +end + +function threads.createTable(n) + local _proxy = {} + local function set(name,val) + if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end + _proxy[name]:performAtomic(manage, val) + end + local function get(name) + if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end + local dat = _proxy[name]:peek() + if type(dat)=="table" and dat[1]=="DATA" then + return threads.unpackTable(dat[2]) + else + return dat + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +end + +function threads.getConsole() + local c = {} + c.queue = love.thread.getChannel("__CONSOLE__") + function c.print(...) + c.queue:push(multi.pack(...)) + end + function c.error(err) + c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} + error(err) + end + return c +end + +if not ISTHREAD then + local queue = love.thread.getChannel("__CONSOLE__") + multi:newLoop(function(loop) + dat = queue:pop() + if dat then + print(multi.unpack(dat)) + end + end) +end + +function threads.createStaticTable(n) + local __proxy = {} + local function set(name,val) + if __proxy[name] then return end + local chan = love.thread.getChannel(n..name) + if chan:getCount()>0 then return end + chan:performAtomic(manage, val) + __proxy[name] = val + end + local function get(name) + if __proxy[name] then return __proxy[name] end + local dat = love.thread.getChannel(n..name):peek() + if type(dat)=="table" and dat[1]=="func" then + __proxy[name] = threads.loadDump(dat[2]) + return __proxy[name] + else + __proxy[name] = dat + return __proxy[name] + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +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/integration/luvitManager.lua b/integration/luvitManager.lua index c1e8dcb..46a0dc2 100644 --- a/integration/luvitManager.lua +++ b/integration/luvitManager.lua @@ -107,7 +107,7 @@ local function _INIT(luvitThread, timer) local c = {} local __self = c c.name = name - c.Type = "sthread" + c.Type = multi.STHREAD c.thread = {} c.func = string.dump(func) function c:kill() diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 615f85d..f3d8fed 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -177,13 +177,21 @@ multi:newThread("Scheduler Thread",function() -- multi.error("SystemThreadedConnections: Failed") -- end -- multi.success("SystemThreadedConnections: Ok") - - local stp = multi:newSystemThreadedProcessor(1) - print(stp) - local tloop = stp:newTLoop(nil, 1) - print(2) local proxy_test = false - print(3) + multi:newThread(function() + t, val = thread.hold(function() + return proxy_test + end,{sleep=5}) + if val == multi.TIMEOUT then + multi.error("SystemThreadedProcessor/Proxies: Failed") + end + thread.sleep(1) + os.exit(1) + end) + local stp = multi:newSystemThreadedProcessor(1) + + local tloop = stp:newTLoop(nil, 1) + multi:newSystemThread("Testing proxy copy THREAD",function(tloop) local multi, thread = require("multi"):init() tloop = tloop:init() @@ -200,10 +208,10 @@ multi:newThread("Scheduler Thread",function() end) multi:mainloop() end, tloop:getTransferable()).OnError(multi.error) - print(4) + multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) - print(5) + thread:newThread(function() multi.print("Testing holding on a proxy connection!") thread.hold(tloop.OnLoop) @@ -239,7 +247,7 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedProcessor: OK") we_good = true - multi:Stop() + multi:Stop() -- Needed in love2d tests to stop the main runner os.exit() end).OnError(multi.error) -- 2.43.0 From afdd98ab13c03c6ac663d193181dc29f7cd9dcdd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 15 Jul 2023 01:16:03 -0400 Subject: [PATCH 083/117] Rewriting love2d threading binding --- init.lua | 15 ++-- integration/lanesManager/init.lua | 13 ++-- integration/loveManager/init.lua | 116 +++++++++++++++++++++++++++- integration/loveManager/threads.lua | 29 ++++--- lovethreads/main.lua | 26 ++++++- 5 files changed, 164 insertions(+), 35 deletions(-) diff --git a/init.lua b/init.lua index 7c79a4a..59172fe 100644 --- a/init.lua +++ b/init.lua @@ -1630,14 +1630,11 @@ function thread:newThread(name, func, ...) c.Destroy = c.Kill if thread.isThread() then - multi:newLoop(function(loop) - if self.Type == multi.PROCESS then - table.insert(self.startme, c) - else - table.insert(threadManager.startme, c) - end - loop:Break() - end) + if self.Type == multi.PROCESS then + table.insert(self.startme, c) + else + table.insert(threadManager.startme, c) + end else if self.Type == multi.PROCESS then table.insert(self.startme, c) @@ -2411,8 +2408,8 @@ function multi.error(self, err) else io.write("\x1b[91mERROR:\x1b[0m " .. err .. " ?\n") end - error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") if multi.defaultSettings.error then + error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") os.exit(1) end end diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 014294e..dbf7eb3 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -70,9 +70,8 @@ function multi:newSystemThread(name, func, ...) local rand = math.random(1, 10000000) local return_linda = lanes.linda() c = {} - c.name = name c.Name = name - c.Id = count + c.ID = count c.loadString = {"base","package","os","io","math","table","string","coroutine"} livingThreads[count] = {true, name} c.returns = return_linda @@ -162,13 +161,13 @@ function multi.InitSystemThreadErrorHandler() for i = #threads, 1, -1 do temp = threads[i] status = temp.thread.status - push = __StatusLinda:get(temp.Id) + push = __StatusLinda:get(temp.ID) if push then - temp.statusconnector:Fire(multi.unpack(({__StatusLinda:receive(nil, temp.Id)})[2])) + temp.statusconnector:Fire(multi.unpack(({__StatusLinda:receive(nil, temp.ID)})[2])) end if status == "done" or temp.returns:get("returns") then returns = ({temp.returns:receive(0, "returns")})[2] - livingThreads[temp.Id] = {false, temp.Name} + livingThreads[temp.ID] = {false, temp.Name} temp.alive = false if returns[1] == false then temp.OnError:Fire(temp, returns[2]) @@ -185,13 +184,13 @@ function multi.InitSystemThreadErrorHandler() elseif status == "error" then -- The thread never really errors, we handle this through our linda object elseif status == "cancelled" then - livingThreads[temp.Id] = {false, temp.Name} + 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} + livingThreads[temp.ID] = {false, temp.Name} temp.alive = false temp.OnError:Fire(temp,"thread_killed") GLOBAL["__THREADS__"] = livingThreads diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 3177adc..849249a 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -1,8 +1,122 @@ +if ISTHREAD then + error("You cannot require the loveManager from within a thread!") +end + +local ThreadFileData = [[ +ISTHREAD = true +__FUNC, THREAD_ID, THREAD_NAME, __PACK = ... +GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() +__FUNC = THREAD.unpackValue(__FUNC) +__PACK = THREAD.unpackValue(__PACK) +math.randomseed(THREAD_ID) +math.random() +math.random() +math.random() +stab = THREAD.createTable(THREAD_NAME .. THREAD_ID) +if GLOBAL["__env"] then + local env = THREAD.unpackENV(GLOBAL["__env"]) + for i,v in pairs(env) do + _G[i] = v + end +end +multi, thread = require("multi"):init() +require("multi.integration.loveManager.extensions") +require("multi.integration.sharedExtensions") +stab["returns"] = {__FUNC(multi.unpack(__PACK))} +]] + +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 + +local multi, thread = require("multi"):init() local GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() +multi.integration = {} +multi.isMainThread = true +local threads = {} +local tid = 0 +function multi:newSystemThread(name, func, ...) + multi.InitSystemThreadErrorHandler() + local name = name or multi.randomString(16) + tid = tid + 1 + local c = {} + c.Type = multi.STHREAD + c.Name = name + c.ID = tid + c.thread = love.thread.newThread(ThreadFileData) + c.thread:start(THREAD.packValue(func), c.ID, c.Name, THREAD.packValue({...})) + c.stab = THREAD.createTable(name .. c.ID) + c.creationTime = os.clock() + c.OnDeath = multi:newConnection() + c.OnError = multi:newConnection() + c.status_channel = love.thread.getChannel("__status_channel__" .. c.ID) + + function c:getName() return c.name end + + table.insert(threads, c) + + if self.isActor then + self:create(c) + else + multi.create(multi, c) + end + + return c +end + +local started = false +local console_channel = love.thread.getChannel("__console_channel__") + +function THREAD:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return multi:newSystemThread("SystemThreaded Function Handler", func, ...) + end, holdme, multi.SFUNCTION)() +end + +function love.threaderror(thread, errorstr) + multi.error("Thread error! " .. errorstr) +end + +function multi.InitSystemThreadErrorHandler() + if started == true then return end + started = true + thread:newThread("Love System Thread Handler", function() + while true do + thread.yield() + for i = #threads, 1, -1 do + local th = threads[i] + if th.status_channel:peek() ~= nil then + th.statusconnector:Fire(multi.unpack(th.status_channel:pop())) + end + local th_err = th.thread:getError() + if th_err == "Thread Killed!\1" then + th.OnDeath:Fire("Thread Killed!") + table.remove(threads, i) + elseif th_err then + th.OnError:Fire(th, th_err) + table.remove(threads, i) + elseif th.stab.returns then + th.OnDeath:Fire(multi.unpack(th.stab.returns)) + th.stab.returns = nil + table.remove(threads, i) + end + end + end + end) +end + +THREAD.newSystemThread = function(...) + multi:newSystemThread(...) +end + +multi.integration.GLOBAL = GLOBAL +multi.integration.THREAD = THREAD +require("multi.integration.loveManager.extensions") +require("multi.integration.sharedExtensions") +multi.print("Integrated Love Threading!") return { - init = function(global_channel, console_channel, status_channel) + init = function() return GLOBAL, THREAD end } diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index eedcc02..e7c049e 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -65,7 +65,7 @@ local function unpackValue(d) if not status then multi.error(data) end - return serpent.load(data:sub(2,-1))[1] + return data[1] else return d end @@ -101,8 +101,10 @@ local function createTable(n) ) end -function INIT(global_channel, console_channel, status_channel) - local GLOBAL, THREAD = createTable("GLOBAL"), {} +function INIT() + local GLOBAL, THREAD = createTable("__GLOBAL__"), {} + local status_channel, console_channel = love.thread.getChannel("__status_channel__" .. THREAD_ID), + love.thread.getChannel("__console_channel__") -- Non portable methods, shouldn't be used unless you know what you are doing THREAD.packValue = packValue @@ -117,16 +119,16 @@ function INIT(global_channel, console_channel, status_channel) return GLOBAL[name] end - function THREAD.waitFor(name) + THREAD.waitFor = thread:newFunction(function(name) local function wait() math.randomseed(os.time()) - love.timer.sleep(.001) + thread.yield() end repeat wait() until GLOBAL[name] return GLOBAL[name] - end + end, true) function THREAD.getCores() return love.system.getProcessorCount() @@ -154,18 +156,16 @@ function INIT(global_channel, console_channel, status_channel) end function THREAD.pushStatus(...) - status_channel:push({THREAD_ID, multi.pack(...)}) + status_channel:push(multi.pack(...)) end - _G.THREAD_ID = 0 - function THREAD.sleep(n) love.timer.sleep(n) end - function THREAD.hold(n) - -- - end + THREAD.hold = thread:newFunction(function(n) + thread.hold(n) + end, true) function THREAD.setENV(env, name) GLOBAL[name or "__env"] = env @@ -187,8 +187,7 @@ function INIT(global_channel, console_channel, status_channel) end return { - -- These are the acutal channels - init = function(global_channel, console_channel, status_channel) - return INIT(global_channel, console_channel, status_channel) + init = function() + return INIT() end } \ No newline at end of file diff --git a/lovethreads/main.lua b/lovethreads/main.lua index 5016548..c06a9ff 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -1,7 +1,7 @@ package.path = "../?/init.lua;../?.lua;"..package.path local multi, thread = require("multi"):init() -local GLOBAL, THREAD = require("multi.integration.loveManager"):init() +GLOBAL, THREAD = require("multi.integration.loveManager"):init() GLOBAL["Test"] = {1,2,3, function() print("HI") end} @@ -10,8 +10,28 @@ for i,v in pairs(GLOBAL["Test"]) do if type(v) == "function" then v() end end -multi:newAlarm(3):OnRing(function() - GLOBAL["Test2"] = "We got a value!" +local test = multi:newSystemThread("Test",function() + print("THREAD_ID:",THREAD_ID) + GLOBAL["Test2"] = "We did it!" + eror("hahaha") + return 1,2,3 +end) + +test.OnDeath(function(a,b,c) + print("Thread finished!",a,b,c) +end) + +test.OnError(function(self, err) + print("Got Error!",err) +end) + +local func = THREAD:newFunction(function(a,b,c) + print("let's do this!",1,2,3) + return true +end) + +func(1,2,3).OnReturn(function(ret) + print("Done",ret) end) thread:newThread(function() -- 2.43.0 From 9e6552d42e1506bbc15e07239ad534158c709bad Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 16 Jul 2023 11:10:35 -0400 Subject: [PATCH 084/117] Working on adding a Hold method to all objects. Will document how they all work when done. --- init.lua | 42 ++-- integration/lanesManager/extensions.lua | 28 +++ integration/loveManager/extensions.lua | 242 ++++++++++++++++++++++++ integration/loveManager/threads.lua | 6 +- lovethreads/main.lua | 45 ++--- 5 files changed, 308 insertions(+), 55 deletions(-) diff --git a/init.lua b/init.lua index 59172fe..821e0f1 100644 --- a/init.lua +++ b/init.lua @@ -482,9 +482,9 @@ function multi:newConnection(protect,func,kill) return temp end - c.Hold = thread:newFunction(function(self) - return thread.hold(self) - end, true) + function c:Hold(self) + return multi.hold(self) + end c.connect=c.Connect c.GetConnection=c.getConnection @@ -1158,36 +1158,18 @@ function multi:newProcessor(name, nothread, priority) end function multi.hold(func,opt) - if thread.isThread() then - if type(func) == "function" or type(func) == "table" then - return thread.hold(func,opt) - end - return thread.sleep(func) - end - local death = false + if thread.isThread() then return thread.hold(func, opt) end local proc = multi.getCurrentTask() proc:Pause() - if type(func)=="number" then - thread:newThread("Hold_func",function() - thread.hold(func) - death = true - end) - while not death do - multi:uManager() - end - proc:Resume() - else - local rets - thread:newThread("Hold_func",function() - rets = {thread.hold(func,opt)} - death = true - end) - while not death do - multi:uManager() - end - proc:Resume() - return multi.unpack(rets) + local rets + thread:newThread("Hold_func",function() + rets = {thread.hold(func,opt)} + end) + while rets == nil do + multi:uManager() end + proc:Resume() + return multi.unpack(rets) end -- Threading stuff diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 343dd8b..d622aab 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -58,6 +58,19 @@ function multi:newSystemThreadedQueue(name) GLOBAL[name] = c end + function c:Hold(opt) + local multi, thread = require("multi"):init() + if opt.peek then + return thread.hold(function() + return self:peek() + end) + else + return thread.hold(function() + return self:pop() + end) + end + end + self:create(c) return c @@ -89,6 +102,17 @@ function multi:newSystemThreadedTable(name) GLOBAL[name] = c end + function c:Hold(opt) + local multi, thread = require("multi"):init() + if opt.key then + return thread.hold(function() + return self.tab[opt.key] + end) + else + multi.error("Must provide a key to check opt.key = 'key'") + end + end + self:create(c) return c @@ -214,6 +238,10 @@ function multi:newSystemThreadedJobQueue(n) end,i).OnError(multi.error) end + function c:Hold(opt) + return thread.hold(self.OnJobCompleted) + end + self:create(c) return c diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index e69de29..2b78793 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -0,0 +1,242 @@ +if not ISTHREAD then + multi, thread = require("multi").init() + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD +end + +function multi:newSystemThreadedQueue(name) + local name = name or multi.randomString(16) + + local c = {} + + c.Name = name + c.Type = multi.SQUEUE + c.chan = love.thread.getChannel(name) + + function c:push(dat) + self.chan:push(THREAD.packValue(dat)) + end + + function c:pop() + return THREAD.unpackValue(self.chan:pop()) + end + + function c:peek() + return THREAD.unpackValue(self.chan:peek()) + end + + function c:init() + self.chan = love.thread.getChannel(self.Name) + return self + end + + function c:Hold(opt) + if opt.peek then + return thread.hold(function() + return self:peek() + end) + else + return thread.hold(function() + return self:pop() + end) + end + end + + GLOBAL[name] = c + + self:create(c) + + return c +end + +function multi:newSystemThreadedTable(name) + local name = name or multi.randomString(16) + + local c = {} + + c.Name = name + c.Type = multi.STABLE + c.tab = THREAD.createTable(name) + + function c:init() + self.tab = THREAD.createTable(self.Name) + setmetatable(self,{ + __index = function(t, k) + print("Getting...", k) + return self.tab[k] + end, + __newindex = function(t,k,v) + print("Setting...", k, v) + self.tab[k] = v + end + }) + return self + end + + function c:Hold(opt) + if opt.key then + return thread.hold(function() + return self.tab[opt.key] + end) + else + multi.error("Must provide a key to check opt.key = 'key'") + end + end + + setmetatable(c,{ + __index = function(t, k) + print("Getting...", k) + return c.tab[k] + end, + __newindex = function(t,k,v) + print("Setting...", k, v) + c.tab[k] = v + end + }) + + GLOBAL[name] = c + + self:create(c) + + return c +end + +local jqc = 1 +function multi:newSystemThreadedJobQueue(n) + local c = {} + + c.cores = n or THREAD.getCores() + c.registerQueue = {} + c.Type = multi.SJOBQUEUE + c.funcs = THREAD.createTable("__JobQueue_"..jqc.."_table") + c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue") + c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn") + c.queueAll = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueAll") + c.id = 0 + c.OnJobCompleted = multi:newConnection() + + local allfunc = 0 + + function c:doToAll(func) + for i = 1, self.cores do + self.queueAll:push({allfunc, func}) + end + allfunc = allfunc + 1 + end + function c:registerFunction(name, func) + if self.funcs[name] then + multi.error("A function by the name "..name.." has already been registered!") + end + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + function c:isEmpty() + return queueJob:peek()==nil + end + local nFunc = 0 + function c:newFunction(name,func,holup) -- This registers with the queue + if type(name)=="function" then + holup = func + func = name + name = "JQ_Function_"..nFunc + end + nFunc = nFunc + 1 + c:registerFunction(name,func) + return thread:newFunction(function(...) + local id = c:pushJob(name,...) + local link + local rets + link = c.OnJobCompleted(function(jid,...) + if id==jid then + rets = multi.pack(...) + end + end) + return thread.hold(function() + if rets then + return multi.unpack(rets) or multi.NIL + end + end) + end,holup),name + end + thread:newThread("jobManager",function() + while true do + thread.yield() + local dat = c.queueReturn:pop() + if dat then + c.OnJobCompleted:Fire(multi.unpack(dat)) + end + end + end) + for i=1,c.cores do + multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + local multi, thread = require("multi"):init() + require("love.timer") + local clock = os.clock + local funcs = THREAD.createTable("__JobQueue_"..jqc.."_table") + local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue") + local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn") + local lastProc = clock() + local queueAll = THREAD.waitFor("__JobQueue_"..jqc.."_queueAll") + local registry = {} + _G["__QR"] = queueReturn + setmetatable(_G,{__index = funcs}) + thread:newThread("startUp",function() + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + queueAll:pop()[2]() + end + end + end) + thread:newThread("runner",function() + thread.sleep(.1) + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + queueAll:pop()[2]() + end + local dat = thread.hold(queue) + if dat then + multi:newThread("Test",function() + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](multi.unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end) + end + end + end).OnError(multi.error) + thread:newThread("Idler",function() + while true do + thread.yield() + if clock()-lastProc> 2 then + THREAD.sleep(.05) + else + THREAD.sleep(.001) + end + end + end) + multi:mainloop() + end,jqc) + end + + function c:Hold(opt) + return thread.hold(self.OnJobCompleted) + end + + jqc = jqc + 1 + + self:create(c) + + return c +end \ No newline at end of file diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index e7c049e..6610690 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -127,7 +127,11 @@ function INIT() repeat wait() until GLOBAL[name] - return GLOBAL[name] + if type(GLOBAL[name].init) == "function" then + return GLOBAL[name]:init() + else + return GLOBAL[name] + end end, true) function THREAD.getCores() diff --git a/lovethreads/main.lua b/lovethreads/main.lua index c06a9ff..199dc70 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -1,42 +1,39 @@ package.path = "../?/init.lua;../?.lua;"..package.path -local multi, thread = require("multi"):init() +local multi, thread = require("multi"):init{print=true, warning = true, error=true} GLOBAL, THREAD = require("multi.integration.loveManager"):init() -GLOBAL["Test"] = {1,2,3, function() print("HI") end} - -for i,v in pairs(GLOBAL["Test"]) do - print(i,v) - if type(v) == "function" then v() end -end +local queue = multi:newSystemThreadedQueue("TestQueue") +local tab = multi:newSystemThreadedTable("TestTable") local test = multi:newSystemThread("Test",function() + local queue = THREAD.waitFor("TestQueue") + local tab = THREAD.waitFor("TestTable") print("THREAD_ID:",THREAD_ID) - GLOBAL["Test2"] = "We did it!" - eror("hahaha") + queue:push("Did it work?") + tab["Test"] = true return 1,2,3 end) -test.OnDeath(function(a,b,c) - print("Thread finished!",a,b,c) +multi:newThread("QueueTest", function() + print(thread.hold(queue)) + print(thread.hold(tab, {key="Test"})) + print("Done!") end) -test.OnError(function(self, err) - print("Got Error!",err) +local jq = multi:newSystemThreadedJobQueue(n) + +jq:registerFunction("test",function(a, b, c) + print(a, b+c) + return a+b+c end) -local func = THREAD:newFunction(function(a,b,c) - print("let's do this!",1,2,3) - return true -end) +print("Job:",jq:pushJob("test",1,2,3)) +print("Job:",jq:pushJob("test",2,3,4)) +print("Job:",jq:pushJob("test",5,6,7)) -func(1,2,3).OnReturn(function(ret) - print("Done",ret) -end) - -thread:newThread(function() - print("Waiting...") - print(THREAD.waitFor("Test2")) +jq.OnJobCompleted(function(...) + print("Job Completed!", ...) end) function love.draw() -- 2.43.0 From bbaac2d779a1c720a8108ed634f5308ad1340f99 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 17 Jul 2023 00:44:59 -0400 Subject: [PATCH 085/117] jobqueues having isues with stp --- integration/lanesManager/extensions.lua | 8 ++++---- integration/loveManager/extensions.lua | 4 ---- integration/loveManager/init.lua | 2 +- integration/sharedExtensions/init.lua | 13 +++++++------ lovethreads/main.lua | 5 +++++ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index d622aab..37526c7 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -123,10 +123,10 @@ function multi:newSystemThreadedJobQueue(n) c.cores = n or THREAD.getCores()*2 c.Type = multi.SJOBQUEUE c.OnJobCompleted = multi:newConnection() - local funcs = multi:newSystemThreadedTable():init() - local queueJob = multi:newSystemThreadedQueue():init() - local queueReturn = multi:newSystemThreadedQueue():init() - local doAll = multi:newSystemThreadedQueue():init() + local funcs = multi:newSystemThreadedTable() + local queueJob = multi:newSystemThreadedQueue() + local queueReturn = multi:newSystemThreadedQueue() + local doAll = multi:newSystemThreadedQueue() local ID=1 local jid = 1 function c:isEmpty() diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 2b78793..c30adc9 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -62,11 +62,9 @@ function multi:newSystemThreadedTable(name) self.tab = THREAD.createTable(self.Name) setmetatable(self,{ __index = function(t, k) - print("Getting...", k) return self.tab[k] end, __newindex = function(t,k,v) - print("Setting...", k, v) self.tab[k] = v end }) @@ -85,11 +83,9 @@ function multi:newSystemThreadedTable(name) setmetatable(c,{ __index = function(t, k) - print("Getting...", k) return c.tab[k] end, __newindex = function(t,k,v) - print("Setting...", k, v) c.tab[k] = v end }) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 849249a..0bc9577 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -14,7 +14,7 @@ math.random() math.random() stab = THREAD.createTable(THREAD_NAME .. THREAD_ID) if GLOBAL["__env"] then - local env = THREAD.unpackENV(GLOBAL["__env"]) + local env = THREAD.getENV() for i,v in pairs(env) do _G[i] = v end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index a4b16ef..1f6f082 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -59,17 +59,17 @@ function multi:newProxy(list) for k, v in pairs(obj) do res[copy(k)] = copy(v) end return res end - if not(c.is_init) then - c.is_init = true + if not(self.is_init) then + self.is_init = true local multi, thread = require("multi"):init() - c.proxy_link = "PL" .. multi.randomString(12) + self.proxy_link = "PL" .. multi.randomString(12) if multi.integration then GLOBAL = multi.integration.GLOBAL THREAD = multi.integration.THREAD end - GLOBAL[c.proxy_link] = c + GLOBAL[self.proxy_link] = self local function check() return self.send:pop() @@ -135,6 +135,7 @@ function multi:newProxy(list) self.recv = THREAD.waitFor(self.name.."_R"):init() self.Type = multi.PROXY for _,v in pairs(funcs) do + print(v,_) if type(v) == "table" then -- We have a connection v[2]:init(proc_name) @@ -280,13 +281,13 @@ function multi:newSystemThreadedProcessor(cores) end function c:newFunction(func, holdme) - return c.jobqueue:newFunction(func, holdme) + return self.jobqueue:newFunction(func, holdme) end function c:newSharedTable(name) if not name then multi.error("You must provide a name when creating a table!") end local tbl_name = "TABLE_"..multi.randomString(8) - c.jobqueue:doToAll(function(tbl_name, interaction) + self.jobqueue:doToAll(function(tbl_name, interaction) _G[interaction] = THREAD.waitFor(tbl_name):init() end, tbl_name, name) return multi:newSystemThreadedTable(tbl_name):init() diff --git a/lovethreads/main.lua b/lovethreads/main.lua index 199dc70..01b16c4 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -23,8 +23,13 @@ end) local jq = multi:newSystemThreadedJobQueue(n) +jq:registerFunction("test2",function() + print("This works!") +end) + jq:registerFunction("test",function(a, b, c) print(a, b+c) + test2() return a+b+c end) -- 2.43.0 From bb0592f3ebaf9e951db2eded80ad9610f1c146c0 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 30 Jul 2023 00:47:27 -0400 Subject: [PATCH 086/117] new pack/unpack for tables, current issue is things being turned into strings --- init.lua | 2 +- integration/loveManager/extensions.lua | 3 +- integration/loveManager/init.lua | 11 +- integration/loveManager/serpent.lua | 176 ------------------------- integration/loveManager/threads.lua | 42 +----- integration/loveManager/utils.lua | 55 ++++++++ integration/sharedExtensions/init.lua | 2 +- lovethreads/main.lua | 132 ++++++++++++------- tests/threadtests.lua | 9 +- 9 files changed, 162 insertions(+), 270 deletions(-) delete mode 100644 integration/loveManager/serpent.lua create mode 100644 integration/loveManager/utils.lua diff --git a/init.lua b/init.lua index 821e0f1..aa87d1f 100644 --- a/init.lua +++ b/init.lua @@ -1287,7 +1287,7 @@ function thread.hold(n, opt) elseif type(n) == "function" then return yield(CMD, t_hold, n, nil, interval) else - multi.error("Invalid argument passed to thread.hold(...)!") + multi.error("Invalid argument passed to thread.hold(...) ".. type(n) .. "!") end end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index c30adc9..163ccf6 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -11,7 +11,7 @@ function multi:newSystemThreadedQueue(name) c.Name = name c.Type = multi.SQUEUE - c.chan = love.thread.getChannel(name) + c.chan = love.thread.newChannel() function c:push(dat) self.chan:push(THREAD.packValue(dat)) @@ -26,7 +26,6 @@ function multi:newSystemThreadedQueue(name) end function c:init() - self.chan = love.thread.getChannel(self.Name) return self end diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 0bc9577..472125c 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -4,10 +4,11 @@ end local ThreadFileData = [[ ISTHREAD = true -__FUNC, THREAD_ID, THREAD_NAME, __PACK = ... +args = {...} +THREAD_ID = table.remove(args, 1) +THREAD_NAME = table.remove(args, 1) GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() -__FUNC = THREAD.unpackValue(__FUNC) -__PACK = THREAD.unpackValue(__PACK) +__FUNC = THREAD.unpackValue(table.remove(args, 1)) math.randomseed(THREAD_ID) math.random() math.random() @@ -22,7 +23,7 @@ end multi, thread = require("multi"):init() require("multi.integration.loveManager.extensions") require("multi.integration.sharedExtensions") -stab["returns"] = {__FUNC(multi.unpack(__PACK))} +stab["returns"] = {__FUNC(multi.unpack(args))} ]] _G.THREAD_NAME = "MAIN_THREAD" @@ -44,7 +45,7 @@ function multi:newSystemThread(name, func, ...) c.Name = name c.ID = tid c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(THREAD.packValue(func), c.ID, c.Name, THREAD.packValue({...})) + c.thread:start(c.ID, c.Name, THREAD.packValue(func), ...) c.stab = THREAD.createTable(name .. c.ID) c.creationTime = os.clock() c.OnDeath = multi:newConnection() diff --git a/integration/loveManager/serpent.lua b/integration/loveManager/serpent.lua deleted file mode 100644 index 5063f3a..0000000 --- a/integration/loveManager/serpent.lua +++ /dev/null @@ -1,176 +0,0 @@ ---[[ -Serpent source is released under the MIT License - -Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com) - -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, sublicense, 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. -]] - -local n, v = "serpent", "0.303" -- (C) 2012-18 Paul Kulchenko; MIT License -local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" -local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} -local badtype = {thread = true, userdata = true, cdata = true} -local getmetatable = debug and debug.getmetatable or getmetatable -local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ -local keyword, globals, G = {}, {}, (_G or _ENV) -for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', - 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', - 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end -for k,v in pairs(G) do globals[v] = k end -- build func to name mapping -for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do - for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end - -local function s(t, opts) - local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum - local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge - local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) - local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring - local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) - local numformat = opts.numformat or "%.17g" - local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 - local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", - -- tostring(val) is needed because __tostring may return a non-string value - function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end - local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or numformat:format(s)) - or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 - or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end - -- handle radix changes in some locales - if opts.fixradix and (".1f"):format(1.2) ~= "1.2" then - local origsafestr = safestr - safestr = function(s) return type(s) == "number" - and (nohuge and snum[tostring(s)] or numformat:format(s):gsub(",",".")) or origsafestr(s) - end - end - local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end - local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal - and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end - local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] - local n = name == nil and '' or name - local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] - local safe = plain and n or '['..safestr(n)..']' - return (path or '')..(plain and path and '.' or '')..safe, safe end - local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding - local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} - local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end - table.sort(k, function(a,b) - -- sort numeric keys first: k[key] is not nil for numerical keys - return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) - < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end - local function val2str(t, name, indent, insref, path, plainindex, level) - local ttype, level, mt = type(t), (level or 0), getmetatable(t) - local spath, sname = safename(path, name) - local tag = plainindex and - ((type(name) == "number") and '' or name..space..'='..space) or - (name ~= nil and sname..space..'='..space or '') - if seen[t] then -- already seen this element - sref[#sref+1] = spath..space..'='..space..seen[t] - return tag..'nil'..comment('ref', level) - end - -- protect from those cases where __tostring may fail - if type(mt) == 'table' and metatostring ~= false then - local to, tr = pcall(function() return mt.__tostring(t) end) - local so, sr = pcall(function() return mt.__serialize(t) end) - if (to or so) then -- knows how to serialize itself - seen[t] = insref or spath - t = so and sr or tr - ttype = type(t) - end -- new value falls through to be serialized - end - if ttype == "table" then - if level >= maxl then return tag..'{}'..comment('maxlvl', level) end - seen[t] = insref or spath - if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty - if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end - local maxn, o, out = math.min(#t, maxnum or #t), {}, {} - for key = 1, maxn do o[key] = key end - if not maxnum or #o < maxnum then - local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables - for key in pairs(t) do - if o[key] ~= key then n = n + 1; o[n] = key end - end - end - if maxnum and #o > maxnum then o[maxnum+1] = nil end - if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end - local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) - for n, key in ipairs(o) do - local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse - if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing - or opts.keyallow and not opts.keyallow[key] - or opts.keyignore and opts.keyignore[key] - or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types - or sparse and value == nil then -- skipping nils; do nothing - elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then - if not seen[key] and not globals[key] then - sref[#sref+1] = 'placeholder' - local sname = safename(iname, gensym(key)) -- iname is table for local variables - sref[#sref] = val2str(key,sname,indent,sname,iname,true) - end - sref[#sref+1] = 'placeholder' - local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']' - sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path)) - else - out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1) - if maxlen then - maxlen = maxlen - #out[#out] - if maxlen < 0 then break end - end - end - end - local prefix = string.rep(indent or '', level) - local head = indent and '{\n'..prefix..indent or '{' - local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) - local tail = indent and "\n"..prefix..'}' or '}' - return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level) - elseif badtype[ttype] then - seen[t] = insref or spath - return tag..globerr(t, level) - elseif ttype == 'function' then - seen[t] = insref or spath - if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end - local ok, res = pcall(string.dump, t) - local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level) - return tag..(func or globerr(t, level)) - else return tag..safestr(t) end -- handle all other types - end - local sepr = indent and "\n" or ";"..space - local body = val2str(t, name, indent) -- this call also populates sref - local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' - local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' - return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" -end - -local function deserialize(data, opts) - local env = (opts and opts.safe == false) and G - or setmetatable({}, { - __index = function(t,k) return t end, - __call = function(t,...) error("cannot call functions") end - }) - local f, res = (loadstring or load)('return '..data, nil, nil, env) - if not f then f, res = (loadstring or load)(data, nil, nil, env) end - if not f then return f, res end - if setfenv then setfenv(f, env) end - return pcall(f) -end - -local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end -return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, - load = deserialize, - dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, - line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, - block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } \ No newline at end of file diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 6610690..a0c6dc1 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,53 +25,19 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") -local serpent = require("multi.integration.loveManager.serpent") +local utils = require("multi.integration.loveManager.utils") local multi, thread = require("multi"):init() -local function ltype(data) - local tp = type(data) - if tp == "userdata" then - return data:type() - end - return tp -end - local NIL = love.data.newByteData("\3") -- If a non table/function is supplied we just return it local function packValue(t) - local tp = type(t) - if tp == "table" then - return love.data.newByteData("\1"..serpent.dump(t,{safe = true})) - elseif tp == "function" then - return love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) - else - return t - end + return utils.pack(t) end -- If a non table/function is supplied we just return it local function unpackValue(d) - if ltype(d) == "ByteData" then - local data = d:getString() - if data:sub(1, 1) == "\1" then - local status, data = serpent.load(data:sub(2,-1),{safe = false}) - if not status then - multi.error(data) - end - return data - elseif data:sub(1, 1) =="\2" then - local status, data = serpent.load(data:sub(2,-1),{safe = false}) - if not status then - multi.error(data) - end - return data[1] - else - return d - end - else - return d - end + return utils.unpack(d) end local function createTable(n) @@ -126,7 +92,7 @@ function INIT() end repeat wait() - until GLOBAL[name] + until GLOBAL[name] ~= nil if type(GLOBAL[name].init) == "function" then return GLOBAL[name]:init() else diff --git a/integration/loveManager/utils.lua b/integration/loveManager/utils.lua new file mode 100644 index 0000000..bdfec3d --- /dev/null +++ b/integration/loveManager/utils.lua @@ -0,0 +1,55 @@ +require("love.data") +local sutils = {} +local NIL = {Type="nil"} + +--love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) + +local ltype = function(v) return v:type() end +local t = function(value) + local v = type(value) + if v == "userdata" then + local status, return_or_err = pcall(ltype, value) + if status then return return_or_err else return "userdata" end + else return v end +end + +function sutils.pack(tbl, seen) + if type(tbl) == "function" then return {["__$FUNC$__"] = love.data.newByteData(string.dump(tbl))} end + if type(tbl) ~= "table" then return tbl end + local seen = seen or {} + local result = {} + for i,v in pairs(tbl) do + if seen[v] then + result[i] = v + elseif t(v) == "table" then + seen[v] = true + result[i] = sutils.pack(v, seen) + elseif t(v) == "function" then + result["$F"..i] = love.data.newByteData(string.dump(v)) + elseif t{v} == "userdata" then + result[i] = tostring(v) + else -- Handle what we need to and pass the rest along as a value + result[i] = v + end + end + return result +end + +function sutils.unpack(tbl) + if type(tbl) ~= "table" then return tbl end + if tbl["__$FUNC$__"] then return loadstring(tbl["__$FUNC$__"]:getString()) end + for i,v in pairs(tbl) do + if type(i) == "string" and i:sub(1,2) == "$F" then + local rawfunc = v:getString() + v:release() + tbl[i] = nil + tbl[i:sub(3,-1)] = loadstring(rawfunc) + end + if type(v) == "table" then + sutils.unpack(v) + end + end + return tbl +end + +return sutils \ No newline at end of file diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 1f6f082..9b29a83 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -135,7 +135,6 @@ function multi:newProxy(list) self.recv = THREAD.waitFor(self.name.."_R"):init() self.Type = multi.PROXY for _,v in pairs(funcs) do - print(v,_) if type(v) == "table" then -- We have a connection v[2]:init(proc_name) @@ -190,6 +189,7 @@ function multi:newProxy(list) THREAD = multi.integration.THREAD end local proxy = THREAD.waitFor(self.proxy_link) + print("Got:",proxy) proxy.funcs = self.funcs return proxy:init() end diff --git a/lovethreads/main.lua b/lovethreads/main.lua index 01b16c4..3b0434f 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -1,50 +1,94 @@ package.path = "../?/init.lua;../?.lua;"..package.path local multi, thread = require("multi"):init{print=true, warning = true, error=true} +local flat = require("flatten") -GLOBAL, THREAD = require("multi.integration.loveManager"):init() +local people = { + { + name = "Fred", + address = "16 Long Street", + phone = "123456" + }, + { + name = "Wilma", + address = "16 Long Street", + phone = "123456", + func = function() + print("Hi") + end + }, + { + name = "Barney", + address = "17 Long Street", + phone = "123457", + important = love.data.newByteData("TEST") + } + } -local queue = multi:newSystemThreadedQueue("TestQueue") -local tab = multi:newSystemThreadedTable("TestTable") - -local test = multi:newSystemThread("Test",function() - local queue = THREAD.waitFor("TestQueue") - local tab = THREAD.waitFor("TestTable") - print("THREAD_ID:",THREAD_ID) - queue:push("Did it work?") - tab["Test"] = true - return 1,2,3 -end) - -multi:newThread("QueueTest", function() - print(thread.hold(queue)) - print(thread.hold(tab, {key="Test"})) - print("Done!") -end) - -local jq = multi:newSystemThreadedJobQueue(n) - -jq:registerFunction("test2",function() - print("This works!") -end) - -jq:registerFunction("test",function(a, b, c) - print(a, b+c) - test2() - return a+b+c -end) - -print("Job:",jq:pushJob("test",1,2,3)) -print("Job:",jq:pushJob("test",2,3,4)) -print("Job:",jq:pushJob("test",5,6,7)) - -jq.OnJobCompleted(function(...) - print("Job Completed!", ...) -end) - -function love.draw() - -- +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end end -function love.update() - multi:uManager() -end \ No newline at end of file +local fpeople = flat.flatten(people) + +print("Flatten", dump(fpeople)) + +local people = flat.unflatten(fpeople) + +print("Unflatten", dump(people)) + +-- GLOBAL, THREAD = require("multi.integration.loveManager"):init() + +-- local queue = multi:newSystemThreadedQueue("TestQueue") +-- local tab = multi:newSystemThreadedTable("TestTable") + +-- local test = multi:newSystemThread("Test",function() +-- local queue = THREAD.waitFor("TestQueue") +-- local tab = THREAD.waitFor("TestTable") +-- print("THREAD_ID:",THREAD_ID) +-- queue:push("Did it work?") +-- tab["Test"] = true +-- return 1,2,3 +-- end) + +-- multi:newThread("QueueTest", function() +-- print(thread.hold(queue)) +-- print(thread.hold(tab, {key="Test"})) +-- print("Done!") +-- end) + +-- local jq = multi:newSystemThreadedJobQueue(n) + +-- jq:registerFunction("test2",function() +-- print("This works!") +-- end) + +-- jq:registerFunction("test",function(a, b, c) +-- print(a, b+c) +-- test2() +-- return a+b+c +-- end) + +-- print("Job:",jq:pushJob("test",1,2,3)) +-- print("Job:",jq:pushJob("test",2,3,4)) +-- print("Job:",jq:pushJob("test",5,6,7)) + +-- jq.OnJobCompleted(function(...) +-- print("Job Completed!", ...) +-- end) + +-- function love.draw() +-- -- +-- end + +-- function love.update() +-- multi:uManager() +-- end \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index f3d8fed..f6acf67 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -71,13 +71,13 @@ multi:newThread("Scheduler Thread",function() func = THREAD:newFunction(function(a,b,c) assert(a == 3, "First argument expected '3' got '".. a .."'!") - assert(b == 2, "Second argument expected '2' got '".. a .."'!") - assert(c == 1, "Third argument expected '1' got '".. a .."'!") + assert(b == 2, "Second argument expected '2' got '".. b .."'!") + assert(c == 1, "Third argument expected '1' got '".. c .."'!") return 1, 2, 3, {"a table"} end, true) -- Hold this a, b, c, d = func(3,2,1) - + print(a, b, c, d) assert(a == 1, "First return was not '1'!") assert(b == 2, "Second return was not '2'!") assert(c == 3, "Third return was not '3'!") @@ -194,6 +194,9 @@ multi:newThread("Scheduler Thread",function() multi:newSystemThread("Testing proxy copy THREAD",function(tloop) local multi, thread = require("multi"):init() + for i,v in pairs(tloop.funcs) do + print(i,v) + end tloop = tloop:init() multi.print("tloop type:",tloop.Type) multi.print("Testing proxies on other threads") -- 2.43.0 From 59cbeb65970f7f57ccb3ab6aadc09704e44d494b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 1 Aug 2023 23:45:06 -0400 Subject: [PATCH 087/117] Fixed packing of values into threads, need to fix system proxies and system threaded processors --- integration/loveManager/init.lua | 5 +++-- integration/loveManager/threads.lua | 3 +-- integration/loveManager/utils.lua | 17 ++++++++------ lovethreads/main.lua | 35 +++++++---------------------- tests/threadtests.lua | 7 ++++-- 5 files changed, 27 insertions(+), 40 deletions(-) diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 472125c..6a26e8d 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -9,6 +9,7 @@ THREAD_ID = table.remove(args, 1) THREAD_NAME = table.remove(args, 1) GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() __FUNC = THREAD.unpackValue(table.remove(args, 1)) +ARGS = THREAD.unpackValue(table.remove(args, 1)) math.randomseed(THREAD_ID) math.random() math.random() @@ -23,7 +24,7 @@ end multi, thread = require("multi"):init() require("multi.integration.loveManager.extensions") require("multi.integration.sharedExtensions") -stab["returns"] = {__FUNC(multi.unpack(args))} +stab["returns"] = {__FUNC(multi.unpack(ARGS))} ]] _G.THREAD_NAME = "MAIN_THREAD" @@ -45,7 +46,7 @@ function multi:newSystemThread(name, func, ...) c.Name = name c.ID = tid c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(c.ID, c.Name, THREAD.packValue(func), ...) + c.thread:start(c.ID, c.Name, THREAD.packValue(func), THREAD.packValue({...})) c.stab = THREAD.createTable(name .. c.ID) c.creationTime = os.clock() c.OnDeath = multi:newConnection() diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index a0c6dc1..83ec3da 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -52,8 +52,7 @@ local function createTable(n) chan:push(packValue(val)) end local function get(name) - local dat = love.thread.getChannel(n .. name):peek() - return unpackValue(dat) + return unpackValue(love.thread.getChannel(n .. name):peek()) end return setmetatable({}, { diff --git a/integration/loveManager/utils.lua b/integration/loveManager/utils.lua index bdfec3d..46a9a91 100644 --- a/integration/loveManager/utils.lua +++ b/integration/loveManager/utils.lua @@ -1,5 +1,5 @@ require("love.data") -local sutils = {} +local utils = {} local NIL = {Type="nil"} --love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) @@ -13,21 +13,22 @@ local t = function(value) else return v end end -function sutils.pack(tbl, seen) +function utils.pack(tbl, seen) if type(tbl) == "function" then return {["__$FUNC$__"] = love.data.newByteData(string.dump(tbl))} end if type(tbl) ~= "table" then return tbl end local seen = seen or {} local result = {} + result.__isPacked = true for i,v in pairs(tbl) do if seen[v] then result[i] = v elseif t(v) == "table" then seen[v] = true - result[i] = sutils.pack(v, seen) + result[i] = utils.pack(v, seen) elseif t(v) == "function" then result["$F"..i] = love.data.newByteData(string.dump(v)) elseif t{v} == "userdata" then - result[i] = tostring(v) + result[i] = "userdata" else -- Handle what we need to and pass the rest along as a value result[i] = v end @@ -35,7 +36,8 @@ function sutils.pack(tbl, seen) return result end -function sutils.unpack(tbl) +function utils.unpack(tbl) + if not tbl then return nil end if type(tbl) ~= "table" then return tbl end if tbl["__$FUNC$__"] then return loadstring(tbl["__$FUNC$__"]:getString()) end for i,v in pairs(tbl) do @@ -46,10 +48,11 @@ function sutils.unpack(tbl) tbl[i:sub(3,-1)] = loadstring(rawfunc) end if type(v) == "table" then - sutils.unpack(v) + utils.unpack(v) end end + tbl.__isPacked = nil return tbl end -return sutils \ No newline at end of file +return utils \ No newline at end of file diff --git a/lovethreads/main.lua b/lovethreads/main.lua index 3b0434f..8936ea3 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -1,35 +1,15 @@ package.path = "../?/init.lua;../?.lua;"..package.path local multi, thread = require("multi"):init{print=true, warning = true, error=true} -local flat = require("flatten") +local utils = require("multi.integration.loveManager.utils") -local people = { - { - name = "Fred", - address = "16 Long Street", - phone = "123456" - }, - { - name = "Wilma", - address = "16 Long Street", - phone = "123456", - func = function() - print("Hi") - end - }, - { - name = "Barney", - address = "17 Long Street", - phone = "123457", - important = love.data.newByteData("TEST") - } - } +local people = {1,2,3} function dump(o) if type(o) == 'table' then local s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end - s = s .. '['..k..'] = ' .. dump(v) .. ',' + s = s .. '['..k..'] = ' .. dump(v) .. '('..type(v):sub(1,1)..'),' end return s .. '} ' else @@ -37,13 +17,14 @@ function dump(o) end end -local fpeople = flat.flatten(people) +local fpeople = utils.pack(people) -print("Flatten", dump(fpeople)) +print("Pack:", dump(fpeople)) -local people = flat.unflatten(fpeople) +local people = utils.unpack(fpeople) -print("Unflatten", dump(people)) +print("Unpack:", dump(people)) +print(type(people[3])) -- GLOBAL, THREAD = require("multi.integration.loveManager"):init() diff --git a/tests/threadtests.lua b/tests/threadtests.lua index f6acf67..f646153 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -55,6 +55,9 @@ multi:newThread("Scheduler Thread",function() multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") multi_assert(true, e, "Argument e is not true!") multi_assert("table", type(f), "Argument f is not a table!") + for i,v in pairs(queue) do + print("Queue:",i,v) + end queue:push("done") end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) multi.error(err) @@ -90,14 +93,14 @@ multi:newThread("Scheduler Thread",function() local worked = false multi:newSystemThread("testing tables",function() - tab=THREAD.waitFor("YO"):init() + tab=THREAD.waitFor("YO") THREAD.hold(function() return tab["test1"] end) THREAD.sleep(.1) tab["test2"] = "Whats so funny?" end).OnError(multi.error) multi:newThread("test2",function() - thread.hold(function() return test["test2"] end) + print(thread.hold(function() return test["test2"] end)) worked = true end) -- 2.43.0 From 5359be0772ddb0aef91f7a24196e864700fb352c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 3 Aug 2023 23:11:32 -0400 Subject: [PATCH 088/117] testing... --- integration/sharedExtensions/init.lua | 8 +++++ tests/threadtests.lua | 46 +++++++++++++-------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 9b29a83..e7cb8c7 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -52,6 +52,9 @@ function multi:newProxy(list) c.is_init = false local multi, thread = nil, nil function c:init() + for i,v in pairs(self) do + print("Pattern",i,v) + end local multi, thread = nil, nil local function copy(obj) if type(obj) ~= 'table' then return obj end @@ -169,6 +172,7 @@ function multi:newProxy(list) return self end end + function c:getTransferable() local cp = {} local multi, thread = require("multi"):init() @@ -193,8 +197,12 @@ function multi:newProxy(list) proxy.funcs = self.funcs return proxy:init() end + for i,v in pairs(cp) do + print("TRANS", i, v) + end return cp end + self:create(c) return c end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index f646153..315cfb7 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -195,25 +195,25 @@ multi:newThread("Scheduler Thread",function() local tloop = stp:newTLoop(nil, 1) - multi:newSystemThread("Testing proxy copy THREAD",function(tloop) - local multi, thread = require("multi"):init() - for i,v in pairs(tloop.funcs) do - print(i,v) - end - tloop = tloop:init() - multi.print("tloop type:",tloop.Type) - multi.print("Testing proxies on other threads") - thread:newThread(function() - while true do - thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") - end - end) - tloop.OnLoop(function(a) - print(THREAD_NAME, "Got loop...") - end) - multi:mainloop() - end, tloop:getTransferable()).OnError(multi.error) + -- multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + -- local multi, thread = require("multi"):init() + -- for i,v in pairs(tloop.funcs) do + -- print(i,v) + -- end + -- tloop = tloop:init() + -- multi.print("tloop type:",tloop.Type) + -- multi.print("Testing proxies on other threads") + -- thread:newThread(function() + -- while true do + -- thread.hold(tloop.OnLoop) + -- print(THREAD_NAME,"Loopy") + -- end + -- end) + -- tloop.OnLoop(function(a) + -- print(THREAD_NAME, "Got loop...") + -- end) + -- multi:mainloop() + -- end, tloop:getTransferable()).OnError(multi.error) multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) @@ -225,8 +225,8 @@ multi:newThread("Scheduler Thread",function() thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... twice") proxy_test = true - end).OnError(print) - print(6) + end).OnError(multi.error) + thread:newThread(function() while true do @@ -240,8 +240,8 @@ multi:newThread("Scheduler Thread",function() end) t, val = thread.hold(function() - return count == 10 - end,{sleep=5}) + return proxy_test + end,{sleep=15}) if val == multi.TIMEOUT then multi.error("SystemThreadedProcessor/Proxies: Failed") -- 2.43.0 From d50c1877105d07d9576f922801f5b772c3b4a522 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 3 Aug 2023 23:22:01 -0400 Subject: [PATCH 089/117] Not hard crashing when error is encountered --- init.lua | 2 +- integration/sharedExtensions/init.lua | 8 ----- tests/threadtests.lua | 45 +++++++++++++-------------- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/init.lua b/init.lua index aa87d1f..7f2b811 100644 --- a/init.lua +++ b/init.lua @@ -2392,8 +2392,8 @@ function multi.error(self, err) end if multi.defaultSettings.error then error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") - os.exit(1) end + os.exit(1) end function multi.success(...) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index e7cb8c7..9b29a83 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -52,9 +52,6 @@ function multi:newProxy(list) c.is_init = false local multi, thread = nil, nil function c:init() - for i,v in pairs(self) do - print("Pattern",i,v) - end local multi, thread = nil, nil local function copy(obj) if type(obj) ~= 'table' then return obj end @@ -172,7 +169,6 @@ function multi:newProxy(list) return self end end - function c:getTransferable() local cp = {} local multi, thread = require("multi"):init() @@ -197,12 +193,8 @@ function multi:newProxy(list) proxy.funcs = self.funcs return proxy:init() end - for i,v in pairs(cp) do - print("TRANS", i, v) - end return cp end - self:create(c) return c end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 315cfb7..47fc9d3 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -55,9 +55,6 @@ multi:newThread("Scheduler Thread",function() multi_assert("Passing some args", a, "First argument is not as expected 'Passing some args'") multi_assert(true, e, "Argument e is not true!") multi_assert("table", type(f), "Argument f is not a table!") - for i,v in pairs(queue) do - print("Queue:",i,v) - end queue:push("done") end,"Passing some args", 1, 2, 3, true, {"Table"}).OnError(function(self,err) multi.error(err) @@ -195,25 +192,25 @@ multi:newThread("Scheduler Thread",function() local tloop = stp:newTLoop(nil, 1) - -- multi:newSystemThread("Testing proxy copy THREAD",function(tloop) - -- local multi, thread = require("multi"):init() - -- for i,v in pairs(tloop.funcs) do - -- print(i,v) - -- end - -- tloop = tloop:init() - -- multi.print("tloop type:",tloop.Type) - -- multi.print("Testing proxies on other threads") - -- thread:newThread(function() - -- while true do - -- thread.hold(tloop.OnLoop) - -- print(THREAD_NAME,"Loopy") - -- end - -- end) - -- tloop.OnLoop(function(a) - -- print(THREAD_NAME, "Got loop...") - -- end) - -- multi:mainloop() - -- end, tloop:getTransferable()).OnError(multi.error) + multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + local multi, thread = require("multi"):init() + for i,v in pairs(tloop.funcs) do + print(i,v) + end + tloop = tloop:init() + multi.print("tloop type:",tloop.Type) + multi.print("Testing proxies on other threads") + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") + end + end) + tloop.OnLoop(function(a) + print(THREAD_NAME, "Got loop...") + end) + multi:mainloop() + end, tloop:getTransferable()).OnError(multi.error) multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) @@ -240,8 +237,8 @@ multi:newThread("Scheduler Thread",function() end) t, val = thread.hold(function() - return proxy_test - end,{sleep=15}) + return count == 10 + end,{sleep=5}) if val == multi.TIMEOUT then multi.error("SystemThreadedProcessor/Proxies: Failed") -- 2.43.0 From 8c2bde7ed8648974321ede2270fec70274e0cf2b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 3 Aug 2023 23:33:16 -0400 Subject: [PATCH 090/117] Should now push non 0 exit codes --- init.lua | 2 +- tests/runtests.lua | 11 ++++++++--- tests/threadtests.lua | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 7f2b811..aa7bb41 100644 --- a/init.lua +++ b/init.lua @@ -2445,7 +2445,7 @@ end multi.OnError=multi:newConnection() multi.OnPreLoad = multi:newConnection() multi.OnExit = multi:newConnection(nil,nil,true) -multi.m = {onexit = function() multi.OnExit:Fire() end} +multi.m = {onexit = function() os.exit() end} if _VERSION >= "Lua 5.2" or jit then setmetatable(multi.m, {__gc = multi.m.onexit}) diff --git a/tests/runtests.lua b/tests/runtests.lua index 988c978..c3d7df7 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -174,11 +174,16 @@ runTest = thread:newFunction(function() multi.error("Connection Test 3: Error removing connection") end if not love then + local ec = 0 multi.print("Testing pseudo threading") - os.execute("lua tests/threadtests.lua p") + ec = ec + os.execute("lua tests/threadtests.lua p") multi.print("Testing lanes threading") - os.execute("lua tests/threadtests.lua l") - os.exit() + ec = ec + os.execute("lua tests/threadtests.lua l") + if ec > 0 then + os.exit(1) + else + os.exit() + end end end) diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 47fc9d3..1f871c6 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -255,6 +255,7 @@ multi:newThread("Scheduler Thread",function() end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) + print("Error Code: ", err_or_errorcode) if not we_good then multi.info("There was an error running some tests!") return -- 2.43.0 From bab9b13cb8eed6c8c0285d6d4bbeb27489afc2a3 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 3 Aug 2023 23:40:43 -0400 Subject: [PATCH 091/117] Push error when an error happens --- error.lua | 1 - init.lua | 3 ++- integration/pseudoManager/init.lua | 2 -- tests/runtests.lua | 4 +--- tests/threadtests.lua | 1 - 5 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 error.lua diff --git a/error.lua b/error.lua deleted file mode 100644 index 84c9b9f..0000000 --- a/error.lua +++ /dev/null @@ -1 +0,0 @@ -os.exit(2) \ No newline at end of file diff --git a/init.lua b/init.lua index aa7bb41..3d713e8 100644 --- a/init.lua +++ b/init.lua @@ -1789,6 +1789,7 @@ co_status = { 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) + multi.error(ref, ret) end if i then table.remove(th,i) @@ -2392,8 +2393,8 @@ function multi.error(self, err) end if multi.defaultSettings.error then error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") + os.exit(1) end - os.exit(1) end function multi.success(...) diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 490cff9..91e0824 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -59,8 +59,6 @@ tab = split(tab) local id = 0 -print("Outerglobal",_G) - function multi:newSystemThread(name, func, ...) local env env = { diff --git a/tests/runtests.lua b/tests/runtests.lua index c3d7df7..77d0c02 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -179,10 +179,8 @@ runTest = thread:newFunction(function() ec = ec + os.execute("lua tests/threadtests.lua p") multi.print("Testing lanes threading") ec = ec + os.execute("lua tests/threadtests.lua l") - if ec > 0 then + if ec ~= 0 then os.exit(1) - else - os.exit() end end end) diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 1f871c6..269b671 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -251,7 +251,6 @@ multi:newThread("Scheduler Thread",function() we_good = true multi:Stop() -- Needed in love2d tests to stop the main runner - os.exit() end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) -- 2.43.0 From a660b63581bac987765ea13b2adf92a97b5aad58 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 6 Aug 2023 00:38:22 -0400 Subject: [PATCH 092/117] Closer to getting things working... --- docs/changes.md | 7 +- init.lua | 6 +- integration/loveManager/extensions.lua | 2 + integration/loveManager/threads.lua | 4 +- integration/loveManagerold/threads.lua | 1 - integration/lovrManager/init.lua | 4 +- integration/pseudoManager/extensions.lua | 186 ++++++++++++++++------- integration/sharedExtensions/init.lua | 4 +- tests/threadtests.lua | 12 +- 9 files changed, 147 insertions(+), 79 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index e2c9f88..91ef5c8 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -62,17 +62,12 @@ Table of contents Full Update Showcase --- + ```lua multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() ``` -## Added New Integration: **effilManager** - -Another option for multithreading support, works just like all the other threading integrations, but uses the internals of effil and it's unique features. -- Refer to this [doc](https://www.lua.org/wshop18/Kupriyanov.pdf) to read more about it. -- Project github [page](https://github.com/effil/effil/tree/master). - ```lua package.path = "?/init.lua;?.lua;"..package.path diff --git a/init.lua b/init.lua index 3d713e8..5392cda 100644 --- a/init.lua +++ b/init.lua @@ -482,7 +482,7 @@ function multi:newConnection(protect,func,kill) return temp end - function c:Hold(self) + function c:Hold() return multi.hold(self) end @@ -1276,7 +1276,6 @@ function thread.hold(n, opt) 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) @@ -2392,7 +2391,8 @@ function multi.error(self, err) io.write("\x1b[91mERROR:\x1b[0m " .. err .. " ?\n") end if multi.defaultSettings.error then - error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. debug.traceback().."\n") + error("^^^ " .. multi:getCurrentProcess():getFullName() .. " " .. multi:getCurrentTask().Type .. "\n" .. + ((coroutine.running()) and debug.traceback((coroutine.running())) or debug.traceback()) .. "\n") os.exit(1) end end diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 163ccf6..358cf6e 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -70,6 +70,8 @@ function multi:newSystemThreadedTable(name) return self end + c.__init = c.init + function c:Hold(opt) if opt.key then return thread.hold(function() diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 83ec3da..d3cec2f 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -92,8 +92,8 @@ function INIT() repeat wait() until GLOBAL[name] ~= nil - if type(GLOBAL[name].init) == "function" then - return GLOBAL[name]:init() + if type(GLOBAL[name].__init) == "function" then + return GLOBAL[name]:__init() else return GLOBAL[name] end diff --git a/integration/loveManagerold/threads.lua b/integration/loveManagerold/threads.lua index 6a2f25a..7749c98 100644 --- a/integration/loveManagerold/threads.lua +++ b/integration/loveManagerold/threads.lua @@ -48,7 +48,6 @@ end local fRef = {"func",nil} local function manage(channel, value) channel:clear() - print("pushing",value) if type(value) == "table" then channel:push{"DATA",threads.packTable(value)} else diff --git a/integration/lovrManager/init.lua b/integration/lovrManager/init.lua index bb86a8a..3d6943d 100644 --- a/integration/lovrManager/init.lua +++ b/integration/lovrManager/init.lua @@ -87,12 +87,12 @@ function multi:newSystemThread(name,func,...) end THREAD.newSystemThread = multi.newSystemThread function lovr.threaderror(thread, errorstr) - print("Thread error!\n"..errorstr) + multi.print("Thread error!\n"..errorstr) end multi.integration.GLOBAL = GLOBAL multi.integration.THREAD = THREAD require("multi.integration.lovrManager.extensions") -print("Integrated lovr Threading!") +multi.print("Integrated lovr Threading!") return {init=function() return GLOBAL,THREAD end} \ No newline at end of file diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index b6a4884..03550b6 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -47,6 +47,17 @@ function multi:newSystemThreadedQueue(name) function c:init() return self end + function c:Hold(opt) + if opt.peek then + return thread.hold(function() + return self:peek() + end) + else + return thread.hold(function() + return self:pop() + end) + end + end GLOBAL[name or "_"] = c return c end @@ -56,58 +67,59 @@ function multi:newSystemThreadedTable(name) function c:init() return self end + function c:Hold(opt) + if opt.key then + return thread.hold(function() + return self.tab[opt.key] + end) + else + multi.error("Must provide a key to check opt.key = 'key'") + end + end GLOBAL[name or "_"] = c return c end -local setfenv = setfenv -if not setfenv then - if not debug then - multi.print("Unable to implement setfenv in lua 5.2+ the debug module is not available!") - else - setfenv = function(f, env) - return load(string.dump(f), nil, nil, env) - end - end -end +local setfenv = multi.isolateFunction +local jqc = 1 function multi:newSystemThreadedJobQueue(n) - local c = {} - c.cores = n or THREAD.getCores()*2 - c.OnJobCompleted = multi:newConnection() - local jobs = {} - local ID=1 - local jid = 1 - local env = {} + local c = {} - setmetatable(env,{ - __index = _G - }) + c.cores = n or THREAD.getCores() + c.registerQueue = {} + c.Type = multi.SJOBQUEUE + c.funcs = multi:newSystemThreadedTable("__JobQueue_"..jqc.."_table") + c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue") + c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn") + c.queueAll = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueAll") + c.id = 0 + c.OnJobCompleted = multi:newConnection() - local funcs = {} - function c:doToAll(func) - setfenv(func,env)() - return self + local allfunc = 0 + + function c:doToAll(func) + for i = 1, self.cores do + self.queueAll:push({allfunc, func}) + end + allfunc = allfunc + 1 + end + function c:registerFunction(name, func) + if self.funcs[name] then + multi.error("A function by the name "..name.." has already been registered!") + end + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + function c:isEmpty() + return queueJob:peek()==nil end - - function c:registerFunction(name,func) - funcs[name] = setfenv(func,env) - return self - end - - function c:pushJob(name,...) - table.insert(jobs,{name,jid,multi.pack(...)}) - jid = jid + 1 - return jid-1 - end - - function c:isEmpty() - return #jobs == 0 - end - - local nFunc = 0 + local nFunc = 0 function c:newFunction(name,func,holup) -- This registers with the queue - local func = stripUpValues(func) if type(name)=="function" then holup = func func = name @@ -129,22 +141,84 @@ function multi:newSystemThreadedJobQueue(n) return multi.unpack(rets) or multi.NIL end end) - end, holup), name + end,holup),name end - for i=1,c.cores do - thread:newThread("PesudoThreadedJobQueue_"..i,function() - while true do - thread.yield() - if #jobs>0 then - local j = table.remove(jobs,1) - c.OnJobCompleted:Fire(j[2],funcs[j[1]](multi.unpack(j[3]))) - else - thread.sleep(.05) - end - end - end).OnError(multi.error) + thread:newThread("jobManager",function() + while true do + thread.yield() + local dat = c.queueReturn:pop() + if dat then + c.OnJobCompleted:Fire(multi.unpack(dat)) + end + end + end) + for i=1,c.cores do + multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + local multi, thread = require("multi"):init() + local clock = os.clock + local funcs = THREAD.waitFor("__JobQueue_"..jqc.."_table") + local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue") + local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn") + local lastProc = clock() + local queueAll = THREAD.waitFor("__JobQueue_"..jqc.."_queueAll") + local registry = {} + _G["__QR"] = queueReturn + setmetatable(_G,{__index = funcs}) + thread:newThread("startUp",function() + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + queueAll:pop()[2]() + end + end + end) + thread:newThread("runner",function() + thread.sleep(.1) + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + queueAll:pop()[2]() + end + local dat = thread.hold(queue) + if dat then + multi:newThread("Test",function() + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {multi.isolateFunction(funcs[name],_G)(multi.unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end) + end + end + end).OnError(multi.error) + thread:newThread("Idler",function() + while true do + thread.yield() + if clock()-lastProc> 2 then + THREAD.sleep(.05) + else + THREAD.sleep(.001) + end + end + end) + multi:mainloop() + end,jqc) + end + + function c:Hold(opt) + return thread.hold(self.OnJobCompleted) end - return c + + jqc = jqc + 1 + + self:create(c) + + return c end function multi:newSystemThreadedConnection(name) diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 9b29a83..c916b0f 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -189,7 +189,6 @@ function multi:newProxy(list) THREAD = multi.integration.THREAD end local proxy = THREAD.waitFor(self.proxy_link) - print("Got:",proxy) proxy.funcs = self.funcs return proxy:init() end @@ -267,7 +266,8 @@ function multi:newSystemThreadedProcessor(cores) for _, method in pairs(implement) do c[method] = function(self, ...) - proxy = self.spawnTask(method, ...):init() + proxy = self.spawnTask(method, ...) + proxy:init() references[proxy] = self return proxy end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 269b671..d2bb646 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -77,7 +77,6 @@ multi:newThread("Scheduler Thread",function() end, true) -- Hold this a, b, c, d = func(3,2,1) - print(a, b, c, d) assert(a == 1, "First return was not '1'!") assert(b == 2, "Second return was not '2'!") assert(c == 3, "Third return was not '3'!") @@ -115,8 +114,11 @@ multi:newThread("Scheduler Thread",function() local ready = false jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads - func = jq:newFunction("test-thread",function(a,b) + func2 = jq:newFunction("sleep",function(a,b) THREAD.sleep(.2) + end) + func = jq:newFunction("test-thread",function(a,b) + sleep() return a+b end) local count = 0 @@ -194,9 +196,6 @@ multi:newThread("Scheduler Thread",function() multi:newSystemThread("Testing proxy copy THREAD",function(tloop) local multi, thread = require("multi"):init() - for i,v in pairs(tloop.funcs) do - print(i,v) - end tloop = tloop:init() multi.print("tloop type:",tloop.Type) multi.print("Testing proxies on other threads") @@ -223,7 +222,6 @@ multi:newThread("Scheduler Thread",function() multi.print("Held on proxy connection... twice") proxy_test = true end).OnError(multi.error) - thread:newThread(function() while true do @@ -256,7 +254,7 @@ end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) print("Error Code: ", err_or_errorcode) if not we_good then - multi.info("There was an error running some tests!") + multi.print("There was an error running some tests!") return else multi.success("Tests complete!") -- 2.43.0 From 5f5723e9361991d2e1384b1096948b498eb1a503 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 4 Sep 2023 12:03:00 -0400 Subject: [PATCH 093/117] Working on new type system, planning out debugmanager --- init.lua | 107 +++++++++++------------ integration/debugManager/init.lua | 8 +- integration/lanesManager/extensions.lua | 8 +- integration/lanesManager/init.lua | 4 +- integration/loveManager/extensions.lua | 6 +- integration/loveManager/init.lua | 3 + integration/priorityManager/init.lua | 4 +- integration/pseudoManager/extensions.lua | 4 +- integration/pseudoManager/init.lua | 3 +- integration/sharedExtensions/init.lua | 24 ++--- tests/threadtests.lua | 14 +-- 11 files changed, 98 insertions(+), 87 deletions(-) diff --git a/init.lua b/init.lua index 5392cda..eebca03 100644 --- a/init.lua +++ b/init.lua @@ -68,38 +68,24 @@ setmetatable(multi.DestroyedObj, { }) multi.DESTROYED = multi.DestroyedObj -multi.ROOTPROCESS = "rootprocess" -multi.CONNECTOR = "connector" -- To be deprecated -multi.CONNECTION = "connector" -- To be changed to connection and replace connector (v17.x,x) -multi.TIMEMASTER = "timemaster" -multi.PROCESS = "process" -multi.TIMER = "timer" -multi.EVENT = "event" -multi.UPDATER = "updater" -multi.ALARM = "alarm" -multi.LOOP = "loop" -multi.TLOOP = "tloop" -multi.STEP = "step" -multi.TSTEP = "tstep" -multi.THREAD = "thread" -multi.SERVICE = "service" -multi.THREADEDFUNCTION = "threaded_function" -- To be deprecated -multi.FUNCTION = "threaded_function" -- To be changed to connection and replace connector (v17.x,x) - --- Extensions -multi.PROXY = "proxy" -multi.STHREAD = "s_thread" -multi.SQUEUE = "s_queue" -multi.STABLE = "s_table" -multi.SJOBQUEUE = "s_jobqueue" -multi.SCONNECTION = "s_connection" -multi.SPROCESS = "s_process" -multi.SFUNCTION = "s_function" +-- I don't like modifying the global namespace, so I prepend a "$" if not _G["$multi"] then _G["$multi"] = {multi = multi, thread = thread} end +local types = {} +function multi.registerType(typ, p) + if multi[typ:upper():gsub("_","")] then return typ end + multi[typ:upper():gsub("_","")] = typ + table.insert(types, {typ, p or typ}) + return typ +end + +function multi.getTypes() + return types +end + multi.Version = "16.0.0" multi.Name = "root" multi.NIL = {Type="NIL"} @@ -107,7 +93,7 @@ local NIL = multi.NIL multi.Mainloop = {} multi.Children = {} multi.Active = true -multi.Type = multi.ROOTPROCESS +multi.Type = multi.registerType("rootprocess") multi.LinkedPath = multi multi.TIMEOUT = "TIMEOUT" multi.TID = 0 @@ -317,7 +303,7 @@ function multi:newConnection(protect,func,kill) return cn end}) - c.Type=multi.CONNECTION + c.Type=multi.registerType("connector", "connections") c.func={} c.ID=0 local protect=protect or false @@ -483,7 +469,8 @@ function multi:newConnection(protect,func,kill) end function c:Hold() - return multi.hold(self) + local rets = {multi.hold(self)} + return unpack(rets) end c.connect=c.Connect @@ -538,7 +525,7 @@ end function multi:SetTime(n) if not n then n=3 end local c=self:newBase() - c.Type=multi.TIMEMASTER + c.Type=multi.registerType("timemaster") c.timer=self:newTimer() c.timer:Start() c.set=n @@ -567,7 +554,7 @@ end -- Timer stuff done multi.PausedObjects = {} function multi:Pause() - if self.Type==multi.ROOTPROCESS then + if self.Type==multi.registerType("rootprocess") then multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") else self.Active=false @@ -578,7 +565,7 @@ function multi:Pause() end function multi:Resume() - if self.Type==multi.PROCESS or self.Type==multi.ROOTPROCESS then + if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then self.Active=true local c=self:getChildren() for i=1,#c do @@ -594,7 +581,7 @@ function multi:Resume() end function multi:Destroy() - if self.Type==multi.PROCESS or self.Type==multi.ROOTPROCESS then + if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then local c=self:getChildren() for i=1,#c do self.OnObjectDestroyed:Fire(c[i]) @@ -648,9 +635,9 @@ end --Constructors [CORE] local _tid = 0 function multi:newBase(ins) - if not(self.Type==multi.ROOTPROCESS or self.Type==multi.PROCESS) then multi.error('Can only create an object on multi or an interface obj') return false end + if not(self.Type==multi.registerType("rootprocess") or self.Type==multi.registerType("process", "processes")) then multi.error('Can only create an object on multi or an interface obj') return false end local c = {} - if self.Type==multi.PROCESS then + if self.Type==multi.registerType("process", "processes") then setmetatable(c, {__index = multi}) else setmetatable(c, {__index = multi}) @@ -687,7 +674,7 @@ end function multi:newTimer() local c={} - c.Type=multi.TIMER + c.Type=multi.registerType("timer", "timers") local time=0 local count=0 local paused=false @@ -720,7 +707,7 @@ end --Core Actors function multi:newEvent(task, func) local c=self:newBase() - c.Type=multi.EVENT + c.Type=multi.registerType("event", "events") local task = task or function() end function c:Act() local t = task(self) @@ -747,7 +734,7 @@ end function multi:newUpdater(skip, func) local c=self:newBase() - c.Type=multi.UPDATER + c.Type=multi.registerType("updater", "updaters") local pos = 1 local skip = skip or 1 function c:Act() @@ -773,7 +760,7 @@ end function multi:newAlarm(set, func) local c=self:newBase() - c.Type=multi.ALARM + c.Type=multi.registerType("alarm", "alarms") c:setPriority("Low") c.set=set or 0 local count = 0 @@ -814,7 +801,7 @@ end function multi:newLoop(func, notime) local c=self:newBase() - c.Type=multi.LOOP + c.Type = multi.registerType("loop", "loops") local start=clock() if notime then function c:Act() @@ -842,7 +829,7 @@ end function multi:newStep(start,reset,count,skip) local c=self:newBase() think=1 - c.Type=multi.STEP + c.Type=multi.registerType("step", "steps") c.pos=start or 1 c.endAt=reset or math.huge c.skip=skip or 0 @@ -901,7 +888,7 @@ end function multi:newTLoop(func, set) local c=self:newBase() - c.Type=multi.TLOOP + c.Type=multi.registerType("tloop", "tloops") c.set=set or 0 c.timer=self:newTimer() c.life=0 @@ -951,7 +938,7 @@ end function multi:newTStep(start,reset,count,set) local c=self:newStep(start,reset,count) - c.Type=multi.TSTEP + c.Type=multi.registerType("tstep", "tsteps") c:setPriority("Low") local reset = reset or math.huge c.timer=clock() @@ -1068,7 +1055,7 @@ function multi:newProcessor(name, nothread, priority) local name = name or "Processor_" .. sandcount sandcount = sandcount + 1 c.Mainloop = {} - c.Type = multi.PROCESS + c.Type = multi.registerType("process", "processes") local Active = nothread or false c.Name = name or "" c.threads = {} @@ -1279,7 +1266,7 @@ function thread.hold(n, opt) 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 == multi.CONNECTION then + elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then return yield(CMD, t_hold, conn_test(n), nil, interval) elseif type(n) == "table" and n.Hold ~= nil then return n:Hold(opt) @@ -1442,7 +1429,7 @@ function thread:newFunctionBase(generator, holdme, TYPE) return temp end setmetatable(tfunc, tfunc) - tfunc.Type = TYPE or multi.FUNCTION + tfunc.Type = TYPE or multi.registerType("function", "functions") return tfunc end end @@ -1543,14 +1530,14 @@ function thread:newThread(name, func, ...) c.Name=name c.thread=create(func) c.sleep=1 - c.Type = multi.THREAD + c.Type = multi.registerType("thread", "threads") c.TID = threadid c.firstRunDone=false c._isPaused = false c.returns = {} c.isError = false - if self.Type == multi.PROCESS then + if self.Type == multi.registerType("process", "processes") then c.OnError = self:newConnection(true,nil,true) c.OnDeath = self:newConnection(true,nil,true) else @@ -1611,13 +1598,13 @@ function thread:newThread(name, func, ...) c.Destroy = c.Kill if thread.isThread() then - if self.Type == multi.PROCESS then + if self.Type == multi.registerType("process", "processes") then table.insert(self.startme, c) else table.insert(threadManager.startme, c) end else - if self.Type == multi.PROCESS then + if self.Type == multi.registerType("process", "processes") then table.insert(self.startme, c) else table.insert(threadManager.startme, c) @@ -1626,7 +1613,7 @@ function thread:newThread(name, func, ...) globalThreads[c] = multi threadid = threadid + 1 - if self.Type == multi.PROCESS then + if self.Type == multi.registerType("process", "processes") then self:create(c) else threadManager:create(c) @@ -1859,7 +1846,7 @@ end function multi:newService(func) -- Priority managed threads local c = {} - c.Type = multi.SERVICE + c.Type = multi.registerType("service", "services") c.OnStopped = self:newConnection() c.OnStarted = self:newConnection() local Service_Data = {} @@ -2034,7 +2021,7 @@ local function doOpt() 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 == multi.CONNECTION then + elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then local rdy = function() return false end @@ -2071,22 +2058,32 @@ local function doOpt() end local init = false +multi.settingsHook = multi:newConnection() 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 = multi.p_mainloop else multi.mainloop = multi.mainloopRef end + if settings.findopt then find_optimization = true doOpt() multi.enableOptimization:Fire(multi, thread) end + + if settings.debugging then + require("multi.integration.debugManager") + end + + multi.settingsHook:Fire(settings) end return _G["$multi"].multi,_G["$multi"].thread end @@ -2194,7 +2191,7 @@ function multi:getLoad() end function multi:setPriority(s) - if not self.IsAnActor or self.Type == multi.PROCESS then return end + if not self.IsAnActor or self.Type == multi.registerType("process", "processes") then return end if type(s)=="number" then self.Priority=s elseif type(s)=='string' then diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua index eae78be..d397d37 100644 --- a/integration/debugManager/init.lua +++ b/integration/debugManager/init.lua @@ -1,5 +1,7 @@ local multi, thread = require("multi"):init() +multi.defaultSettings.debugging = true + local dbg = {} local creation_hook @@ -11,6 +13,8 @@ creation_hook = function(obj, process) end end +local debug_stats = {} + local tmulti = multi:getThreadManagerProcess() multi.OnObjectCreated(creation_hook) tmulti.OnObjectCreated(creation_hook) @@ -32,6 +36,4 @@ tmulti.OnObjectCreated(creation_hook) multi.SERVICE = "service" multi.PROXY = "proxy" multi.THREADEDFUNCTION = "threaded_function" -]] - -return dbg \ No newline at end of file +]] \ No newline at end of file diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 37526c7..4ff3605 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -34,7 +34,7 @@ function multi:newSystemThreadedQueue(name) local c = {} c.Name = name c.linda = lanes.linda() - c.Type = multi.SQUEUE + c.Type = multi.registerType("s_queue") function c:push(v) self.linda:send("Q", v) @@ -81,7 +81,7 @@ function multi:newSystemThreadedTable(name) local c = {} c.link = lanes.linda() c.Name = name - c.Type = multi.STABLE + c.Type = multi.registerType("s_table") function c:init() return self @@ -121,7 +121,7 @@ end function multi:newSystemThreadedJobQueue(n) local c = {} c.cores = n or THREAD.getCores()*2 - c.Type = multi.SJOBQUEUE + c.Type = multi.registerType("s_jobqueue") c.OnJobCompleted = multi:newConnection() local funcs = multi:newSystemThreadedTable() local queueJob = multi:newSystemThreadedQueue() @@ -250,7 +250,7 @@ end function multi:newSystemThreadedConnection(name) local name = name or multi.randomString(16) local c = {} - c.Type = multi.SCONNECTION + c.Type = multi.registerType("s_connection") c.CONN = 0x00 c.TRIG = 0x01 c.PING = 0x02 diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index dbf7eb3..2f3e646 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -61,7 +61,7 @@ local livingThreads = {} function THREAD:newFunction(func, holdme) return thread:newFunctionBase(function(...) return multi:newSystemThread("TempSystemThread",func,...) - end, holdme, multi.SFUNCTION)() + end, holdme, multi.registerType("s_function"))() end function multi:newSystemThread(name, func, ...) @@ -75,7 +75,7 @@ function multi:newSystemThread(name, func, ...) c.loadString = {"base","package","os","io","math","table","string","coroutine"} livingThreads[count] = {true, name} c.returns = return_linda - c.Type = multi.STHREAD + c.Type = multi.registerType("s_thread") c.creationTime = os.clock() c.alive = true c.priority = THREAD.Priority_Normal diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 358cf6e..f1a7f27 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -10,7 +10,7 @@ function multi:newSystemThreadedQueue(name) local c = {} c.Name = name - c.Type = multi.SQUEUE + c.Type = multi.registerType("s_queue") c.chan = love.thread.newChannel() function c:push(dat) @@ -54,7 +54,7 @@ function multi:newSystemThreadedTable(name) local c = {} c.Name = name - c.Type = multi.STABLE + c.Type = multi.registerType("s_table") c.tab = THREAD.createTable(name) function c:init() @@ -104,7 +104,7 @@ function multi:newSystemThreadedJobQueue(n) c.cores = n or THREAD.getCores() c.registerQueue = {} - c.Type = multi.SJOBQUEUE + c.Type = multi.registerType("s_jobqueue") c.funcs = THREAD.createTable("__JobQueue_"..jqc.."_table") c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue") c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn") diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 6a26e8d..92cba27 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -33,6 +33,9 @@ _G.THREAD_ID = 0 local multi, thread = require("multi"):init() local GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() +multi.registerType("s_function") +multi.registerType("s_thread") + multi.integration = {} multi.isMainThread = true local threads = {} diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index 95ed193..f3fe45c 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -102,7 +102,7 @@ priorityManager.uManager = function(self) end local function processHook(obj, proc) - if obj.Type == multi.PROCESS or not(obj.IsAnActor) then return end + if obj.Type == multi.registerType("process", "processes") or not(obj.IsAnActor) then return end obj.__restoreProc = proc obj.__profiling = {} obj:reallocate(priorityManager) @@ -171,7 +171,7 @@ local function init() function multi:setPriorityScheme(scheme) - if not self.Type == multi.PROCESS or not self.Type == multi.ROOTPROCESS then + if not self.Type == multi.registerType("process", "processes") or not self.Type == multi.registerType("rootprocess") then multi.warn("You should only invoke setPriorityScheme on a processor object!") end diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 03550b6..5e6a8a2 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -35,6 +35,7 @@ end function multi:newSystemThreadedQueue(name) local c = {} + c.Type = multi.registerType("s_queue") function c:push(v) table.insert(self,v) end @@ -64,6 +65,7 @@ end function multi:newSystemThreadedTable(name) local c = {} + c.Type = multi.registerType("s_table") function c:init() return self end @@ -88,7 +90,7 @@ function multi:newSystemThreadedJobQueue(n) c.cores = n or THREAD.getCores() c.registerQueue = {} - c.Type = multi.SJOBQUEUE + c.Type = multi.registerType("s_jobqueue") c.funcs = multi:newSystemThreadedTable("__JobQueue_"..jqc.."_table") c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue") c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn") diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 91e0824..ec2f70e 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -91,6 +91,7 @@ function multi:newSystemThread(name, func, ...) local GLOBAL, THREAD = activator.init(thread, env) local th = thread:newISOThread(name, func, env, ...) + th.Type = multi.registerType("s_thread", "pseudoThreads") id = id + 1 @@ -104,7 +105,7 @@ THREAD.newSystemThread = multi.newSystemThread function THREAD:newFunction(func,holdme) return thread:newFunctionBase(function(...) return multi:newSystemThread("TempSystemThread",func,...) - end,holdme)() + end, holdme, multi.registerType("s_function", "pseudoFunctions"))() end multi.print("Integrated Pesudo Threading!") diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index c916b0f..7d4af48 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -35,9 +35,9 @@ function multi:chop(obj) local list = {[0] = multi.randomString(12)} _G[list[0]] = obj for i,v in pairs(obj) do - if type(v) == "function" or type(v) == "table" and v.Type == multi.THREADEDFUNCTION then + if type(v) == "function" or type(v) == "table" and v.Type == multi.registerType("s_function") then table.insert(list, i) - elseif type(v) == "table" and v.Type == multi.CONNECTOR then + elseif type(v) == "table" and v.Type == multi.registerType("connector", "connections") then table.insert(list, {i, multi:newProxy(multi:chop(v)):init()}) end end @@ -79,7 +79,7 @@ function multi:newProxy(list) self.recv = multi:newSystemThreadedQueue(self.name.."_R"):init() self.funcs = list self._funcs = copy(list) - self.Type = multi.PROXY + self.Type = multi.registerType("proxy", "proxies") self.TID = THREAD_ID thread:newThread("Proxy_Handler_" .. multi.randomString(4), function() @@ -99,7 +99,7 @@ function multi:newProxy(list) end for i = 1,#ret do - if type(ret[i]) == "table" and ret[i].Type ~= nil and ret[i].Type ~= multi.PROXY then + if type(ret[i]) == "table" and ret[i].Type ~= nil and ret[i].Type ~= multi.registerType("proxy", "proxies") then ret[i] = "\1PARENT_REF" end if type(ret[i]) == "table" and getmetatable(ret[i]) then @@ -133,7 +133,7 @@ function multi:newProxy(list) end self.send = THREAD.waitFor(self.name.."_S"):init() self.recv = THREAD.waitFor(self.name.."_R"):init() - self.Type = multi.PROXY + self.Type = multi.registerType("proxy", "proxies") for _,v in pairs(funcs) do if type(v) == "table" then -- We have a connection @@ -184,11 +184,14 @@ function multi:newProxy(list) cp.funcs = copy(self._funcs) cp.init = function(self) local multi, thread = require("multi"):init() - if multi.integration then - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD - end + -- if multi.integration then + -- GLOBAL = multi.integration.GLOBAL + -- THREAD = multi.integration.THREAD + -- end local proxy = THREAD.waitFor(self.proxy_link) + for i,v in pairs(proxy) do + print("proxy",i,v) + end proxy.funcs = self.funcs return proxy:init() end @@ -211,7 +214,7 @@ function multi:newSystemThreadedProcessor(cores) setmetatable(c,{__index = multi}) - c.Type = multi.SPROCESS + c.Type = multi.registerType("s_process", "s_processes") c.threads = {} c.cores = cores or 8 c.Name = name @@ -331,3 +334,4 @@ function multi:newSystemThreadedProcessor(cores) return c end + diff --git a/tests/threadtests.lua b/tests/threadtests.lua index d2bb646..dcf3a8b 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -102,7 +102,7 @@ multi:newThread("Scheduler Thread",function() t, val = thread.hold(function() return worked - end,{sleep=1}) + end,{sleep=2}) if val == multi.TIMEOUT then multi.error("SystemThreadedTables: Failed") @@ -190,9 +190,11 @@ multi:newThread("Scheduler Thread",function() thread.sleep(1) os.exit(1) end) - local stp = multi:newSystemThreadedProcessor(1) + local stp = multi:newSystemThreadedProcessor(5) - local tloop = stp:newTLoop(nil, 1) + local tloop = stp:newTLoop(function() + print("Test") + end, 1) multi:newSystemThread("Testing proxy copy THREAD",function(tloop) local multi, thread = require("multi"):init() @@ -235,18 +237,18 @@ multi:newThread("Scheduler Thread",function() end) t, val = thread.hold(function() - return count == 10 + return proxy_test end,{sleep=5}) if val == multi.TIMEOUT then multi.error("SystemThreadedProcessor/Proxies: Failed") os.exit(1) + else + multi.success("SystemThreadedProcessor: OK") end thread.sleep(2) - multi.success("SystemThreadedProcessor: OK") - we_good = true multi:Stop() -- Needed in love2d tests to stop the main runner end).OnError(multi.error) -- 2.43.0 From 98198a4af2ed74da4585b47afe677664bac98eff Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 4 Sep 2023 19:48:13 -0400 Subject: [PATCH 094/117] Fixed error code issue --- init.lua | 2 +- integration/debugManager/init.lua | 5 +++++ tests/runtests.lua | 24 ++++++++++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/init.lua b/init.lua index eebca03..e2af02c 100644 --- a/init.lua +++ b/init.lua @@ -67,7 +67,7 @@ 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 }) -multi.DESTROYED = multi.DestroyedObj +multi.DESTROYED = multi.DestroyedObj -- I don't like modifying the global namespace, so I prepend a "$" if not _G["$multi"] then diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua index d397d37..da70e03 100644 --- a/integration/debugManager/init.lua +++ b/integration/debugManager/init.lua @@ -5,12 +5,15 @@ multi.defaultSettings.debugging = true local dbg = {} local creation_hook +local types creation_hook = function(obj, process) + local types = multi:getTypes() print("Created: ",obj.Type, "in", process.Type, process:getFullName()) if obj.Type == multi.PROCESS then obj.OnObjectCreated(creation_hook) end + end local debug_stats = {} @@ -19,6 +22,8 @@ local tmulti = multi:getThreadManagerProcess() multi.OnObjectCreated(creation_hook) tmulti.OnObjectCreated(creation_hook) +multi + --[[ multi.ROOTPROCESS = "rootprocess" multi.CONNECTOR = "connector" diff --git a/tests/runtests.lua b/tests/runtests.lua index 77d0c02..723f431 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -175,12 +175,24 @@ runTest = thread:newFunction(function() end if not love then local ec = 0 - multi.print("Testing pseudo threading") - ec = ec + os.execute("lua tests/threadtests.lua p") - multi.print("Testing lanes threading") - ec = ec + os.execute("lua tests/threadtests.lua l") - if ec ~= 0 then - os.exit(1) + if _VERSION > "5.1" then + multi.print("Testing pseudo threading") + _, str, ecc = os.execute("lua tests/threadtests.lua p") + ec = ec + ecc + multi.print("Testing lanes threading") + _, str, ecc = os.execute("lua tests/threadtests.lua l") + ec = ec + ecc + if ec ~= 0 then + os.exit(1) + end + else + multi.print("Testing pseudo threading") + ec = ec + os.execute("lua tests/threadtests.lua p") + multi.print("Testing lanes threading") + ec = ec + os.execute("lua tests/threadtests.lua l") + if ec ~= 0 then + os.exit(1) + end end end end) -- 2.43.0 From 13221ca47e74d698ead6866814cbced38380adc6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Mon, 4 Sep 2023 19:50:50 -0400 Subject: [PATCH 095/117] Test for 5.1 --- tests/runtests.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runtests.lua b/tests/runtests.lua index 723f431..65a2f67 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -175,7 +175,7 @@ runTest = thread:newFunction(function() end if not love then local ec = 0 - if _VERSION > "5.1" then + if _VERSION == "5.1" then multi.print("Testing pseudo threading") _, str, ecc = os.execute("lua tests/threadtests.lua p") ec = ec + ecc -- 2.43.0 From 3d557047261be4a07b26aabc98588fec2d5772a8 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 7 Sep 2023 21:17:15 -0400 Subject: [PATCH 096/117] Planning out debugManager --- docs/changes.md | 1 + init.lua | 33 ++++++++++---- integration/debugManager/init.lua | 72 ++++++++++++++++++++----------- tests/test.lua | 15 ++++++- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 91ef5c8..c7436b4 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -299,6 +299,7 @@ Added multi:mainloop() ``` +- multi.OnObjectDestroyed(func(obj, process)) now supplies obj, process just like OnObjectCreated - thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. - multi.Processors:getHandler() -- returns the thread handler for a process - multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! diff --git a/init.lua b/init.lua index e2af02c..318ff2a 100644 --- a/init.lua +++ b/init.lua @@ -584,7 +584,7 @@ function multi:Destroy() if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then local c=self:getChildren() for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) + self.OnObjectDestroyed:Fire(c[i], self) c[i]:Destroy() end local new = {} @@ -601,7 +601,7 @@ function multi:Destroy() else for i=#self.Parent.Mainloop,1,-1 do if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) + self.Parent.OnObjectDestroyed:Fire(self, self.Parent) table.remove(self.Parent.Mainloop,i) self.Destroyed = true break @@ -1442,8 +1442,9 @@ end function thread:newProcessor(name, nothread, priority) -- Inactive proxy proc - local proc = multi:getCurrentProcess():newProcessor(name, true) - local thread_proc = multi:getCurrentProcess():newProcessor(name).Start() + local process = multi:getCurrentProcess() + local proc = process:newProcessor(name, true) + local thread_proc = process:newProcessor(name).Start() local Active = true local handler @@ -1458,7 +1459,7 @@ function thread:newProcessor(name, nothread, priority) end function proc:getFullName() - return thread_proc.parent:getFullName() .. "." .. c.Name + return thread_proc.parent:getFullName() .. "." .. self.Name end function proc:getName() @@ -1496,6 +1497,7 @@ function thread:newProcessor(name, nothread, priority) proc.OnObjectCreated(function(obj) if not obj.Act then return end + multi.print("Converting "..obj.Type.." to thread!") thread_proc:newThread(function() obj.reallocate = empty_func while true do @@ -1505,7 +1507,7 @@ function thread:newProcessor(name, nothread, priority) end) end) - self:create(proc) + process:create(proc) return proc end @@ -2191,7 +2193,7 @@ function multi:getLoad() end function multi:setPriority(s) - if not self.IsAnActor or self.Type == multi.registerType("process", "processes") then return end + if not self:IsAnActor() or self.Type == multi.registerType("process", "processes") then return end if type(s)=="number" then self.Priority=s elseif type(s)=='string' then @@ -2378,6 +2380,17 @@ function multi.warn(...) end end +function multi.debug(...) + if multi.defaultSettings.debugging then + local t = {} + for i,v in ipairs(multi.pack(...)) do t[#t+1] = tostring(v) end + io.write("\x1b[97mDEBUG:\x1b[0m " .. table.concat(t," ") + .. "\n" .. multi:getCurrentProcess():getFullName() + .. " " .. (multi:getCurrentTask() and multi:getCurrentTask().Type or "Unknown Type") .. "\n" .. + ((coroutine.running()) and debug.traceback((coroutine.running())) or debug.traceback()) .. "\n") + end +end + function multi.error(self, err) if type(err) == "bool" then crash = err end if type(self) == "string" then err = self end @@ -2462,12 +2475,16 @@ function multi:getHandler() return threadManager:getHandler() end +local function task_holder() + return #tasks > 0 +end + multi:newThread("Task Handler", function() while true do if #tasks > 0 then table.remove(tasks)() else - thread.yield() + thread.hold(task_holder) end end end).OnError(multi.error) diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua index da70e03..bb6f24b 100644 --- a/integration/debugManager/init.lua +++ b/integration/debugManager/init.lua @@ -3,17 +3,55 @@ local multi, thread = require("multi"):init() multi.defaultSettings.debugging = true local dbg = {} +dbg.__index = dbg +dbg.processors = {} -local creation_hook +-- Hooks to all on object created events! +local c_cache = {} +local d_cache = {} + +local proc = multi:newProcessor("Debug_Processor").Start() + +dbg.OnObjectCreated = function(obj, process) + if c_cache[obj] then + return false + else + c_cache[obj] = true + proc:newTask(function() + c_cache[obj] = false + end) + return true + end +end .. multi:newConnection() + +dbg.OnObjectDestroyed = function(obj, process) + if d_cache[obj] then + return false + else + d_cache[obj] = true + proc:newTask(function() + d_cache[obj] = false + end) + return true + end +end .. multi:newConnection() + +local creation_hook, destruction_hook local types +local processes = {} creation_hook = function(obj, process) - local types = multi:getTypes() - print("Created: ",obj.Type, "in", process.Type, process:getFullName()) - if obj.Type == multi.PROCESS then + types = multi:getTypes() + if obj.Type == multi.PROCESS and not dbg.processors[obj] then obj.OnObjectCreated(creation_hook) + obj.OnObjectDestroyed(destruction_hook) + dbg.processors[obj] = {} end - + dbg.OnObjectCreated:Fire(obj, process) +end + +destruction_hook = function(obj, process) + dbg.OnObjectDestroyed:Fire(obj, process) end local debug_stats = {} @@ -21,24 +59,8 @@ local debug_stats = {} local tmulti = multi:getThreadManagerProcess() multi.OnObjectCreated(creation_hook) tmulti.OnObjectCreated(creation_hook) +multi.OnObjectDestroyed(destroction_hook) +tmulti.OnObjectDestroyed(destroction_hook) -multi - ---[[ - multi.ROOTPROCESS = "rootprocess" - multi.CONNECTOR = "connector" - multi.TIMEMASTER = "timemaster" - multi.PROCESS = "process" - multi.TIMER = "timer" - multi.EVENT = "event" - multi.UPDATER = "updater" - multi.ALARM = "alarm" - multi.LOOP = "loop" - multi.TLOOP = "tloop" - multi.STEP = "step" - multi.TSTEP = "tstep" - multi.THREAD = "thread" - multi.SERVICE = "service" - multi.PROXY = "proxy" - multi.THREADEDFUNCTION = "threaded_function" -]] \ No newline at end of file +-- We write to a debug interface in the multi namespace +multi.debugging = dbg diff --git a/tests/test.lua b/tests/test.lua index 806f58a..f96eeb1 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,7 +1,15 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{print=true,warn=true,error=true} +multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging=true} require("multi.integration.priorityManager") +multi.debugging.OnObjectCreated(function(obj, process) + multi.print("Created:", obj.Type, "in", process.Type, process:getFullName()) +end) + +multi.debugging.OnObjectDestroyed(function(obj, process) + multi.print("Destroyed:", obj.Type, "in", process.Type, process:getFullName()) +end) + -- test = multi:newProcessor("Test") -- test:setPriorityScheme(multi.priorityScheme.TimeBased) @@ -68,6 +76,11 @@ proc:newThread(function() end end) +proc:newAlarm(5):OnRing(function(a) + multi.print(";) Goodbye") + a:Destroy() +end) + multi:mainloop() -- 2.43.0 From ad929da4840b27418eaf0c6aba818e95cf32f241 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 3 Oct 2023 22:21:37 -0400 Subject: [PATCH 097/117] Some work on the debug manager, proxies working on lanes, todo get pseudo manager and love2d working --- integration/debugManager/init.lua | 44 +++++++++++++++++++++++++-- integration/sharedExtensions/init.lua | 8 ++--- lovethreads/multi | 0 tests/threadtests.lua | 11 +------ 4 files changed, 47 insertions(+), 16 deletions(-) mode change 120000 => 100644 lovethreads/multi diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua index bb6f24b..c467547 100644 --- a/integration/debugManager/init.lua +++ b/integration/debugManager/init.lua @@ -38,22 +38,62 @@ end .. multi:newConnection() local creation_hook, destruction_hook local types -local processes = {} +local objects = {} creation_hook = function(obj, process) types = multi:getTypes() if obj.Type == multi.PROCESS and not dbg.processors[obj] then obj.OnObjectCreated(creation_hook) obj.OnObjectDestroyed(destruction_hook) - dbg.processors[obj] = {} end + + table.insert(objects, obj) + dbg.OnObjectCreated:Fire(obj, process) end destruction_hook = function(obj, process) + for i = 1, #objects do + if objects[i] == obj then + table.remove(objects, i) + break + end + end dbg.OnObjectDestroyed:Fire(obj, process) end +function dbg:getObjects(typ) + if type(typ) == "string" then + local objs = {} + for i = 1, #objects do + if objects[i].Type == typ then + objs[#objs+1] = objects[i] + end + end + return objs + elseif type(typ) == "table" then -- Process + local objs = {} + for i = 1, #objects do + if objects[i].Parent == typ then + objs[#objs+1] = objects[i] + end + end + return objs + elseif type(typ) == "function" then + local objs = {} + -- Keep objects local/private, return true to add to list, false to reject, "break" to break loop + for i = 1, #objects do + local ret = typ(objects[i]) + if ret then + objs[#objs+1] = objects[i] + elseif ret == "break" then + break + end + end + return objs + end +end + local debug_stats = {} local tmulti = multi:getThreadManagerProcess() diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 7d4af48..a5b7caa 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -184,10 +184,10 @@ function multi:newProxy(list) cp.funcs = copy(self._funcs) cp.init = function(self) local multi, thread = require("multi"):init() - -- if multi.integration then - -- GLOBAL = multi.integration.GLOBAL - -- THREAD = multi.integration.THREAD - -- end + if multi.integration then + GLOBAL = multi.integration.GLOBAL + THREAD = multi.integration.THREAD + end local proxy = THREAD.waitFor(self.proxy_link) for i,v in pairs(proxy) do print("proxy",i,v) diff --git a/lovethreads/multi b/lovethreads/multi deleted file mode 120000 index b870225..0000000 --- a/lovethreads/multi +++ /dev/null @@ -1 +0,0 @@ -../ \ No newline at end of file diff --git a/lovethreads/multi b/lovethreads/multi new file mode 100644 index 0000000..b870225 --- /dev/null +++ b/lovethreads/multi @@ -0,0 +1 @@ +../ \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index dcf3a8b..bb5754b 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -180,16 +180,6 @@ multi:newThread("Scheduler Thread",function() -- end -- multi.success("SystemThreadedConnections: Ok") local proxy_test = false - multi:newThread(function() - t, val = thread.hold(function() - return proxy_test - end,{sleep=5}) - if val == multi.TIMEOUT then - multi.error("SystemThreadedProcessor/Proxies: Failed") - end - thread.sleep(1) - os.exit(1) - end) local stp = multi:newSystemThreadedProcessor(5) local tloop = stp:newTLoop(function() @@ -251,6 +241,7 @@ multi:newThread("Scheduler Thread",function() we_good = true multi:Stop() -- Needed in love2d tests to stop the main runner + os.exit(0) end).OnError(multi.error) multi.OnExit(function(err_or_errorcode) -- 2.43.0 From ef7464f70def93a0c0b25fc74a0339039b10040b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 15 Oct 2023 13:21:57 -0400 Subject: [PATCH 098/117] Working on getting pseudoThreading tests to work --- init.lua | 3 +- integration/lanesManager/extensions.lua | 10 +- integration/lanesManager/init.lua | 1 + integration/loveManager/extensions.lua | 4 +- integration/loveManager/init.lua | 2 + integration/loveManagerold/extensions.lua | 4 +- integration/loveManagerold/init.lua | 2 + integration/priorityManager/init.lua | 2 +- integration/pseudoManager/extensions.lua | 8 +- integration/pseudoManager/init.lua | 1 + integration/sharedExtensions/init.lua | 5 +- tests/main.lua | 8 +- tests/threadtests.lua | 26 +- tests/vscode-debuggee.lua | 1102 +++++++++++++++++++++ 14 files changed, 1150 insertions(+), 28 deletions(-) create mode 100644 tests/vscode-debuggee.lua diff --git a/init.lua b/init.lua index 318ff2a..928237e 100644 --- a/init.lua +++ b/init.lua @@ -1423,7 +1423,7 @@ function thread:newFunctionBase(generator, holdme, TYPE) end } t.OnDeath(function(...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) end) + t.OnError(function(self,err) temp.OnError:Fire(err) temp.OnError(multi.error) end) t.linkedFunction = temp t.statusconnector = temp.OnStatus return temp @@ -1885,6 +1885,7 @@ function multi:newService(func) -- Priority managed threads end) th.OnError = c.OnError -- use the threads onerror as our own + th.OnError(multi.error) function c.Destroy() th:kill() diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 4ff3605..618762f 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -207,9 +207,9 @@ function multi:newSystemThreadedJobQueue(n) local jid = table.remove(dat, 1) local args = table.remove(dat, 1) queueReturn:push{jid, funcs[name](args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8]), queue} - end).OnError(multi.error) + end) end - end).OnError(multi.error) + end) thread:newThread("DoAllHandler",function() while true do local dat = thread.hold(function() @@ -225,7 +225,7 @@ function multi:newSystemThreadedJobQueue(n) end end end - end).OnError(multi.error) + end) thread:newThread("IdleHandler",function() while true do thread.hold(function() @@ -233,9 +233,9 @@ function multi:newSystemThreadedJobQueue(n) end) THREAD.sleep(.01) end - end).OnError(multi.error) + end) multi:mainloop() - end,i).OnError(multi.error) + end,i) end function c:Hold(opt) diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 2f3e646..82d5610 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -130,6 +130,7 @@ function multi:newSystemThread(name, func, ...) c.OnDeath = multi:newConnection() c.OnError = multi:newConnection() GLOBAL["__THREADS__"] = livingThreads + c.OnError(multi.error) if self.isActor then self:create(c) diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index f1a7f27..9a22c5b 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -172,6 +172,7 @@ function multi:newSystemThreadedJobQueue(n) multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) local multi, thread = require("multi"):init() require("love.timer") + love.timer.sleep(1) local clock = os.clock local funcs = THREAD.createTable("__JobQueue_"..jqc.."_table") local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue") @@ -208,11 +209,12 @@ function multi:newSystemThreadedJobQueue(n) local id = table.remove(dat,1) local tab = {funcs[name](multi.unpack(dat))} table.insert(tab,1,id) + --local test = queueReturn.push queueReturn:push(tab) end) end end - end).OnError(multi.error) + end) thread:newThread("Idler",function() while true do thread.yield() diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 92cba27..0bc062b 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -60,6 +60,8 @@ function multi:newSystemThread(name, func, ...) table.insert(threads, c) + c.OnError(multi.error) + if self.isActor then self:create(c) else diff --git a/integration/loveManagerold/extensions.lua b/integration/loveManagerold/extensions.lua index 36931b3..91f5819 100644 --- a/integration/loveManagerold/extensions.lua +++ b/integration/loveManagerold/extensions.lua @@ -300,7 +300,7 @@ function multi:newSystemThreadedConnection(name) -- This shouldn't be the case end end - end).OnError(multi.error) + end) return self end @@ -377,7 +377,7 @@ function multi:newSystemThreadedConnection(name) c.proxy_conn:Fire(multi.unpack(item[2])) end end - end).OnError(multi.error) + end) --- ^^^ This will only exist in the init thread THREAD.package(name,c) diff --git a/integration/loveManagerold/init.lua b/integration/loveManagerold/init.lua index f913103..2ab3061 100644 --- a/integration/loveManagerold/init.lua +++ b/integration/loveManagerold/init.lua @@ -104,6 +104,8 @@ function multi:newSystemThread(name, func, ...) c.stab.returns = nil end end) + + c.OnError(multi.error) if self.isActor then self:create(c) diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index f3fe45c..ab5e6da 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -210,7 +210,7 @@ local function init_chronos() thread.yield() priorityManager.run() end - end).OnError(multi.error) + end) end if chronos then diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 5e6a8a2..fe0abd8 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -35,15 +35,16 @@ end function multi:newSystemThreadedQueue(name) local c = {} + c.data = {} c.Type = multi.registerType("s_queue") function c:push(v) table.insert(self,v) end function c:pop() - return table.remove(self,1) + return table.remove(self.data,1) end function c:peek() - return self[1] + return self.data[1] end function c:init() return self @@ -156,6 +157,7 @@ function multi:newSystemThreadedJobQueue(n) end) for i=1,c.cores do multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + local GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() local multi, thread = require("multi"):init() local clock = os.clock local funcs = THREAD.waitFor("__JobQueue_"..jqc.."_table") @@ -197,7 +199,7 @@ function multi:newSystemThreadedJobQueue(n) end) end end - end).OnError(multi.error) + end) thread:newThread("Idler",function() while true do thread.yield() diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index ec2f70e..e1b7b03 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -92,6 +92,7 @@ function multi:newSystemThread(name, func, ...) local th = thread:newISOThread(name, func, env, ...) th.Type = multi.registerType("s_thread", "pseudoThreads") + th.OnError(multi.error) id = id + 1 diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index a5b7caa..a048b5f 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -92,6 +92,8 @@ function multi:newProxy(list) local sref = table.remove(data, 1) local ret + print(_G[list[0]], func) + if sref then ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} else @@ -115,7 +117,7 @@ function multi:newProxy(list) end) end end - end).OnError(multi.error) + end) return self else local function copy(obj) @@ -143,6 +145,7 @@ function multi:newProxy(list) setmetatable(v[2],getmetatable(multi:newConnection())) else self[v] = thread:newFunction(function(self,...) + multi.print("Pushing: " .. v) if self == me then me.send:push({v, true, ...}) else diff --git a/tests/main.lua b/tests/main.lua index fb4e2f9..a21d620 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,8 +1,12 @@ package.path = "../?/init.lua;../?.lua;"..package.path -require("runtests") -require("threadtests") +-- require("runtests") +-- require("threadtests") -- Allows you to run "love tests" which runs the tests +multi, thread = require("multi"):init() +GLOBAL, THREAD = require("multi.integration.loveManager"):init() + + function love.update() multi:uManager() end \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index bb5754b..f3298a2 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,4 +1,5 @@ -package.path = "../?/init.lua;../?.lua;"..package.path +package.path = "D:/VSCWorkspace/?/init.lua;D:/VSCWorkspace/?.lua;"..package.path +package.cpath = "C:/luaInstalls/lua5.4/lib/lua/5.4/?/core.dll;" .. package.cpath multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 @@ -37,12 +38,12 @@ THREAD.setENV({ }) multi:newThread("Scheduler Thread",function() - multi:newThread(function() - thread.sleep(30) - print("Timeout tests took longer than 30 seconds") - multi:Stop() - os.exit(1) - end) + -- multi:newThread(function() + -- thread.sleep(30) + -- print("Timeout tests took longer than 30 seconds") + -- multi:Stop() + -- os.exit(1) + -- end) queue = multi:newSystemThreadedQueue("Test_Queue"):init() multi:newSystemThread("Test_Thread_0", function() @@ -201,7 +202,7 @@ multi:newThread("Scheduler Thread",function() print(THREAD_NAME, "Got loop...") end) multi:mainloop() - end, tloop:getTransferable()).OnError(multi.error) + end, tloop:getTransferable()) multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) @@ -213,12 +214,13 @@ multi:newThread("Scheduler Thread",function() thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... twice") proxy_test = true - end).OnError(multi.error) + end) thread:newThread(function() + print("While Test!") while true do thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") + print(THREAD_NAME,"Local Loopy") end end) @@ -228,7 +230,7 @@ multi:newThread("Scheduler Thread",function() t, val = thread.hold(function() return proxy_test - end,{sleep=5}) + end--[[,{sleep=5}]]) -- No timeouts if val == multi.TIMEOUT then multi.error("SystemThreadedProcessor/Proxies: Failed") @@ -242,7 +244,7 @@ multi:newThread("Scheduler Thread",function() we_good = true multi:Stop() -- Needed in love2d tests to stop the main runner os.exit(0) -end).OnError(multi.error) +end) multi.OnExit(function(err_or_errorcode) print("Error Code: ", err_or_errorcode) diff --git a/tests/vscode-debuggee.lua b/tests/vscode-debuggee.lua new file mode 100644 index 0000000..61b47fb --- /dev/null +++ b/tests/vscode-debuggee.lua @@ -0,0 +1,1102 @@ +local debuggee = {} + +local socket = require 'socket.core' +local json +local handlers = {} +local sock +local directorySeperator = package.config:sub(1,1) +local sourceBasePath = '.' +local storedVariables = {} +local nextVarRef = 1 +local baseDepth +local breaker +local sendEvent +local dumpCommunication = false +local ignoreFirstFrameInC = false +local debugTargetCo = nil +local redirectedPrintFunction = nil + +local onError = nil +local addUserdataVar = nil + +local function defaultOnError(e) + print('****************************************************') + print(e) + print('****************************************************') +end + +local function valueToString(value, depth) + local str = '' + depth = depth or 0 + local t = type(value) + if t == 'table' then + str = str .. '{\n' + for k, v in pairs(value) do + str = str .. string.rep(' ', depth + 1) .. '[' .. valueToString(k) ..']' .. ' = ' .. valueToString(v, depth + 1) .. ',\n' + end + str = str .. string.rep(' ', depth) .. '}' + elseif t == 'string' then + str = str .. '"' .. tostring(value) .. '"' + else + str = str .. tostring(value) + end + return str +end + +------------------------------------------------------------------------------- +local sethook = debug.sethook +debug.sethook = nil + +local cocreate = coroutine.create +coroutine.create = function(f) + local c = cocreate(f) + debuggee.addCoroutine(c) + return c +end + +------------------------------------------------------------------------------- +local function debug_getinfo(depth, what) + if debugTargetCo then + return debug.getinfo(debugTargetCo, depth, what) + else + return debug.getinfo(depth + 1, what) + end +end + +------------------------------------------------------------------------------- +local function debug_getlocal(depth, i) + if debugTargetCo then + return debug.getlocal(debugTargetCo, depth, i) + else + return debug.getlocal(depth + 1, i) + end +end + +------------------------------------------------------------------------------- +local DO_TEST = false + +------------------------------------------------------------------------------- +-- chunkname matching {{{ +local function getMatchCount(a, b) + local n = math.min(#a, #b) + for i = 0, n - 1 do + if a[#a - i] == b[#b - i] then + -- pass + else + return i + end + end + return n +end +if DO_TEST then + assert(getMatchCount({'a','b','c'}, {'a','b','c'}) == 3) + assert(getMatchCount({'b','c'}, {'a','b','c'}) == 2) + assert(getMatchCount({'a','b','c'}, {'b','c'}) == 2) + assert(getMatchCount({}, {'a','b','c'}) == 0) + assert(getMatchCount({'a','b','c'}, {}) == 0) + assert(getMatchCount({'a','b','c'}, {'a','b','c','d'}) == 0) +end + +local function splitChunkName(s) + if string.sub(s, 1, 1) == '@' then + s = string.sub(s, 2) + end + + local a = {} + for word in string.gmatch(s, '[^/\\]+') do + a[#a + 1] = string.lower(word) + end + return a +end +if DO_TEST then + local a = splitChunkName('@.\\vscode-debuggee.lua') + assert(#a == 2) + assert(a[1] == '.') + assert(a[2] == 'vscode-debuggee.lua') + + local a = splitChunkName('@C:\\dev\\VSCodeLuaDebug\\debuggee/lua\\socket.lua') + assert(#a == 6) + assert(a[1] == 'c:') + assert(a[2] == 'dev') + assert(a[3] == 'vscodeluadebug') + assert(a[4] == 'debuggee') + assert(a[5] == 'lua') + assert(a[6] == 'socket.lua') + + local a = splitChunkName('@main.lua') + assert(#a == 1) + assert(a[1] == 'main.lua') +end +-- chunkname matching }}} + +-- path control {{{ +local Path = {} + +function Path.isAbsolute(a) + local firstChar = string.sub(a, 1, 1) + if firstChar == '/' or firstChar == '\\' then + return true + end + + if string.match(a, '^%a%:[/\\]') then + return true + end + + return false +end + +local np_pat1, np_pat2 = ('[^SEP:]+SEP%.%.SEP?'):gsub('SEP', directorySeperator), ('SEP+%.?SEP'):gsub('SEP', directorySeperator) +function Path.normpath(path) + path = path:gsub('[/\\]', directorySeperator) + + if directorySeperator == '\\' then + local unc = ('SEPSEP'):gsub('SEP', directorySeperator) -- UNC + if path:match('^'..unc) then + return unc..Path.normpath(path:sub(3)) + end + end + + local k + repeat -- /./ -> / + path,k = path:gsub(np_pat2, directorySeperator) + until k == 0 + repeat -- A/../ -> (empty) + path,k = path:gsub(np_pat1, '', 1) + until k == 0 + if path == '' then + path = '.' + end + return path +end + +function Path.concat(a, b) + -- normalize a + local lastChar = string.sub(a, #a, #a) + if not (lastChar == '/' or lastChar == '\\') then + a = a .. directorySeperator + end + + -- normalize b + if string.match(b, '^%.%\\') or string.match(b, '^%.%/') then + b = string.sub(b, 3) + end + + return a .. b +end + +function Path.toAbsolute(base, sub) + if Path.isAbsolute(sub) then + return Path.normpath(sub) + else + return Path.normpath(Path.concat(base, sub)) + end +end + +if DO_TEST then + assert(Path.isAbsolute('c:\\asdf\\afsd')) + assert(Path.isAbsolute('c:/asdf/afsd')) + if directorySeperator == '\\' then + assert(Path.toAbsolute('c:\\asdf', 'fdsf') == 'c:\\asdf\\fdsf') + assert(Path.toAbsolute('c:\\asdf', '.\\fdsf') == 'c:\\asdf\\fdsf') + assert(Path.toAbsolute('c:\\asdf', '..\\fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('c:\\asdf', 'c:\\fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('c:/asdf', '../fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('\\\\HOST\\asdf', '..\\fdsf') == '\\\\HOST\\fdsf') + elseif directorySeperator == '/' then + assert(Path.toAbsolute('/usr/bin/asdf', 'fdsf') == '/usr/bin/asdf/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', './fdsf') == '/usr/bin/asdf/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', '../fdsf') == '/usr/bin/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', '/usr/bin/fdsf') == '/usr/bin/fdsf') + assert(Path.toAbsolute('\\usr\\bin\\asdf', '..\\fdsf') == '/usr/bin/fdsf') + end +end +-- path control }}} + +local coroutineSet = {} +setmetatable(coroutineSet, { __mode = 'v' }) + +------------------------------------------------------------------------------- +-- network utility {{{ +local function sendFully(str) + local first = 1 + while first <= #str do + local sent = sock:send(str, first) + if sent and sent > 0 then + first = first + sent; + else + error('sock:send() returned < 0') + end + end +end + +-- send log to debug console +local function logToDebugConsole(output, category) + local dumpMsg = { + event = 'output', + type = 'event', + body = { + category = category or 'console', + output = output + } + } + local dumpBody = json.encode(dumpMsg) + sendFully('#' .. #dumpBody .. '\n' .. dumpBody) +end + +-- pure mode {{{ +local function createHaltBreaker() + -- chunkname matching { + local loadedChunkNameMap = {} + for chunkname, _ in pairs(debug.getchunknames()) do + loadedChunkNameMap[chunkname] = splitChunkName(chunkname) + end + + local function findMostSimilarChunkName(path) + local splitedReqPath = splitChunkName(path) + local maxMatchCount = 0 + local foundChunkName = nil + for chunkName, splitted in pairs(loadedChunkNameMap) do + local count = getMatchCount(splitedReqPath, splitted) + if (count > maxMatchCount) then + maxMatchCount = count + foundChunkName = chunkName + end + end + return foundChunkName + end + -- chunkname matching } + + local lineBreakCallback = nil + local function updateCoroutineHook(c) + if lineBreakCallback then + sethook(c, lineBreakCallback, 'l') + else + sethook(c) + end + end + local function sethalt(cname, ln) + for i = ln, ln + 10 do + if debug.sethalt(cname, i) then + return i + end + end + return nil + end + return { + setBreakpoints = function(path, lines) + local foundChunkName = findMostSimilarChunkName(path) + local verifiedLines = {} + + if foundChunkName then + debug.clearhalt(foundChunkName) + for _, ln in ipairs(lines) do + verifiedLines[ln] = sethalt(foundChunkName, ln) + end + end + + return verifiedLines + end, + + setLineBreak = function(callback) + if callback then + sethook(callback, 'l') + else + sethook() + end + + lineBreakCallback = callback + for cid, c in pairs(coroutineSet) do + updateCoroutineHook(c) + end + end, + + coroutineAdded = function(c) + updateCoroutineHook(c) + end, + + stackOffset = + { + enterDebugLoop = 6, + halt = 6, + step = 4, + stepDebugLoop = 6 + } + } +end + +local function createPureBreaker() + local lineBreakCallback = nil + local breakpointsPerPath = {} + local chunknameToPathCache = {} + + local function chunkNameToPath(chunkname) + local cached = chunknameToPathCache[chunkname] + if cached then + return cached + end + + local splitedReqPath = splitChunkName(chunkname) + local maxMatchCount = 0 + local foundPath = nil + for path, _ in pairs(breakpointsPerPath) do + local splitted = splitChunkName(path) + local count = getMatchCount(splitedReqPath, splitted) + if (count > maxMatchCount) then + maxMatchCount = count + foundPath = path + end + end + + if foundPath then + chunknameToPathCache[chunkname] = foundPath + end + return foundPath + end + + local entered = false + local function hookfunc() + if entered then return false end + entered = true + + if lineBreakCallback then + lineBreakCallback() + end + + local info = debug_getinfo(2, 'Sl') + if info then + local path = chunkNameToPath(info.source) + if path then + path = string.lower(path) + end + local bpSet = breakpointsPerPath[path] + if bpSet and bpSet[info.currentline] then + _G.__halt__() + end + end + + entered = false + end + sethook(hookfunc, 'l') + + return { + setBreakpoints = function(path, lines) + local t = {} + local verifiedLines = {} + for _, ln in ipairs(lines) do + t[ln] = true + verifiedLines[ln] = ln + end + if path then + path = string.lower(path) + end + breakpointsPerPath[path] = t + return verifiedLines + end, + + setLineBreak = function(callback) + lineBreakCallback = callback + end, + + coroutineAdded = function(c) + sethook(c, hookfunc, 'l') + end, + + stackOffset = + { + enterDebugLoop = 6, + halt = 7, + step = 4, + stepDebugLoop = 7 + } + } +end +-- pure mode }}} + + +-- 센드는 블럭이어도 됨. +local function sendMessage(msg) + local body = json.encode(msg) + + if dumpCommunication then + logToDebugConsole('[SENDING] ' .. valueToString(msg)) + end + + sendFully('#' .. #body .. '\n' .. body) +end + +-- 리시브는 블럭이 아니어야 할 거 같은데... 음... 블럭이어도 괜찮나? +local function recvMessage() + local header = sock:receive('*l') + if (header == nil) then + -- 디버거가 떨어진 상황 + return nil + end + if (string.sub(header, 1, 1) ~= '#') then + error('헤더 이상함:' .. header) + end + + local bodySize = tonumber(header:sub(2)) + local body = sock:receive(bodySize) + + return json.decode(body) +end +-- network utility }}} + +------------------------------------------------------------------------------- +local function debugLoop() + storedVariables = {} + nextVarRef = 1 + while true do + local msg = recvMessage() + if msg then + if dumpCommunication then + logToDebugConsole('[RECEIVED] ' .. valueToString(msg), 'stderr') + end + + local fn = handlers[msg.command] + if fn then + local rv = fn(msg) + + -- continue인데 break하는 게 역설적으로 느껴지지만 + -- 디버그 루프를 탈출(break)해야 정상 실행 흐름을 계속(continue)할 수 있지.. + if (rv == 'CONTINUE') then + break; + end + else + --print('UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) + end + else + -- 디버그 중에 디버거가 떨어졌다. + -- print펑션을 리다이렉트 한경우에는 원래대로 돌려놓는다 + if redirectedPrintFunction then + _G.print = redirectedPrintFunction + end + break + end + end + storedVariables = {} + nextVarRef = 1 +end + +------------------------------------------------------------------------------- +local sockArray = {} +function debuggee.start(jsonLib, config) + json = jsonLib + assert(jsonLib) + + config = config or {} + local connectTimeout = config.connectTimeout or 5.0 + local controllerHost = config.controllerHost or 'localhost' + local controllerPort = config.controllerPort or 56789 + onError = config.onError or defaultOnError + addUserdataVar = config.addUserdataVar or function() return end + local redirectPrint = config.redirectPrint or false + dumpCommunication = config.dumpCommunication or false + ignoreFirstFrameInC = config.ignoreFirstFrameInC or false + if not config.luaStyleLog then + valueToString = function(value) return json.encode(value) end + end + + local breakerType + if debug.sethalt then + breaker = createHaltBreaker() + breakerType = 'halt' + else + breaker = createPureBreaker() + breakerType = 'pure' + end + + local err + sock, err = socket.tcp() + if not sock then error(err) end + sockArray = { sock } + if sock.settimeout then sock:settimeout(connectTimeout) end + local res, err = sock:connect(controllerHost, tostring(controllerPort)) + if not res then + sock:close() + sock = nil + return false, breakerType + end + + if sock.settimeout then sock:settimeout() end + sock:setoption('tcp-nodelay', true) + + local initMessage = recvMessage() + assert(initMessage and initMessage.command == 'welcome') + sourceBasePath = initMessage.sourceBasePath + directorySeperator = initMessage.directorySeperator + + if redirectPrint then + redirectedPrintFunction = _G.print -- 디버거가 떨어질때를 대비해서 보관한다 + _G.print = function(...) + local t = { n = select("#", ...), ... } + for i = 1, #t do + t[i] = tostring(t[i]) + end + sendEvent( + 'output', + { + category = 'stdout', + output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. + }) + end + end + + debugLoop() + return true, breakerType +end + +------------------------------------------------------------------------------- +function debuggee.poll() + if not sock then return end + + -- Processes commands in the queue. + -- Immediately returns when the queue is/became empty. + while true do + local r, w, e = socket.select(sockArray, nil, 0) + if e == 'timeout' then break end + + local msg = recvMessage() + if msg then + if dumpCommunication then + logToDebugConsole('[POLL-RECEIVED] ' .. valueToString(msg), 'stderr') + end + + if msg.command == 'pause' then + debuggee.enterDebugLoop(1) + return + end + + local fn = handlers[msg.command] + if fn then + local rv = fn(msg) + -- Ignores rv, because this loop never blocks except explicit pause command. + else + --print('POLL-UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) + end + else + break + end + end +end + +------------------------------------------------------------------------------- +local function getCoroutineId(c) + -- 'thread: 011DD5B0' + -- 12345678^ + local threadIdHex = string.sub(tostring(c), 9) + return tonumber(threadIdHex, 16) +end + +------------------------------------------------------------------------------- +function debuggee.addCoroutine(c) + local cid = getCoroutineId(c) + coroutineSet[cid] = c + breaker.coroutineAdded(c) +end + +------------------------------------------------------------------------------- +local function sendSuccess(req, body) + sendMessage({ + command = req.command, + success = true, + request_seq = req.seq, + type = "response", + body = body + }) +end + +------------------------------------------------------------------------------- +local function sendFailure(req, msg) + sendMessage({ + command = req.command, + success = false, + request_seq = req.seq, + type = "response", + message = msg + }) +end + +------------------------------------------------------------------------------- +sendEvent = function(eventName, body) + sendMessage({ + event = eventName, + type = "event", + body = body + }) +end + +------------------------------------------------------------------------------- +local function currentThreadId() +--[[ + local threadId = 0 + if coroutine.running() then + end + return threadId +]] + return 0 +end + +------------------------------------------------------------------------------- +local function startDebugLoop() + sendEvent( + 'stopped', + { + reason = 'breakpoint', + threadId = currentThreadId(), + allThreadsStopped = true + }) + + local status, err = pcall(debugLoop) + if not status then + onError(err) + end +end + +------------------------------------------------------------------------------- +_G.__halt__ = function() + baseDepth = breaker.stackOffset.halt + startDebugLoop() +end + +------------------------------------------------------------------------------- +function debuggee.enterDebugLoop(depthOrCo, what) + if sock == nil then + return false + end + + if what then + sendEvent( + 'output', + { + category = 'stderr', + output = what, + }) + end + + if type(depthOrCo) == 'thread' then + baseDepth = 0 + debugTargetCo = depthOrCo + elseif type(depthOrCo) == 'table' then + baseDepth = (depthOrCo.depth or 0) + debugTargetCo = depthOrCo.co + else + baseDepth = (depthOrCo or 0) + breaker.stackOffset.enterDebugLoop + debugTargetCo = nil + end + startDebugLoop() + return true +end + +------------------------------------------------------------------------------- +-- Function for printing on vscode debug console +-- First parameter 'category' can colorizes print text +function debuggee.print(category, ...) + if sock == nil then + return false + end + local t = { ... } + for i = 1, #t do + t[i] = tostring(t[i]) + end + + local categoryVscodeConsole = 'stdout' + if category == 'warning' then + categoryVscodeConsole = 'console' -- yellow + elseif category == 'error' then + categoryVscodeConsole = 'stderr' -- red + elseif category == 'log' then + categoryVscodeConsole = 'stdout' -- white + end + + sendEvent( + 'output', + { + category = categoryVscodeConsole, + output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. + }) +end + +------------------------------------------------------------------------------- +-- ★★★ https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +function handlers.setBreakpoints(req) + local bpLines = {} + for _, bp in ipairs(req.arguments.breakpoints) do + bpLines[#bpLines + 1] = bp.line + end + + local verifiedLines = breaker.setBreakpoints( + req.arguments.source.path, + bpLines) + + local breakpoints = {} + for i, ln in ipairs(bpLines) do + breakpoints[i] = { + verified = (verifiedLines[ln] ~= nil), + line = verifiedLines[ln] + } + end + + sendSuccess(req, { + breakpoints = breakpoints + }) +end + +------------------------------------------------------------------------------- +function handlers.configurationDone(req) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.threads(req) + local c = coroutine.running() + + local mainThread = { + id = currentThreadId(), + name = (c and tostring(c)) or "main" + } + + sendSuccess(req, { + threads = { mainThread } + }) +end + +------------------------------------------------------------------------------- +function handlers.stackTrace(req) + assert(req.arguments.threadId == 0) + + local stackFrames = {} + local firstFrame = (req.arguments.startFrame or 0) + baseDepth + local lastFrame = (req.arguments.levels and (req.arguments.levels ~= 0)) + and (firstFrame + req.arguments.levels - 1) + or (9999) + + -- if firstframe function of stack is C function, ignore it. + if ignoreFirstFrameInC then + local info = debug_getinfo(firstFrame, 'lnS') + if info and info.what == "C" then + firstFrame = firstFrame + 1 + end + end + + for i = firstFrame, lastFrame do + local info = debug_getinfo(i, 'lnS') + if (info == nil) then break end + --print(json.encode(info)) + + local src = info.source + if string.sub(src, 1, 1) == '@' then + src = string.sub(src, 2) -- 앞의 '@' 떼어내기 + end + + local name + if info.name then + name = info.name .. ' (' .. (info.namewhat or '?') .. ')' + else + name = '?' + end + + local sframe = { + name = name, + source = { + name = nil, + path = Path.toAbsolute(sourceBasePath, src) + }, + column = 1, + line = info.currentline or 1, + id = i, + } + stackFrames[#stackFrames + 1] = sframe + end + + sendSuccess(req, { + stackFrames = stackFrames + }) +end + +------------------------------------------------------------------------------- +local scopeTypes = { + Locals = 1, + Upvalues = 2, + Globals = 3, +} +function handlers.scopes(req) + local depth = req.arguments.frameId + + local scopes = {} + local function addScope(name) + scopes[#scopes + 1] = { + name = name, + expensive = false, + variablesReference = depth * 1000000 + scopeTypes[name] + } + end + + addScope('Locals') + addScope('Upvalues') + addScope('Globals') + + sendSuccess(req, { + scopes = scopes + }) +end + +------------------------------------------------------------------------------- +local function registerVar(varNameCount, name_, value, noQuote) + local ty = type(value) + local name + if type(name_) == 'number' then + name = '[' .. name_ .. ']' + else + name = tostring(name_) + end + if varNameCount[name] then + varNameCount[name] = varNameCount[name] + 1 + name = name .. ' (' .. varNameCount[name] .. ')' + else + varNameCount[name] = 1 + end + + local item = { + name = name, + type = ty + } + + if (ty == 'string' and (not noQuote)) then + item.value = '"' .. value .. '"' + else + item.value = tostring(value) + end + + if (ty == 'table') or + (ty == 'function') or + (ty == 'userdata') then + storedVariables[nextVarRef] = value + item.variablesReference = nextVarRef + nextVarRef = nextVarRef + 1 + else + item.variablesReference = -1 + end + + return item +end + +------------------------------------------------------------------------------- +function handlers.variables(req) + local varRef = req.arguments.variablesReference + local variables = {} + local varNameCount = {} + local function addVar(name, value, noQuote) + variables[#variables + 1] = registerVar(varNameCount, name, value, noQuote) + end + + if (varRef >= 1000000) then + -- Scope. + local depth = math.floor(varRef / 1000000) + local scopeType = varRef % 1000000 + if scopeType == scopeTypes.Locals then + for i = 1, 9999 do + local name, value = debug_getlocal(depth, i) + if name == nil then break end + addVar(name, value, nil) + end + elseif scopeType == scopeTypes.Upvalues then + local info = debug_getinfo(depth, 'f') + if info and info.func then + for i = 1, 9999 do + local name, value = debug.getupvalue(info.func, i) + if name == nil then break end + addVar(name, value, nil) + end + end + elseif scopeType == scopeTypes.Globals then + for name, value in pairs(_G) do + addVar(name, value) + end + table.sort(variables, function(a, b) return a.name < b.name end) + end + else + -- Expansion. + local var = storedVariables[varRef] + if type(var) == 'table' then + for k, v in pairs(var) do + addVar(k, v) + end + table.sort(variables, function(a, b) + local aNum, aMatched = string.gsub(a.name, '^%[(%d+)%]$', '%1') + local bNum, bMatched = string.gsub(b.name, '^%[(%d+)%]$', '%1') + + if (aMatched == 1) and (bMatched == 1) then + -- both are numbers. compare numerically. + return tonumber(aNum) < tonumber(bNum) + elseif aMatched == bMatched then + -- both are strings. compare alphabetically. + return a.name < b.name + else + -- string comes first. + return aMatched < bMatched + end + end) + elseif type(var) == 'function' then + local info = debug.getinfo(var, 'S') + addVar('(source)', tostring(info.short_src), true) + addVar('(line)', info.linedefined) + + for i = 1, 9999 do + local name, value = debug.getupvalue(var, i) + if name == nil then break end + addVar(name, value) + end + elseif type(var) == 'userdata' then + addUserdataVar(var, addVar) + end + + local mt = getmetatable(var) + if mt then + addVar("(metatable)", mt) + end + end + + sendSuccess(req, { + variables = variables + }) +end + +------------------------------------------------------------------------------- +function handlers.continue(req) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +local function stackHeight() + for i = 1, 9999999 do + if (debug_getinfo(i, '') == nil) then + return i + end + end +end + +------------------------------------------------------------------------------- +local stepTargetHeight = nil +local function step() + if (stepTargetHeight == nil) or (stackHeight() <= stepTargetHeight) then + breaker.setLineBreak(nil) + baseDepth = breaker.stackOffset.stepDebugLoop + startDebugLoop() + end +end + +------------------------------------------------------------------------------- +function handlers.next(req) + stepTargetHeight = stackHeight() - breaker.stackOffset.step + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.stepIn(req) + stepTargetHeight = nil + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.stepOut(req) + stepTargetHeight = stackHeight() - (breaker.stackOffset.step + 1) + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.evaluate(req) + -- 실행할 소스 코드 준비 + local sourceCode = req.arguments.expression + if string.sub(sourceCode, 1, 1) == '!' then + sourceCode = string.sub(sourceCode, 2) + else + sourceCode = 'return (' .. sourceCode .. ')' + end + + -- 환경 준비. + -- 뭘 요구할지 모르니까 로컬, 업밸류, 글로벌을 죄다 복사해둔다. + -- 우선순위는 글로벌-업밸류-로컬 순서니까 + -- 그 반대로 갖다놓아서 나중 것이 앞의 것을 덮어쓰게 한다. + local depth = req.arguments.frameId + local tempG = {} + local declared = {} + local function set(k, v) + tempG[k] = v + declared[k] = true + end + + for name, value in pairs(_G) do + set(name, value) + end + + if depth then + local info = debug_getinfo(depth, 'f') + if info and info.func then + for i = 1, 9999 do + local name, value = debug.getupvalue(info.func, i) + if name == nil then break end + set(name, value) + end + end + + for i = 1, 9999 do + local name, value = debug_getlocal(depth, i) + if name == nil then break end + set(name, value) + end + else + -- VSCode가 depth를 안 보낼 수도 있다. + -- 특정 스택 프레임을 선택하지 않은, 전역 이름만 조회하는 경우이다. + end + local mt = { + __newindex = function() error('assignment not allowed', 2) end, + __index = function(t, k) if not declared[k] then error('not declared', 2) end end + } + setmetatable(tempG, mt) + + -- 파싱 + -- loadstring for Lua 5.1 + -- load for Lua 5.2 and 5.3(supports the private environment's load function) + local fn, err = (loadstring or load)(sourceCode, 'X', nil, tempG) + if fn == nil then + sendFailure(req, string.gsub(err, '^%[string %"X%"%]%:%d+%: ', '')) + return + end + + -- 실행하고 결과 송신 + if setfenv ~= nil then + -- Only for Lua 5.1 + setfenv(fn, tempG) + end + + local success, aux = pcall(fn) + if not success then + aux = aux or '' -- Execution of 'error()' returns nil as aux + sendFailure(req, string.gsub(aux, '^%[string %"X%"%]%:%d+%: ', '')) + return + end + + local varNameCount = {} + local item = registerVar(varNameCount, '', aux) + + sendSuccess(req, { + result = item.value, + type = item.type, + variablesReference = item.variablesReference + }) +end + +------------------------------------------------------------------------------- +return debuggee -- 2.43.0 From 46ace58ab827e508a738e63fbe4362de810ed67c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 17 Oct 2023 21:37:33 -0400 Subject: [PATCH 099/117] Added function / connection --- docs/changes.md | 19 +++++++++++++++++++ init.lua | 15 +++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/changes.md b/docs/changes.md index c7436b4..f15b3b3 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -366,6 +366,25 @@ Added return cn end ``` +- Connection objects can be divided, function / connection + This is a mix between the behavior between mod and concat, where the original connection can forward it's events to the new one as well as do a check like concat can. View it's implementation below: + ```lua + __div = function(obj1, obj2) -- / + local cn = self:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + local args = {obj1(...)} + if args[1] then + cn:Fire(multi.unpack(args)) + end + end) + else + multi.error("Invalid divide! ", type(obj1), type(obj2)," Expected function/connection(table)") + end + return cn + end + ``` - Connection objects can now be concatenated with functions, not each other. For example: ```lua multi, thread = require("multi"):init{print=true,findopt=true} diff --git a/init.lua b/init.lua index 928237e..b01b569 100644 --- a/init.lua +++ b/init.lua @@ -225,6 +225,21 @@ function multi:newConnection(protect,func,kill) end return cn end, + __div = function(obj1, obj2) -- / + local cn = self:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + local args = {obj1(...)} + if args[1] then + cn:Fire(multi.unpack(args)) + end + end) + else + multi.error("Invalid divide!", type(obj1), type(obj2),"Expected function/connection(table)") + end + return cn + end, __concat = function(obj1, obj2) -- .. local cn = self:newConnection() local ref -- 2.43.0 From f5403969321e4ed063d54a0984a1c79d8764bd6b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 28 Oct 2023 17:57:21 -0400 Subject: [PATCH 100/117] Added boost method --- docs/changes.md | 1 + init.lua | 12 ++++- integration/pseudoManager/extensions.lua | 20 ++------ tests/test.lua | 62 +++++++++++++++--------- tests/threadtests.lua | 57 ++++------------------ 5 files changed, 63 insertions(+), 89 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index c7436b4..ba4d36d 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -84,6 +84,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function - thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. - shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. ```lua diff --git a/init.lua b/init.lua index 928237e..44914db 100644 --- a/init.lua +++ b/init.lua @@ -1063,7 +1063,9 @@ function multi:newProcessor(name, nothread, priority) c.parent = self c.OnObjectCreated = self:newConnection() + local boost = 1 local handler + if priority then handler = c:createPriorityHandler(c) else @@ -1113,10 +1115,16 @@ function multi:newProcessor(name, nothread, priority) end, holdme)() end + function c:boost(count) + boost = count or 1 + end + function c.run() if not Active then return end - c:uManager(true) - handler() + for i=1,boost do + c:uManager(true) + handler() + end return c end diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index fe0abd8..d21aebb 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -38,7 +38,7 @@ function multi:newSystemThreadedQueue(name) c.data = {} c.Type = multi.registerType("s_queue") function c:push(v) - table.insert(self,v) + table.insert(self.data,v) end function c:pop() return table.remove(self.data,1) @@ -163,7 +163,6 @@ function multi:newSystemThreadedJobQueue(n) local funcs = THREAD.waitFor("__JobQueue_"..jqc.."_table") local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue") local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() local queueAll = THREAD.waitFor("__JobQueue_"..jqc.."_queueAll") local registry = {} _G["__QR"] = queueReturn @@ -173,7 +172,6 @@ function multi:newSystemThreadedJobQueue(n) thread.yield() local all = queueAll:peek() if all and not registry[all[1]] then - lastProc = os.clock() queueAll:pop()[2]() end end @@ -184,13 +182,11 @@ function multi:newSystemThreadedJobQueue(n) thread.yield() local all = queueAll:peek() if all and not registry[all[1]] then - lastProc = os.clock() queueAll:pop()[2]() end local dat = thread.hold(queue) if dat then - multi:newThread("Test",function() - lastProc = os.clock() + multi:newThread("JobSubRunner",function() local name = table.remove(dat,1) local id = table.remove(dat,1) local tab = {multi.isolateFunction(funcs[name],_G)(multi.unpack(dat))} @@ -200,18 +196,8 @@ function multi:newSystemThreadedJobQueue(n) end end end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) multi:mainloop() - end,jqc) + end, jqc) end function c:Hold(opt) diff --git a/tests/test.lua b/tests/test.lua index f96eeb1..6a0479e 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,14 +1,19 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging=true} -require("multi.integration.priorityManager") +-- require("multi.integration.priorityManager") + +-- multi.debugging.OnObjectCreated(function(obj, process) +-- multi.print("Created:", obj.Type, "in", process.Type, process:getFullName()) +-- end) + +-- multi.debugging.OnObjectDestroyed(function(obj, process) +-- multi.print("Destroyed:", obj.Type, "in", process.Type, process:getFullName()) +-- end) + + + -multi.debugging.OnObjectCreated(function(obj, process) - multi.print("Created:", obj.Type, "in", process.Type, process:getFullName()) -end) -multi.debugging.OnObjectDestroyed(function(obj, process) - multi.print("Destroyed:", obj.Type, "in", process.Type, process:getFullName()) -end) -- test = multi:newProcessor("Test") -- test:setPriorityScheme(multi.priorityScheme.TimeBased) @@ -60,26 +65,37 @@ end) -- end) -- conn5:Fire() -multi.print("Testing thread:newProcessor()") -proc = thread:newProcessor("Test") -proc:newLoop(function() - multi.print("Running...") - thread.sleep(1) + + +-- multi.print("Testing thread:newProcessor()") + +-- proc = thread:newProcessor("Test") + +-- proc:newLoop(function() +-- multi.print("Running...") +-- thread.sleep(1) +-- end) + +-- proc:newThread(function() +-- while true do +-- multi.warn("Everything is a thread in this proc!") +-- thread.sleep(1) +-- end +-- end) + +-- proc:newAlarm(5):OnRing(function(a) +-- multi.print(";) Goodbye") +-- a:Destroy() +-- end) + +local func = thread:newFunction(function() + thread.sleep(4) + print("Hello!") end) -proc:newThread(function() - while true do - multi.warn("Everything is a thread in this proc!") - thread.sleep(1) - end -end) - -proc:newAlarm(5):OnRing(function(a) - multi.print(";) Goodbye") - a:Destroy() -end) +multi:newTLoop(func, 1) multi:mainloop() diff --git a/tests/threadtests.lua b/tests/threadtests.lua index f3298a2..0b91544 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -47,7 +47,9 @@ multi:newThread("Scheduler Thread",function() queue = multi:newSystemThreadedQueue("Test_Queue"):init() multi:newSystemThread("Test_Thread_0", function() - print("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) + if THREAD_NAME~="Test_Thread_0" then + multi.error("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) + end end) th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) @@ -97,7 +99,7 @@ multi:newThread("Scheduler Thread",function() end).OnError(multi.error) multi:newThread("test2",function() - print(thread.hold(function() return test["test2"] end)) + thread.hold(function() return test["test2"] end) worked = true end) @@ -114,7 +116,7 @@ multi:newThread("Scheduler Thread",function() local ready = false - jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads + jq = multi:newSystemThreadedJobQueue(1) -- Job queue with 4 worker threads func2 = jq:newFunction("sleep",function(a,b) THREAD.sleep(.2) end) @@ -131,7 +133,7 @@ multi:newThread("Scheduler Thread",function() t, val = thread.hold(function() return count == 10 - end,{sleep=2}) + end,{sleep=3}) if val == multi.TIMEOUT then multi.error("SystemThreadedJobQueues: Failed") @@ -140,46 +142,6 @@ multi:newThread("Scheduler Thread",function() multi.success("SystemThreadedJobQueues: Ok") - -- queue2 = multi:newSystemThreadedQueue("Test_Queue2"):init() - -- multi:newSystemThread("Test_Thread_2",function() - -- queue2 = THREAD.waitFor("Test_Queue2"):init() - -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - -- connOut(function(arg) - -- queue2:push("Test_Thread_2") - -- end) - -- multi:mainloop() - -- end).OnError(multi.error) - -- multi:newSystemThread("Test_Thread_3",function() - -- queue2 = THREAD.waitFor("Test_Queue2"):init() - -- connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - -- connOut(function(arg) - -- queue2:push("Test_Thread_3") - -- end) - -- multi:mainloop() - -- end).OnError(multi.error) - -- connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() - -- a=0 - -- connOut(function(arg) - -- queue2:push("Main") - -- end) - -- for i=1,3 do - -- thread.sleep(.1) - -- connOut:Fire("Test From Main Thread: "..i.."\n") - -- end - -- thread.sleep(2) - -- local count = 0 - -- multi:newThread(function() - -- while count < 9 do - -- if queue2:pop() then - -- count = count + 1 - -- end - -- end - -- end).OnError(multi.error) - -- _, err = thread.hold(function() return count == 9 end,{sleep=.3}) - -- if err == multi.TIMEOUT then - -- multi.error("SystemThreadedConnections: Failed") - -- end - -- multi.success("SystemThreadedConnections: Ok") local proxy_test = false local stp = multi:newSystemThreadedProcessor(5) @@ -213,11 +175,12 @@ multi:newThread("Scheduler Thread",function() multi.print("Held on proxy connection... once") thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... twice") + thread.hold(tloop.OnLoop) + multi.print("Held on proxy connection... finally") proxy_test = true end) thread:newThread(function() - print("While Test!") while true do thread.hold(tloop.OnLoop) print(THREAD_NAME,"Local Loopy") @@ -230,7 +193,7 @@ multi:newThread("Scheduler Thread",function() t, val = thread.hold(function() return proxy_test - end--[[,{sleep=5}]]) -- No timeouts + end,{sleep=10}) if val == multi.TIMEOUT then multi.error("SystemThreadedProcessor/Proxies: Failed") @@ -247,7 +210,7 @@ multi:newThread("Scheduler Thread",function() end) multi.OnExit(function(err_or_errorcode) - print("Error Code: ", err_or_errorcode) + multi.print("Error Code: ", err_or_errorcode) if not we_good then multi.print("There was an error running some tests!") return -- 2.43.0 From 9ad2c45b8f71284bad32ea1ddd8d5e580787ac50 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 1 Nov 2023 23:37:00 -0400 Subject: [PATCH 101/117] Document new features to conns, todo fix newTask --- docs/changes.md | 40 ++++++++++++++++++++++++++++++++++++++++ init.lua | 15 ++++++++++++++- tests/test.lua | 25 +++++++++++++++++++------ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index a959786..d638d78 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -367,6 +367,46 @@ Added return cn end ``` +- The len operator `#` will return the number of connections in the object! + ``` + local conn = multi:newConnection() + conn(function() print("Test 1") end) + conn(function() print("Test 2") end) + conn(function() print("Test 3") end) + conn(function() print("Test 4") end) + print(#conn) + ``` + Output: + ``` + 4 + ``` +- Connection objects can be negated -conn returns self so conn = -conn, reverses the order of connection events + ```lua + local conn = multi:newConnection() + conn(function() print("Test 1") end) + conn(function() print("Test 2") end) + conn(function() print("Test 3") end) + conn(function() print("Test 4") end) + + print("Fire 1") + conn:Fire() + conn = -conn + print("Fire 2") + conn:Fire() + ``` + Output: + ``` + Fire 1 + Test 1 + Test 2 + Test 3 + Test 4 + Fire 2 + Test 4 + Test 3 + Test 2 + Test 1 + ``` - Connection objects can be divided, function / connection This is a mix between the behavior between mod and concat, where the original connection can forward it's events to the new one as well as do a check like concat can. View it's implementation below: ```lua diff --git a/init.lua b/init.lua index 703bc4b..709ae0c 100644 --- a/init.lua +++ b/init.lua @@ -196,7 +196,8 @@ function multi:newConnection(protect,func,kill) c.rawadd = false c.Parent = self - setmetatable(c,{__call=function(self,...) + setmetatable(c,{ + __call=function(self,...) local t = ... if type(t)=="table" then for i,v in pairs(t) do @@ -214,6 +215,14 @@ function multi:newConnection(protect,func,kill) return self:Connect(...) end end, + __unm = function(obj) -- -obj Reverses the order of connected events + local conns = obj:Bind({}) + for i = #conns, 1, -1 do + obj.rawadd = true + obj(conns[i]) + end + return obj + end, __mod = function(obj1, obj2) -- % local cn = self:newConnection() if type(obj1) == "function" and type(obj2) == "table" then @@ -267,6 +276,8 @@ function multi:newConnection(protect,func,kill) end end) end + elseif type(obj1) == "table" and type(obj2) == "table" then + -- else error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") end @@ -391,6 +402,7 @@ function multi:newConnection(protect,func,kill) function c:Unconnect(conn) for i = 1, #fast do if fast[conn.ref] == fast[i] then + table.remove(self) return table.remove(fast, i), i end end @@ -466,6 +478,7 @@ function multi:newConnection(protect,func,kill) if self.rawadd then self.rawadd = false else + table.insert(self,true) self.__connectionAdded(temp, func) end return temp diff --git a/tests/test.lua b/tests/test.lua index 6a0479e..a9ec6e8 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -90,15 +90,28 @@ multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging= -- a:Destroy() -- end) -local func = thread:newFunction(function() - thread.sleep(4) - print("Hello!") -end) +-- local func = thread:newFunction(function() +-- thread.sleep(4) +-- print("Hello!") +-- end) -multi:newTLoop(func, 1) +-- multi:newTLoop(func, 1) -multi:mainloop() +-- multi:mainloop() +local conn = multi:newConnection() +conn(function() print("Test 1") end) +conn(function() print("Test 2") end) +conn(function() print("Test 3") end) +conn(function() print("Test 4") end) + +print("Fire 1") +conn:Fire() +conn = -conn +print("Fire 2") +conn:Fire() + +print(#conn) -- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() -- 2.43.0 From 0af807a930ef1bd7239076af1c8d9e3bd78481fa Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 2 Nov 2023 09:31:09 -0400 Subject: [PATCH 102/117] Fixed newTask() --- docs/changes.md | 2 ++ init.lua | 36 +++++++++++++++++++----------------- tests/test.lua | 7 +++++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index d638d78..e835fcd 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -523,6 +523,8 @@ Added Changed --- +- multi:newTask(task) is not tied to the processor it is created on. +- `multi:getTasks()` renamed to `multi:getRunners()`, should help with confusion between multi:newTask() - changed how multi adds unpack to the global namespace. Instead we capture that value into multi.unpack. - multi:newUpdater(skip, func) -- Now accepts func as the second argument. So you don't need to call OnUpdate(func) after creation. - multi errors now internally call `multi.error` instead of `multi.print` diff --git a/init.lua b/init.lua index 709ae0c..c0f75a6 100644 --- a/init.lua +++ b/init.lua @@ -1011,10 +1011,10 @@ function multi:newTStep(start,reset,count,set) return c end -local tasks = {} +multi.tasks = {} function multi:newTask(func) - tasks[#tasks + 1] = func + self.tasks[#self.tasks + 1] = func end local scheduledjobs = {} @@ -1086,6 +1086,7 @@ function multi:newProcessor(name, nothread, priority) c.Type = multi.registerType("process", "processes") local Active = nothread or false c.Name = name or "" + c.tasks = {} c.threads = {} c.startme = {} c.parent = self @@ -1174,6 +1175,20 @@ function multi:newProcessor(name, nothread, priority) Active = false c.process:Destroy() end + + c:newThread("Task Handler", function() + local self = multi:getCurrentProcess() + local function task_holder() + return #self.tasks > 0 + end + while true do + if #self.tasks > 0 then + table.remove(self.tasks,1)() + else + thread.hold(task_holder) + end + end + end).OnError(multi.error) table.insert(processes,c) self:create(c) @@ -1213,7 +1228,7 @@ function multi:getThreads() return threads end -function multi:getTasks() +function multi:getRunners() local tasks = {} for i,v in pairs(self.Mainloop) do if not v.__ignore then @@ -2503,6 +2518,7 @@ else end threadManager = multi:newProcessor("Global_Thread_Manager", nil, true).Start() +threadManager.tasks = multi.tasks -- The main multi interface is a bit different. function multi:getThreadManagerProcess() return threadManager @@ -2512,18 +2528,4 @@ function multi:getHandler() return threadManager:getHandler() end -local function task_holder() - return #tasks > 0 -end - -multi:newThread("Task Handler", function() - while true do - if #tasks > 0 then - table.remove(tasks)() - else - thread.hold(task_holder) - end - end -end).OnError(multi.error) - return multi \ No newline at end of file diff --git a/tests/test.lua b/tests/test.lua index a9ec6e8..c1b0192 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -98,6 +98,11 @@ multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging= -- multi:newTLoop(func, 1) -- multi:mainloop() +for i = 1, 100 do + multi:newTask(function() + print("Task "..i) + end) +end local conn = multi:newConnection() conn(function() print("Test 1") end) @@ -113,6 +118,8 @@ conn:Fire() print(#conn) +multi:mainloop() + -- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() -- local link = conn1(function() -- 2.43.0 From 30db370cc93dc72ce267207a2f8ec1bc8339a894 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 2 Nov 2023 23:59:34 -0400 Subject: [PATCH 103/117] Updated changes.md and fixed some bugs --- docs/changes.md | 1 + init.lua | 12 ++++++++++++ tests/test.lua | 13 ++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index e835fcd..b09d3d0 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -84,6 +84,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. - processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function - thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. - shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. diff --git a/init.lua b/init.lua index c0f75a6..9e70cfd 100644 --- a/init.lua +++ b/init.lua @@ -1085,6 +1085,7 @@ function multi:newProcessor(name, nothread, priority) c.Mainloop = {} c.Type = multi.registerType("process", "processes") local Active = nothread or false + local task_delay = 0 c.Name = name or "" c.tasks = {} c.threads = {} @@ -1176,6 +1177,10 @@ function multi:newProcessor(name, nothread, priority) c.process:Destroy() end + function c:setTaskDelay(delay) + task_delay = tonumber(delay) or 0 + end + c:newThread("Task Handler", function() local self = multi:getCurrentProcess() local function task_holder() @@ -1187,6 +1192,9 @@ function multi:newProcessor(name, nothread, priority) else thread.hold(task_holder) end + if task_delay~=0 then + thread.sleep(task_delay) + end end end).OnError(multi.error) @@ -2520,6 +2528,10 @@ end threadManager = multi:newProcessor("Global_Thread_Manager", nil, true).Start() threadManager.tasks = multi.tasks -- The main multi interface is a bit different. +function multi:setTaskDelay(delay) + threadManager:setTaskDelay(delay) +end + function multi:getThreadManagerProcess() return threadManager end diff --git a/tests/test.lua b/tests/test.lua index c1b0192..62c793f 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -98,11 +98,18 @@ multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging= -- multi:newTLoop(func, 1) -- multi:mainloop() -for i = 1, 100 do + +multi:setTaskDelay(.05) +multi:newTask(function() + for i = 1, 100 do + multi:newTask(function() + print("Task "..i) + end) + end multi:newTask(function() - print("Task "..i) + multi:Stop() end) -end +end) local conn = multi:newConnection() conn(function() print("Test 1") end) -- 2.43.0 From 8d843a59587d0e8b9da7598228e5d9dc689ab1a7 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 9 Nov 2023 22:04:11 -0500 Subject: [PATCH 104/117] Added thread.defer(func) --- docs/changes.md | 1 + init.lua | 8 ++++++++ tests/test.lua | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index b09d3d0..294a38c 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -84,6 +84,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- +- thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a function, when the function returns or errors. - multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. - processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function - thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. diff --git a/init.lua b/init.lua index 9e70cfd..f758de4 100644 --- a/init.lua +++ b/init.lua @@ -1250,6 +1250,14 @@ function thread.request(t,cmd,...) thread.requests[t.thread] = {cmd, multi.pack(...)} end +function thread.defer(func) + local th = thread.getRunningThread() + local conn = (th.OnError + th.OnDeath) + conn(function() + func(th) + end) +end + function thread.getRunningThread() local threads = globalThreads local t = coroutine.running() diff --git a/tests/test.lua b/tests/test.lua index 62c793f..d9cd152 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,5 +1,5 @@ package.path = "../?/init.lua;../?.lua;"..package.path -multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging=true} +multi, thread = require("multi"):init{print=true,warn=true,debugging=true} -- require("multi.integration.priorityManager") -- multi.debugging.OnObjectCreated(function(obj, process) @@ -101,14 +101,11 @@ multi, thread = require("multi"):init{print=true,warn=true,error=true,debugging= multi:setTaskDelay(.05) multi:newTask(function() - for i = 1, 100 do + for i = 1, 10 do multi:newTask(function() print("Task "..i) end) end - multi:newTask(function() - multi:Stop() - end) end) local conn = multi:newConnection() @@ -125,6 +122,17 @@ conn:Fire() print(#conn) +thread:newThread("Test thread", function() + print("Starting thread!") + thread.defer(function() -- Runs when the thread finishes execution + print("Clean up time!") + end) + --[[ + Do lot's of stuff + ]] + thread.sleep(3) +end) + multi:mainloop() -- local conn1, conn2, conn3 = multi:newConnection(nil,nil,true), multi:newConnection(), multi:newConnection() -- 2.43.0 From 91561f7f249c2d17e3a16f00b9275ccd673128bf Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 12:49:23 -0500 Subject: [PATCH 105/117] Fixed tests on lanes and pseudo threading, todo fix love2d threading --- docs/changes.md | 2 +- init.lua | 40 +++++++++++++++++++++------ integration/pseudoManager/init.lua | 1 - integration/sharedExtensions/init.lua | 8 ++---- tests/main.lua | 4 +-- tests/runtests.lua | 5 +++- tests/test.lua | 34 ++++++++++++++++++++++- tests/threadtests.lua | 10 +++---- 8 files changed, 78 insertions(+), 26 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 294a38c..0206192 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -85,7 +85,7 @@ Allows the user to have multi auto set priorities (Requires chronos). Also adds Added --- - thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a function, when the function returns or errors. -- multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. +- multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. `delay` can be a function as well and will be processed as if thread.hold was called. - processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function - thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. - shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. diff --git a/init.lua b/init.lua index f758de4..4959e2a 100644 --- a/init.lua +++ b/init.lua @@ -189,6 +189,7 @@ local optimization_stats = {} local ignoreconn = true local empty_func = function() end function multi:newConnection(protect,func,kill) + local processor = self local c={} local lock = false local fast = {} @@ -380,7 +381,7 @@ function multi:newConnection(protect,func,kill) end if kill then table.insert(kills,i) - multi:newTask(function() + processor:newTask(function() for _, k in pairs(kills) do table.remove(kills, _) table.remove(fast, k) @@ -418,7 +419,7 @@ function multi:newConnection(protect,func,kill) fast[i](...) if kill then table.insert(kills,i) - multi:newTask(function() + processor:newTask(function() for _, k in pairs(kills) do table.remove(kills, _) table.remove(fast, k) @@ -1147,14 +1148,29 @@ function multi:newProcessor(name, nothread, priority) function c:boost(count) boost = count or 1 + if boost > 1 then + self.run = function() + if not Active then return end + for i=1,boost do + c:uManager(true) + handler() + end + return c + end + else + self.run = function() + if not Active then return end + c:uManager(true) + handler() + return c + end + end end function c.run() if not Active then return end - for i=1,boost do - c:uManager(true) - handler() - end + c:uManager(true) + handler() return c end @@ -1178,7 +1194,11 @@ function multi:newProcessor(name, nothread, priority) end function c:setTaskDelay(delay) - task_delay = tonumber(delay) or 0 + if type(delay) == "function" then + task_delay = delay + else + task_delay = tonumber(delay) or 0 + end end c:newThread("Task Handler", function() @@ -1193,7 +1213,7 @@ function multi:newProcessor(name, nothread, priority) thread.hold(task_holder) end if task_delay~=0 then - thread.sleep(task_delay) + thread.hold(task_delay) end end end).OnError(multi.error) @@ -2481,12 +2501,14 @@ function multi.success(...) io.write("\x1b[92mSUCCESS:\x1b[0m " .. table.concat(t," ") .. "\n") end +-- Old things for compatability multi.GetType = multi.getType multi.IsPaused = multi.isPaused multi.IsActive = multi.isActive -multi.Reallocate = multi.Reallocate +multi.Reallocate = multi.reallocate multi.ConnectFinal = multi.connectFinal multi.ResetTime = multi.SetTime +multi.setTime = multi.SetTime multi.IsDone = multi.isDone multi.SetName = multi.setName diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index e1b7b03..31106e9 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -66,7 +66,6 @@ function multi:newSystemThread(name, func, ...) THREAD = THREAD, THREAD_NAME = tostring(name), __THREADNAME__ = tostring(name), - test = "testing", THREAD_ID = id, thread = thread, multi = multi, diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index a048b5f..8526954 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -60,6 +60,7 @@ function multi:newProxy(list) return res end if not(self.is_init) then + THREAD.sleep(.3) self.is_init = true local multi, thread = require("multi"):init() self.proxy_link = "PL" .. multi.randomString(12) @@ -92,8 +93,6 @@ function multi:newProxy(list) local sref = table.remove(data, 1) local ret - print(_G[list[0]], func) - if sref then ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} else @@ -120,6 +119,7 @@ function multi:newProxy(list) end) return self else + THREAD.sleep(.3) local function copy(obj) if type(obj) ~= 'table' then return obj end local res = {} @@ -145,7 +145,6 @@ function multi:newProxy(list) setmetatable(v[2],getmetatable(multi:newConnection())) else self[v] = thread:newFunction(function(self,...) - multi.print("Pushing: " .. v) if self == me then me.send:push({v, true, ...}) else @@ -192,9 +191,6 @@ function multi:newProxy(list) THREAD = multi.integration.THREAD end local proxy = THREAD.waitFor(self.proxy_link) - for i,v in pairs(proxy) do - print("proxy",i,v) - end proxy.funcs = self.funcs return proxy:init() end diff --git a/tests/main.lua b/tests/main.lua index a21d620..b23a884 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,6 +1,6 @@ package.path = "../?/init.lua;../?.lua;"..package.path --- require("runtests") --- require("threadtests") +require("runtests") +require("threadtests") -- Allows you to run "love tests" which runs the tests multi, thread = require("multi"):init() diff --git a/tests/runtests.lua b/tests/runtests.lua index 65a2f67..c62f04b 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -175,7 +175,7 @@ runTest = thread:newFunction(function() end if not love then local ec = 0 - if _VERSION == "5.1" then + if _VERSION == "Lua 5.1" then multi.print("Testing pseudo threading") _, str, ecc = os.execute("lua tests/threadtests.lua p") ec = ec + ecc @@ -185,14 +185,17 @@ runTest = thread:newFunction(function() if ec ~= 0 then os.exit(1) end + os.exit(0) else multi.print("Testing pseudo threading") ec = ec + os.execute("lua tests/threadtests.lua p") multi.print("Testing lanes threading") ec = ec + os.execute("lua tests/threadtests.lua l") + multi:Stop() if ec ~= 0 then os.exit(1) end + os.exit(0) end end end) diff --git a/tests/test.lua b/tests/test.lua index d9cd152..8ca374e 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,5 +1,8 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,debugging=true} +for i,v in pairs(thread) do + print(i,v) +end -- require("multi.integration.priorityManager") -- multi.debugging.OnObjectCreated(function(obj, process) @@ -266,4 +269,33 @@ multi:mainloop() -- multi:mainloop() -- end) --- multi:mainloop() \ No newline at end of file +-- multi:mainloop() +--[[ + newFunction function: 0x00fad170 + waitFor function: 0x00fad0c8 + request function: 0x00fa4f10 + newThread function: 0x00fad1b8 + --__threads table: 0x00fa4dc8 + defer function: 0x00fa4f98 + isThread function: 0x00facd40 + holdFor function: 0x00fa5058 + yield function: 0x00faccf8 + hold function: 0x00fa51a0 + chain function: 0x00fa5180 + __CORES 32 + newISOThread function: 0x00fad250 + newFunctionBase function: 0x00fad128 + requests table: 0x00fa4e68 + newProcessor function: 0x00fad190 + exec function: 0x00fa50e8 + pushStatus function: 0x00fad108 + kill function: 0x00faccd8 + get function: 0x00fad0a8 + set function: 0x00fad088 + getCores function: 0x00facd60 + skip function: 0x00faccb0 + --_Requests function: 0x00fa50a0 + getRunningThread function: 0x00fa4fb8 + holdWithin function: 0x00facc80 + sleep function: 0x00fa4df0 +]] \ No newline at end of file diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 0b91544..6a21cbc 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -146,7 +146,7 @@ multi:newThread("Scheduler Thread",function() local stp = multi:newSystemThreadedProcessor(5) local tloop = stp:newTLoop(function() - print("Test") + --print("Test") end, 1) multi:newSystemThread("Testing proxy copy THREAD",function(tloop) @@ -157,11 +157,11 @@ multi:newThread("Scheduler Thread",function() thread:newThread(function() while true do thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") + --print(THREAD_NAME,"Loopy") end end) tloop.OnLoop(function(a) - print(THREAD_NAME, "Got loop...") + --print(THREAD_NAME, "Got loop...") end) multi:mainloop() end, tloop:getTransferable()) @@ -183,12 +183,12 @@ multi:newThread("Scheduler Thread",function() thread:newThread(function() while true do thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Local Loopy") + --print(THREAD_NAME,"Local Loopy") end end) tloop.OnLoop(function() - print("OnLoop",THREAD_NAME) + --print("OnLoop",THREAD_NAME) end) t, val = thread.hold(function() -- 2.43.0 From 726f55e5c67af15a1fe18fd7100d5f8ef3ec7eda Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 12:59:54 -0500 Subject: [PATCH 106/117] Fixed paths --- tests/runtests.lua | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/tests/runtests.lua b/tests/runtests.lua index c62f04b..363c418 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,25 +1,5 @@ -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 = "../?/init.lua;../?.lua;"..package.path -end ---[[ - This file runs all tests. - Format: - Expected: - ... - ... - ... - Actual: - ... - ... - ... - - Each test that is ran should have a 5 second pause after the test is complete - 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. -]] +package.path = "?/init.lua;?.lua;"..package.path + local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true} local good = false local proc = multi:newProcessor("Test") -- 2.43.0 From 2b9f732d88ebf3b1754ecaa136f55f1a7783d1ae Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 13:10:16 -0500 Subject: [PATCH 107/117] Working on paths --- .github/workflows/nix_ci.yml | 2 +- tests/runtests.lua | 30 +++++++++--------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index 386cd50..34a6c6b 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.10' - name: Setup env run: | diff --git a/tests/runtests.lua b/tests/runtests.lua index 363c418..34ed212 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -155,28 +155,16 @@ runTest = thread:newFunction(function() end if not love then local ec = 0 - if _VERSION == "Lua 5.1" then - multi.print("Testing pseudo threading") - _, str, ecc = os.execute("lua tests/threadtests.lua p") - ec = ec + ecc - multi.print("Testing lanes threading") - _, str, ecc = os.execute("lua tests/threadtests.lua l") - ec = ec + ecc - if ec ~= 0 then - os.exit(1) - end - os.exit(0) - else - multi.print("Testing pseudo threading") - ec = ec + os.execute("lua tests/threadtests.lua p") - multi.print("Testing lanes threading") - ec = ec + os.execute("lua tests/threadtests.lua l") - multi:Stop() - if ec ~= 0 then - os.exit(1) - end - os.exit(0) + multi.print("Testing pseudo threading") + _, str, ecc = os.execute("lua multi/tests/threadtests.lua p") + ec = ec + ecc + multi.print("Testing lanes threading") + _, str, ecc = os.execute("lua multi/tests/threadtests.lua l") + ec = ec + ecc + if ec ~= 0 then + os.exit(1) end + os.exit(0) end end) -- 2.43.0 From 5ac17f8e59a2bfa89d7c43f41168802e9d78300e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 13:14:02 -0500 Subject: [PATCH 108/117] Testing paths --- tests/runtests.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/runtests.lua b/tests/runtests.lua index 34ed212..db7eb4e 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -1,4 +1,4 @@ -package.path = "?/init.lua;?.lua;"..package.path +package.path = "../?/init.lua;../?.lua;./init.lua;./?.lua;"..package.path local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true} local good = false @@ -156,10 +156,10 @@ runTest = thread:newFunction(function() if not love then local ec = 0 multi.print("Testing pseudo threading") - _, str, ecc = os.execute("lua multi/tests/threadtests.lua p") + _, str, ecc = os.execute("lua tests/threadtests.lua p") ec = ec + ecc multi.print("Testing lanes threading") - _, str, ecc = os.execute("lua multi/tests/threadtests.lua l") + _, str, ecc = os.execute("lua tests/threadtests.lua l") ec = ec + ecc if ec ~= 0 then os.exit(1) -- 2.43.0 From b7a7e5bf169ea5931a81aa38a481e45d78d934cd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 20:57:59 -0500 Subject: [PATCH 109/117] Add test for rockspec --- .github/workflows/nix_ci.yml | 1 + rockspecs/multi-16.0-0.rockspec | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index 34a6c6b..905c85b 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -33,6 +33,7 @@ jobs: run: | source ${{github.workspace}}/lua-pkg/bin/activate luarocks install lanes + luarocks install rockspecs/multi-16.0-0.rockspec - name: Run Tests run: | diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec index 06d3b3e..5e50994 100644 --- a/rockspecs/multi-16.0-0.rockspec +++ b/rockspecs/multi-16.0-0.rockspec @@ -26,6 +26,7 @@ build = { ["multi.integration.loveManager"] = "integration/loveManager/init.lua", ["multi.integration.loveManager.extensions"] = "integration/loveManager/extensions.lua", ["multi.integration.loveManager.threads"] = "integration/loveManager/threads.lua", + ["multi.integration.loveManager.utils"] = "integration/loveManager/threads.lua", --["multi.integration.lovrManager"] = "integration/lovrManager/init.lua", --["multi.integration.lovrManager.extensions"] = "integration/lovrManager/extensions.lua", --["multi.integration.lovrManager.threads"] = "integration/lovrManager/threads.lua", -- 2.43.0 From ea6503d7b2e7e2cf4ca2aaf9f75dae28c306831d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 21:10:37 -0500 Subject: [PATCH 110/117] Fixed issues --- .github/workflows/nix_ci.yml | 2 +- rockspecs/multi-16.0-0.rockspec | 1 + tests/runtests.lua | 19 +++++++++++++------ tests/threadtests.lua | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index 905c85b..fd6b9f0 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -29,7 +29,7 @@ jobs: pip install hererocks hererocks lua-pkg --${{ matrix.lua }} -rlatest - - name: Install lanes + - name: Install lanes and multi run: | source ${{github.workspace}}/lua-pkg/bin/activate luarocks install lanes diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec index 5e50994..818d8b7 100644 --- a/rockspecs/multi-16.0-0.rockspec +++ b/rockspecs/multi-16.0-0.rockspec @@ -35,6 +35,7 @@ build = { ["multi.integration.pseudoManager.threads"] = "integration/pseudoManager/threads.lua", ["multi.integration.luvitManager"] = "integration/luvitManager.lua", ["multi.integration.threading"] = "integration/threading.lua", + ["multi.integration.sharedExtension"] = "integration/sharedExtension/init.lua", --["multi.integration.networkManager"] = "integration/networkManager.lua", } } \ No newline at end of file diff --git a/tests/runtests.lua b/tests/runtests.lua index db7eb4e..c5b7c13 100644 --- a/tests/runtests.lua +++ b/tests/runtests.lua @@ -156,13 +156,20 @@ runTest = thread:newFunction(function() if not love then local ec = 0 multi.print("Testing pseudo threading") - _, str, ecc = os.execute("lua tests/threadtests.lua p") - ec = ec + ecc - multi.print("Testing lanes threading") - _, str, ecc = os.execute("lua tests/threadtests.lua l") - ec = ec + ecc - if ec ~= 0 then + capture = io.popen("lua tests/threadtests.lua p"):read("*a") + if capture:lower():match("error") then + ec = ec + 1 os.exit(1) + else + io.write(capture) + end + multi.print("Testing lanes threading") + capture = io.popen("lua tests/threadtests.lua l"):read("*a") + if capture:lower():match("error") then + ec = ec + 1 + os.exit(1) + else + io.write(capture) end os.exit(0) end diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 6a21cbc..05dc643 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -210,7 +210,7 @@ multi:newThread("Scheduler Thread",function() end) multi.OnExit(function(err_or_errorcode) - multi.print("Error Code: ", err_or_errorcode) + multi.print("EC: ", err_or_errorcode) if not we_good then multi.print("There was an error running some tests!") return -- 2.43.0 From f07b1ebb5764e05e30b5785fac0797004d652deb Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 21:12:44 -0500 Subject: [PATCH 111/117] Fixed typo --- rockspecs/multi-16.0-0.rockspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec index 818d8b7..80d91f9 100644 --- a/rockspecs/multi-16.0-0.rockspec +++ b/rockspecs/multi-16.0-0.rockspec @@ -35,7 +35,7 @@ build = { ["multi.integration.pseudoManager.threads"] = "integration/pseudoManager/threads.lua", ["multi.integration.luvitManager"] = "integration/luvitManager.lua", ["multi.integration.threading"] = "integration/threading.lua", - ["multi.integration.sharedExtension"] = "integration/sharedExtension/init.lua", + ["multi.integration.sharedExtensions"] = "integration/sharedExtensions/init.lua", --["multi.integration.networkManager"] = "integration/networkManager.lua", } } \ No newline at end of file -- 2.43.0 From 42b4e0fcf41afb048dbf54e4e28c5af6c2313d05 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 26 Nov 2023 22:13:18 -0500 Subject: [PATCH 112/117] Added test for defer --- init.lua | 3 ++- integration/lanesManager/threads.lua | 14 ++++++++++++-- integration/loveManager/threads.lua | 4 ++++ integration/pseudoManager/threads.lua | 2 ++ tests/threadtests.lua | 19 +++++++++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/init.lua b/init.lua index 4959e2a..10ba6d5 100644 --- a/init.lua +++ b/init.lua @@ -1340,7 +1340,7 @@ end function thread.hold(n, opt) thread._Requests() local opt = opt or {} - if type(opt)=="table" then + if type(opt)=="table" and type(n) == "function" then interval = opt.interval if opt.cycles then return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) @@ -1350,6 +1350,7 @@ function thread.hold(n, opt) 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) diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 6db9b79..2c416ea 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -137,9 +137,19 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end end + function THREAD.defer(func) + local m = {onexit = function() func() end} + if _VERSION >= "Lua 5.2" then + setmetatable(m, {__gc = m.onexit}) + else + m.sentinel = newproxy(true) + getmetatable(m.sentinel).__gc = m.onexit + end + end + return GLOBAL, THREAD end -return {init = function(g,s,st,c) - return INIT(g,s,st,c) +return {init = function(g,s,st,c,onexit) + return INIT(g,s,st,c,onexit) end} \ No newline at end of file diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index d3cec2f..82d12f1 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -152,6 +152,10 @@ function INIT() end end + function THREAD.defer(func) + multi.OnExit(func) + end + return GLOBAL, THREAD end diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 2b46982..91a1667 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -88,6 +88,8 @@ local function INIT(thread) THREAD.sleep = thread.sleep THREAD.hold = thread.hold + + THREAD.defer = thread.defer function THREAD.setENV(env, name) name = name or "__env" diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 05dc643..3f62321 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -44,13 +44,32 @@ multi:newThread("Scheduler Thread",function() -- multi:Stop() -- os.exit(1) -- end) + queue = multi:newSystemThreadedQueue("Test_Queue"):init() + defer_queue = multi:newSystemThreadedQueue("Defer_Queue"):init() multi:newSystemThread("Test_Thread_0", function() + defer_queue = THREAD.waitFor("Defer_Queue"):init() + + THREAD.defer(function() + defer_queue:push("done") + multi.print("This function was defered until the end of the threads life") + end) + + multi.print("Testing defer, should print below this") + if THREAD_NAME~="Test_Thread_0" then multi.error("The name should be Test_Thread_0",THREAD_NAME,THREAD_NAME,_G.THREAD_NAME) end end) + + thread:newThread(function() + if thread.hold(function() + return defer_queue:pop() == "done" + end,{sleep=1}) == nil then + multi.error("Thread.defer didn't work!") + end + end) th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() -- 2.43.0 From e3ac0951abb2af9a0ef16f20f6974691f089e076 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 24 Feb 2024 23:05:05 -0500 Subject: [PATCH 113/117] Threading working in love2d --- init.lua | 25 +- integration/lanesManager/init.lua | 19 +- integration/lanesManager/threads.lua | 12 +- integration/loveManager/extensions.lua | 9 +- integration/loveManager/init.lua | 24 +- integration/loveManager/threads.lua | 96 +++++- integration/loveManager/utils.lua | 58 ---- integration/loveManagerold/extensions.lua | 389 ---------------------- integration/loveManagerold/init.lua | 138 -------- integration/loveManagerold/threads.lua | 257 -------------- integration/pseudoManager/extensions.lua | 5 +- integration/pseudoManager/init.lua | 4 +- integration/pseudoManager/threads.lua | 4 + integration/sharedExtensions/init.lua | 10 +- tests/conf.lua | 23 +- tests/main.lua | 14 +- tests/test.lua | 69 ++-- tests/threadtests.lua | 53 +-- 18 files changed, 210 insertions(+), 999 deletions(-) delete mode 100644 integration/loveManager/utils.lua delete mode 100644 integration/loveManagerold/extensions.lua delete mode 100644 integration/loveManagerold/init.lua delete mode 100644 integration/loveManagerold/threads.lua diff --git a/init.lua b/init.lua index 10ba6d5..f93a1ae 100644 --- a/init.lua +++ b/init.lua @@ -2152,8 +2152,7 @@ local init = false multi.settingsHook = multi:newConnection() 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 @@ -2164,18 +2163,22 @@ function multi.init(settings, realsettings) multi.mainloop = multi.mainloopRef end - if settings.findopt then - find_optimization = true - doOpt() - multi.enableOptimization:Fire(multi, thread) - end + if not init then + + if settings.findopt then + find_optimization = true + doOpt() + multi.enableOptimization:Fire(multi, thread) + end - if settings.debugging then - require("multi.integration.debugManager") - end + if settings.debugging then + require("multi.integration.debugManager") + end - multi.settingsHook:Fire(settings) + multi.settingsHook:Fire(settings) + end end + init = true return _G["$multi"].multi,_G["$multi"].thread end diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index 82d5610..f9d0848 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -85,7 +85,8 @@ function multi:newSystemThread(name, func, ...) THREAD_ID = count, THREAD = THREAD, GLOBAL = GLOBAL, - _Console = __ConsoleLinda + _Console = __ConsoleLinda, + _DEFER = {} } if GLOBAL["__env"] then for i,v in pairs(GLOBAL["__env"]) do @@ -97,26 +98,16 @@ function multi:newSystemThread(name, func, ...) globals = globe, priority = c.priority },function(...) - local profi - - if multi_settings.debug then - profi = require("proFI") - profi:start() - end - multi, thread = require("multi"):init(multi_settings) require("multi.integration.lanesManager.extensions") require("multi.integration.sharedExtensions") local has_error = true returns = {pcall(func, ...)} return_linda:set("returns", returns) - has_error = false - if profi then - multi.OnExit(function(...) - profi:stop() - profi:writeReport("Profiling Report [".. THREAD_NAME .."].txt") - end) + for i,v in pairs(_DEFER) do + pcall(v) end + has_error = false end)(...) count = count + 1 function c:getName() diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 2c416ea..2dc231c 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -88,6 +88,10 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) function THREAD.kill() -- trigger the lane destruction error("Thread was killed!\1") end + + function THREAD.sync() + -- Maybe do something... + end function THREAD.pushStatus(...) local args = multi.pack(...) @@ -138,13 +142,7 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end function THREAD.defer(func) - local m = {onexit = function() func() end} - if _VERSION >= "Lua 5.2" then - setmetatable(m, {__gc = m.onexit}) - else - m.sentinel = newproxy(true) - getmetatable(m.sentinel).__gc = m.onexit - end + table.insert(_DEFER, func) end return GLOBAL, THREAD diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index 9a22c5b..ee29baf 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -11,25 +11,27 @@ function multi:newSystemThreadedQueue(name) c.Name = name c.Type = multi.registerType("s_queue") - c.chan = love.thread.newChannel() + c.chan = love.thread.getChannel(name) function c:push(dat) self.chan:push(THREAD.packValue(dat)) end function c:pop() - return THREAD.unpackValue(self.chan:pop()) + return THREAD.unpackValue(self.chan:pop()) end function c:peek() - return THREAD.unpackValue(self.chan:peek()) + return THREAD.unpackValue(self.chan:peek()) end function c:init() + self.chan = love.thread.getChannel(self.Name) return self end function c:Hold(opt) + local multi, thread = require("multi"):init() if opt.peek then return thread.hold(function() return self:peek() @@ -73,6 +75,7 @@ function multi:newSystemThreadedTable(name) c.__init = c.init function c:Hold(opt) + local multi, thread = require("multi"):init() if opt.key then return thread.hold(function() return self.tab[opt.key] diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 0bc062b..44ec815 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -5,11 +5,13 @@ end local ThreadFileData = [[ ISTHREAD = true args = {...} -THREAD_ID = table.remove(args, 1) -THREAD_NAME = table.remove(args, 1) -GLOBAL, THREAD = require("multi.integration.loveManager.threads"):init() -__FUNC = THREAD.unpackValue(table.remove(args, 1)) -ARGS = THREAD.unpackValue(table.remove(args, 1)) +THREAD_ID = args[1] +THREAD_NAME = args[2] +GLOBAL, THREAD, DEFER = require("multi.integration.loveManager.threads"):init() +__FUNC = THREAD.unpackValue(args[3]) +ARGS = THREAD.unpackValue(args[4]) +settings = args[5] +if ARGS == nil then ARGS = {} end math.randomseed(THREAD_ID) math.random() math.random() @@ -21,10 +23,16 @@ if GLOBAL["__env"] then _G[i] = v end end -multi, thread = require("multi"):init() +multi, thread = require("multi"):init{error=true, warning=true, print=true, priority=true} +multi.defaultSettings.print = true require("multi.integration.loveManager.extensions") require("multi.integration.sharedExtensions") -stab["returns"] = {__FUNC(multi.unpack(ARGS))} +local returns = {pcall(__FUNC, multi.unpack(ARGS))} +table.remove(returns,1) +stab["returns"] = returns +for i,v in pairs(DEFER) do + pcall(v) +end ]] _G.THREAD_NAME = "MAIN_THREAD" @@ -49,7 +57,7 @@ function multi:newSystemThread(name, func, ...) c.Name = name c.ID = tid c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(c.ID, c.Name, THREAD.packValue(func), THREAD.packValue({...})) + c.thread:start(c.ID, c.Name, THREAD.packValue(func), THREAD.packValue({...}), multi.defaultSettings) c.stab = THREAD.createTable(name .. c.ID) c.creationTime = os.clock() c.OnDeath = multi:newConnection() diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 82d12f1..4ad7474 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -25,19 +25,76 @@ require("love.timer") require("love.system") require("love.data") require("love.thread") -local utils = require("multi.integration.loveManager.utils") local multi, thread = require("multi"):init() -local NIL = love.data.newByteData("\3") - --- If a non table/function is supplied we just return it -local function packValue(t) - return utils.pack(t) +-- Checks if the given value is a LOVE2D object (i.e. has metatable with __index field) and if that __index field contains functions typical of LOVE2D objects +function isLoveObject(value) + -- Check if the value has metatable + if type(value) == "userdata" and getmetatable(value) then + -- Check if the metatable has the __index field + local index = getmetatable(value).__index + if type(index) == "table" then + -- Check if the metatable's __index table contains functions typical of LOVE2D objects + if index.draw or index.update or index.getWidth or index.getHeight or index.getString or index.getPointer then + return true + end + end + end + return false end --- If a non table/function is supplied we just return it -local function unpackValue(d) - return utils.unpack(d) +-- Converts any function values in a table to a string with the value "\1\2:func:" where is the Lua stringified version of the function +function tableToFunctionString(t) + if type(t) == "nil" then return "\1\2:nil:" end + if type(t) == "function" then return "\1\2:func:"..string.dump(t) end + if type(t) ~= "table" then return t end + local newtable = {} + for k, v in pairs(t) do + if type(v) == "function" then + newtable[k] = "\1\2:func:"..string.dump(v) + elseif type(v) == "table" then + newtable[k] = tableToFunctionString(v) + elseif isLoveObject(v) then + newtable[k] = v + elseif type(v) == "userdata" then + newtable[k] = tostring(v) + else + newtable[k] = v + end + end + return newtable +end + +-- Converts strings with the value "\1\2:func:" back to functions +function functionStringToTable(t) + if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end + if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end + if type(t) ~= "table" then return t end + for k, v in pairs(t) do + if type(v) == "string" then + if v:sub(1, 8) == "\1\2:func:" then + t[k] = loadstring(v:sub(9, -1)) + else + t[k] = v + end + elseif type(v) == "table" then + t[k] = functionStringToTable(v) + else + t[k] = v + end + end + if t.init then + t:init() + end + return t +end + +local function packValue(t) + return tableToFunctionString(t) +end + +local function unpackValue(t) + return functionStringToTable(t) end local function createTable(n) @@ -53,6 +110,11 @@ local function createTable(n) end local function get(name) return unpackValue(love.thread.getChannel(n .. name):peek()) + -- if type(data) == "table" and data.init then + -- return data:init() + -- else + -- return data + -- end end return setmetatable({}, { @@ -67,7 +129,7 @@ local function createTable(n) end function INIT() - local GLOBAL, THREAD = createTable("__GLOBAL__"), {} + local GLOBAL, THREAD, DEFER = createTable("__GLOBAL__"), {}, {} local status_channel, console_channel = love.thread.getChannel("__status_channel__" .. THREAD_ID), love.thread.getChannel("__console_channel__") @@ -92,11 +154,7 @@ function INIT() repeat wait() until GLOBAL[name] ~= nil - if type(GLOBAL[name].__init) == "function" then - return GLOBAL[name]:__init() - else - return GLOBAL[name] - end + return GLOBAL[name] end, true) function THREAD.getCores() @@ -153,10 +211,14 @@ function INIT() end function THREAD.defer(func) - multi.OnExit(func) + table.insert(DEFER, func) end - return GLOBAL, THREAD + function THREAD.sync() + -- Maybe do something... + end + + return GLOBAL, THREAD, DEFER end return { diff --git a/integration/loveManager/utils.lua b/integration/loveManager/utils.lua deleted file mode 100644 index 46a9a91..0000000 --- a/integration/loveManager/utils.lua +++ /dev/null @@ -1,58 +0,0 @@ -require("love.data") -local utils = {} -local NIL = {Type="nil"} - ---love.data.newByteData("\2"..serpent.dump({t,true},{safe = true})) - -local ltype = function(v) return v:type() end -local t = function(value) - local v = type(value) - if v == "userdata" then - local status, return_or_err = pcall(ltype, value) - if status then return return_or_err else return "userdata" end - else return v end -end - -function utils.pack(tbl, seen) - if type(tbl) == "function" then return {["__$FUNC$__"] = love.data.newByteData(string.dump(tbl))} end - if type(tbl) ~= "table" then return tbl end - local seen = seen or {} - local result = {} - result.__isPacked = true - for i,v in pairs(tbl) do - if seen[v] then - result[i] = v - elseif t(v) == "table" then - seen[v] = true - result[i] = utils.pack(v, seen) - elseif t(v) == "function" then - result["$F"..i] = love.data.newByteData(string.dump(v)) - elseif t{v} == "userdata" then - result[i] = "userdata" - else -- Handle what we need to and pass the rest along as a value - result[i] = v - end - end - return result -end - -function utils.unpack(tbl) - if not tbl then return nil end - if type(tbl) ~= "table" then return tbl end - if tbl["__$FUNC$__"] then return loadstring(tbl["__$FUNC$__"]:getString()) end - for i,v in pairs(tbl) do - if type(i) == "string" and i:sub(1,2) == "$F" then - local rawfunc = v:getString() - v:release() - tbl[i] = nil - tbl[i:sub(3,-1)] = loadstring(rawfunc) - end - if type(v) == "table" then - utils.unpack(v) - end - end - tbl.__isPacked = nil - return tbl -end - -return utils \ No newline at end of file diff --git a/integration/loveManagerold/extensions.lua b/integration/loveManagerold/extensions.lua deleted file mode 100644 index 91f5819..0000000 --- a/integration/loveManagerold/extensions.lua +++ /dev/null @@ -1,389 +0,0 @@ ---[[ -MIT License - -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 -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 not ISTHREAD then - multi, thread = require("multi").init() - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -else - GLOBAL = multi.integration.GLOBAL - THREAD = multi.integration.THREAD -end - -function multi:newSystemThreadedQueue(name) - local name = name or multi.randomString(16) - local c = {} - c.Name = name - c.Type = multi.SQUEUE - local fRef = {"\2",nil} - function c:init() - local q = {} - q.chan = love.thread.getChannel(self.Name) - function q:push(dat) - if type(dat) == "table" then - self.chan:push{"DATA",THREAD.packTable(dat)} - else - self.chan:push(dat) - end - -- if type(dat) == "function" then - -- fRef[2] = THREAD.dump(dat) - -- self.chan:push(fRef) - -- return - -- else - -- self.chan:push(dat) - -- end - end - function q:pop() - local dat = self.chan:pop() - if type(dat)=="table" and dat[1]=="DATA" then - return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) - else - return dat - end - end - function q:peek() - local dat = self.chan:peek() - if type(dat)=="table" and dat[1]=="DATA" then - return THREAD.unpackTable(dat[2])--THREAD.loadDump(dat[2]) - else - return dat - end - end - return q - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -function multi:newSystemThreadedTable(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Name = name - c.Type = multi.STABLE - - function c:init() - return THREAD.createTable(self.Name) - end - - THREAD.package(name,c) - - self:create(c) - - return c -end - -local jqc = 1 -function multi:newSystemThreadedJobQueue(n) - local c = {} - - c.cores = n or THREAD.getCores() - c.registerQueue = {} - c.Type = multi.SJOBQUEUE - c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - c.id = 0 - c.OnJobCompleted = multi:newConnection() - - local allfunc = 0 - - function c:doToAll(func) - local f = THREAD.dump(func) - for i = 1, self.cores do - self.queueAll:push({allfunc,f}) - end - allfunc = allfunc + 1 - end - function c:registerFunction(name,func) - if self.funcs[name] then - error("A function by the name "..name.." has already been registered!") - end - self.funcs[name] = func - end - function c:pushJob(name,...) - self.id = self.id + 1 - self.queue:push{name,self.id,...} - return self.id - end - function c:isEmpty() - return queueJob:peek()==nil - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = multi.pack(...) - end - end) - return thread.hold(function() - if rets then - return multi.unpack(rets) or multi.NIL - end - end) - end,holup),name - end - thread:newThread("jobManager",function() - while true do - thread.yield() - local dat = c.queueReturn:pop() - if dat then - c.OnJobCompleted:Fire(multi.unpack(dat)) - end - end - end) - for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) - local multi, thread = require("multi"):init() - require("love.timer") - local function atomic(channel) - return channel:pop() - end - local clock = os.clock - local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") - local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() - local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - local registry = {} - _G["__QR"] = queueReturn - setmetatable(_G,{__index = funcs}) - thread:newThread("startUp",function() - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - end - end) - thread:newThread("runner",function() - thread.sleep(.1) - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - local dat = queue:performAtomic(atomic) - if dat then - multi:newThread("Test",function() - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](multi.unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) - end) - end - end - end):OnError(function(...) - error(...) - end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) - multi:mainloop() - end,jqc) - end - - jqc = jqc + 1 - - self:create(c) - - return c -end - -function multi:newSystemThreadedConnection(name) - local name = name or multi.randomString(16) - - local c = {} - - c.Type = multi.SCONNECTION - c.CONN = 0x00 - c.TRIG = 0x01 - c.PING = 0x02 - c.PONG = 0x03 - - local subscribe = love.thread.getChannel("SUB_STC_" .. name) - - function c:init() - - self.subscribe = love.thread.getChannel("SUB_STC_" .. self.Name) - - function self:Fire(...) - local args = multi.pack(...) - if self.CID == THREAD_ID then -- Host Call - for _, link in pairs(self.links) do - love.thread.getChannel(link):push{self.TRIG, args} - end - self.proxy_conn:Fire(...) - else - self.subscribe:push{self.TRIG, args} - end - end - - local multi, thread = require("multi"):init() - self.links = {} - self.proxy_conn = multi:newConnection() - local mt = getmetatable(self.proxy_conn) - setmetatable(self, {__index = self.proxy_conn, __call = function(t,func) self.proxy_conn(func) end, __add = mt.__add}) - if self.CID == THREAD_ID then return self end - thread:newThread("STC_CONN_MAN" .. self.Name,function() - local item - local string_self_ref = "LSF_" .. multi.randomString(16) - local link_self_ref = love.thread.getChannel(string_self_ref) - self.subscribe:push{self.CONN, string_self_ref} - while true do - item = thread.hold(function() - return link_self_ref:peek() - end) - if item[1] == self.PING then - link_self_ref:push{self.PONG} - link_self_ref:pop() - elseif item[1] == self.CONN then - if string_self_ref ~= item[2] then - table.insert(self.links, love.thread.getChannel(item[2])) - end - link_self_ref:pop() - elseif item[1] == self.TRIG then - self.proxy_conn:Fire(multi.unpack(item[2])) - link_self_ref:pop() - else - -- This shouldn't be the case - end - end - end) - return self - end - - local function remove(a, b) - local ai = {} - local r = {} - for k,v in pairs(a) do ai[v]=true end - for k,v in pairs(b) do - if ai[v]==nil then table.insert(r,a[k]) end - end - return r - end - - c.CID = THREAD_ID - c.Name = name - c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out. - - -- Locals will only live in the thread that creates the original object - local ping - local pong = function(link, links) - local res = thread.hold(function() - return love.thread.getChannel(link):peek()[1] == c.PONG - end,{sleep=3}) - - if not res then - for i=1,#links do - if links[i] == link then - table.remove(links,i,link) - break - end - end - else - love.thread.getChannel(link):pop() - end - end - - ping = thread:newFunction(function(self) - ping:Pause() - - multi.ForEach(self.links, function(link) -- Sync new connections - love.thread.getChannel(link):push{self.PING} - multi:newThread("pong Thread", pong, link, self.links) - end) - - thread.sleep(3) - - ping:Resume() - end, false) - - local function fire(...) - for _, link in pairs(c.links) do - love.thread.getChannel(link):push {c.TRIG, multi.pack(...)} - end - end - - thread:newThread("STC_SUB_MAN"..name,function() - local item - while true do - thread.yield() - -- We need to check on broken connections - ping(c) -- Should return instantlly and process this in another thread - item = thread.hold(function() -- This will keep things held up until there is something to process - return c.subscribe:pop() - end) - if item[1] == c.CONN then - - multi.ForEach(c.links, function(link) -- Sync new connections - love.thread.getChannel(item[2]):push{c.CONN, link} - end) - c.links[#c.links+1] = item[2] - - elseif item[1] == c.TRIG then - fire(multi.unpack(item[2])) - c.proxy_conn:Fire(multi.unpack(item[2])) - end - end - end) - --- ^^^ This will only exist in the init thread - - THREAD.package(name,c) - - self:create(c) - - return c -end -require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/loveManagerold/init.lua b/integration/loveManagerold/init.lua deleted file mode 100644 index 2ab3061..0000000 --- a/integration/loveManagerold/init.lua +++ /dev/null @@ -1,138 +0,0 @@ ---[[ -MIT License - -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 -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 ISTHREAD then - error("You cannot require the loveManager from within a thread!") -end - -local ThreadFileData = [[ -ISTHREAD = true -THREAD = require("multi.integration.loveManager.threads") -sThread = THREAD -__IMPORTS = {...} -__FUNC__=table.remove(__IMPORTS,1) -THREAD_ID=table.remove(__IMPORTS,1) -THREAD_NAME=table.remove(__IMPORTS,1) -math.randomseed(THREAD_ID) -math.random() -math.random() -math.random() -stab = THREAD.createStaticTable(THREAD_NAME .. THREAD_ID) -GLOBAL = THREAD.getGlobal() -if GLOBAL["__env"] then - local env = THREAD.unpackENV(GLOBAL["__env"]) - for i,v in pairs(env) do - _G[i] = v - end -end -multi, thread = require("multi").init() -multi.integration={} -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -pcall(require,"multi.integration.loveManager.extensions") -pcall(require,"multi.integration.sharedExtensions") -stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} -]] - -local multi, thread = require("multi"):init() - -local THREAD = {} -_G.THREAD_NAME = "MAIN_THREAD" -_G.THREAD_ID = 0 -multi.integration = {} -local THREAD = require("multi.integration.loveManager.threads") -local GLOBAL = THREAD.getGlobal() -multi.isMainThread = true - -function multi:newSystemThread(name, func, ...) - THREAD_ID = THREAD_ID + 1 - local c = {} - c.Type = multi.STHREAD - c.name = name - c.ID = THREAD_ID - c.thread = love.thread.newThread(ThreadFileData) - c.thread:start(THREAD.dump(func), c.ID, c.name, ...) - c.stab = THREAD.createStaticTable(name .. c.ID) - 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 - - function c:getName() return c.name end - thread:newThread(name .. "_System_Thread_Handler",function() - if name == "SystemThreaded Function Handler" then - local status_channel = love.thread.getChannel("STATCHAN_" .. c.ID) - thread.hold(function() - -- While the thread is running we might as well do something in the loop - if status_channel:peek() ~= nil then - c.statusconnector:Fire(multi.unpack(status_channel:pop())) - 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(multi.unpack(c.stab.returns)) - c.stab.returns = nil - end - end) - - c.OnError(multi.error) - - if self.isActor then - self:create(c) - else - multi.create(multi, c) - end - - return c -end - -function THREAD:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return multi:newSystemThread("SystemThreaded Function Handler", func, ...) - end, holdme, multi.SFUNCTION)() -end - -THREAD.newSystemThread = function(...) - multi:newSystemThread(...) -end - -function love.threaderror(thread, errorstr) - multi.print("Thread error!\n" .. errorstr) -end - -multi.integration.GLOBAL = GLOBAL -multi.integration.THREAD = THREAD -require("multi.integration.loveManager.extensions") -require("multi.integration.sharedExtensions") -multi.print("Integrated Love Threading!") -return {init = function() return GLOBAL, THREAD end} diff --git a/integration/loveManagerold/threads.lua b/integration/loveManagerold/threads.lua deleted file mode 100644 index 7749c98..0000000 --- a/integration/loveManagerold/threads.lua +++ /dev/null @@ -1,257 +0,0 @@ ---[[ -MIT License - -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 -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. -]] -require("love.timer") -require("love.system") -require("love.data") -require("love.thread") -local serpent = require("multi.integration.loveManager.serpent") -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 - -function threads.packTable(table) - return love.data.newByteData(serpent.dump(table)) -end - -function threads.unpackTable(data) - return serpent.load(data:getString()) -end - -local fRef = {"func",nil} -local function manage(channel, value) - channel:clear() - if type(value) == "table" then - channel:push{"DATA",threads.packTable(value)} - else - channel:push(value) - end -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() - if type(dat)=="table" and dat[1]=="DATA" then - return threads.unpackTable(dat[2]) - else - return dat - end -end - -function threads.waitFor(name) - if thread.isThread() then - return thread.hold(function() - return threads.get(name) - end) - end - while threads.get(name)==nil do - love.timer.sleep(.001) - end - local dat = threads.get(name) - if type(dat) == "table" and dat.init then - dat.init = threads.loadDump(dat.init) - 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 threads.pushStatus(...) - local status_channel = love.thread.getChannel("STATCHAN_" ..__THREADID__) - local args = multi.pack(...) - status_channel:push(args) -end - -function threads.getThreads() - local t = {} - for i=1,GLOBAL["__THREAD_COUNT"] do - t[#t+1]=GLOBAL["__THREAD_"..i] - end - return t -end - -function threads.getThread(n) - return GLOBAL["__THREAD_"..n] -end - -function threads.sleep(n) - love.timer.sleep(n) -end - -function threads.getGlobal() - return setmetatable({}, - { - __index = function(t, k) - return THREAD.get(k) - end, - __newindex = function(t, k, v) - THREAD.set(k,v) - end - } - ) -end - -function threads.packENV(env) - return threads.packTable(env) -end - -function threads.unpackENV(env) - return threads.unpackTable(env) -end - - -function threads.setENV(env, name) - name = name or "__env" - (threads.getGlobal())[name] = threads.packTable(env) -end - -function threads.getENV(name) - name = name or "__env" - return threads.unpackTable((threads.getGlobal())[name]) -end - -function threads.exposeENV(name) - name = name or "__env" - local env = threads.getENV(name) - for i,v in pairs(env) do - _G[i] = v - end -end - -function threads.createTable(n) - local _proxy = {} - local function set(name,val) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - _proxy[name]:performAtomic(manage, val) - end - local function get(name) - if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end - local dat = _proxy[name]:peek() - if type(dat)=="table" and dat[1]=="DATA" then - return threads.unpackTable(dat[2]) - else - return dat - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end - -function threads.getConsole() - local c = {} - c.queue = love.thread.getChannel("__CONSOLE__") - function c.print(...) - c.queue:push(multi.pack(...)) - end - function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} - error(err) - end - return c -end - -if not ISTHREAD then - local queue = love.thread.getChannel("__CONSOLE__") - multi:newLoop(function(loop) - dat = queue:pop() - if dat then - print(multi.unpack(dat)) - end - end) -end - -function threads.createStaticTable(n) - local __proxy = {} - local function set(name,val) - if __proxy[name] then return end - local chan = love.thread.getChannel(n..name) - if chan:getCount()>0 then return end - chan:performAtomic(manage, val) - __proxy[name] = val - end - local function get(name) - if __proxy[name] then return __proxy[name] end - local dat = love.thread.getChannel(n..name):peek() - if type(dat)=="table" and dat[1]=="func" then - __proxy[name] = threads.loadDump(dat[2]) - return __proxy[name] - else - __proxy[name] = dat - return __proxy[name] - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -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/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index d21aebb..00ddfa3 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local multi, thread = require("multi"):init() -local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD +local GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD local function stripUpValues(func) local dmp = string.dump(func) @@ -156,7 +156,7 @@ function multi:newSystemThreadedJobQueue(n) end end) for i=1,c.cores do - multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) + multi:newSystemThread("STJQ_"..multi.randomString(8),function(jqc) local GLOBAL, THREAD = require("multi.integration.pseudoManager"):init() local multi, thread = require("multi"):init() local clock = os.clock @@ -217,4 +217,5 @@ function multi:newSystemThreadedConnection(name) GLOBAL[name or "_"] = conn return conn end + require("multi.integration.sharedExtensions") \ No newline at end of file diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 31106e9..46c4c9b 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -24,6 +24,8 @@ SOFTWARE. package.path = "?/init.lua;?.lua;" .. package.path local multi, thread = require("multi"):init() +local pseudoProcessor = multi:newProcessor() + if multi.integration then return { init = function() @@ -89,7 +91,7 @@ function multi:newSystemThread(name, func, ...) local GLOBAL, THREAD = activator.init(thread, env) - local th = thread:newISOThread(name, func, env, ...) + local th = pseudoProcessor:newISOThread(name, func, env, ...) th.Type = multi.registerType("s_thread", "pseudoThreads") th.OnError(multi.error) diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 91a1667..5490607 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -110,6 +110,10 @@ local function INIT(thread) end end + function THREAD.sync() + thread.sleep(.5) + end + return GLOBAL, THREAD end diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 8526954..01bae7f 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -60,7 +60,7 @@ function multi:newProxy(list) return res end if not(self.is_init) then - THREAD.sleep(.3) + THREAD.sync() self.is_init = true local multi, thread = require("multi"):init() self.proxy_link = "PL" .. multi.randomString(12) @@ -92,7 +92,6 @@ function multi:newProxy(list) local func = table.remove(data, 1) local sref = table.remove(data, 1) local ret - if sref then ret = {_G[list[0]][func](_G[list[0]], multi.unpack(data))} else @@ -119,7 +118,8 @@ function multi:newProxy(list) end) return self else - THREAD.sleep(.3) + THREAD.sync() + if not self.funcs then return self end local function copy(obj) if type(obj) ~= 'table' then return obj end local res = {} @@ -267,12 +267,12 @@ function multi:newSystemThreadedProcessor(cores) } for _, method in pairs(implement) do - c[method] = function(self, ...) + c[method] = thread:newFunction(function(self, ...) proxy = self.spawnTask(method, ...) proxy:init() references[proxy] = self return proxy - end + end, true) end function c:newThread(name, func, ...) diff --git a/tests/conf.lua b/tests/conf.lua index 6b728c5..3a072a4 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -1,22 +1,3 @@ function love.conf(t) - t.identity = nil -- The name of the save directory (string) - t.version = "11.4" -- The LOVE version this game was made for (string) - t.console = true -- Attach a console (boolean, Windows only) - - t.window = false - - t.modules.audio = false -- Enable the audio module (boolean) - t.modules.event = false -- Enable the event module (boolean) - t.modules.graphics = false -- Enable the graphics module (boolean) - t.modules.image = false -- Enable the image module (boolean) - t.modules.joystick = false -- Enable the joystick module (boolean) - t.modules.keyboard = false -- Enable the keyboard module (boolean) - t.modules.math = false -- Enable the math module (boolean) - t.modules.mouse = false -- Enable the mouse module (boolean) - t.modules.physics = false -- Enable the physics module (boolean) - t.modules.sound = false -- Enable the sound module (boolean) - t.modules.system = true -- Enable the system module (boolean) - t.modules.timer = true -- Enable the timer module (boolean) - t.modules.window = false -- Enable the window module (boolean) - t.modules.thread = true -- Enable the thread module (boolean) -end + t.console = true +end \ No newline at end of file diff --git a/tests/main.lua b/tests/main.lua index b23a884..edeee3a 100644 --- a/tests/main.lua +++ b/tests/main.lua @@ -1,12 +1,10 @@ package.path = "../?/init.lua;../?.lua;"..package.path -require("runtests") -require("threadtests") --- Allows you to run "love tests" which runs the tests -multi, thread = require("multi"):init() +if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then + require("lldebugger").start() +end + GLOBAL, THREAD = require("multi.integration.loveManager"):init() - -function love.update() - multi:uManager() -end \ No newline at end of file +require("runtests") +require("threadtests") diff --git a/tests/test.lua b/tests/test.lua index 8ca374e..d5056db 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,8 +1,9 @@ package.path = "../?/init.lua;../?.lua;"..package.path multi, thread = require("multi"):init{print=true,warn=true,debugging=true} -for i,v in pairs(thread) do - print(i,v) -end +-- for i,v in pairs(thread) do +-- print(i,v) +-- end + -- require("multi.integration.priorityManager") -- multi.debugging.OnObjectCreated(function(obj, process) @@ -14,10 +15,6 @@ end -- end) - - - - -- test = multi:newProcessor("Test") -- test:setPriorityScheme(multi.priorityScheme.TimeBased) @@ -102,39 +99,39 @@ end -- multi:mainloop() -multi:setTaskDelay(.05) -multi:newTask(function() - for i = 1, 10 do - multi:newTask(function() - print("Task "..i) - end) - end -end) +-- multi:setTaskDelay(.05) +-- multi:newTask(function() +-- for i = 1, 10 do +-- multi:newTask(function() +-- print("Task "..i) +-- end) +-- end +-- end) -local conn = multi:newConnection() -conn(function() print("Test 1") end) -conn(function() print("Test 2") end) -conn(function() print("Test 3") end) -conn(function() print("Test 4") end) +-- local conn = multi:newConnection() +-- conn(function() print("Test 1") end) +-- conn(function() print("Test 2") end) +-- conn(function() print("Test 3") end) +-- conn(function() print("Test 4") end) -print("Fire 1") -conn:Fire() -conn = -conn -print("Fire 2") -conn:Fire() +-- print("Fire 1") +-- conn:Fire() +-- conn = -conn +-- print("Fire 2") +-- conn:Fire() -print(#conn) +-- print(#conn) -thread:newThread("Test thread", function() - print("Starting thread!") - thread.defer(function() -- Runs when the thread finishes execution - print("Clean up time!") - end) - --[[ - Do lot's of stuff - ]] - thread.sleep(3) -end) +-- thread:newThread("Test thread", function() +-- print("Starting thread!") +-- thread.defer(function() -- Runs when the thread finishes execution +-- print("Clean up time!") +-- end) +-- --[[ +-- Do lot's of stuff +-- ]] +-- thread.sleep(3) +-- end) multi:mainloop() diff --git a/tests/threadtests.lua b/tests/threadtests.lua index 3f62321..9570a39 100644 --- a/tests/threadtests.lua +++ b/tests/threadtests.lua @@ -1,6 +1,6 @@ package.path = "D:/VSCWorkspace/?/init.lua;D:/VSCWorkspace/?.lua;"..package.path package.cpath = "C:/luaInstalls/lua5.4/lib/lua/5.4/?/core.dll;" .. package.cpath -multi, thread = require("multi"):init{error=true,warning=true,print=true}--{priority=true} +multi, thread = require("multi"):init{error=true,warning=true,print=true, priority=true} proc = multi:newProcessor("Thread Test",true) local LANES, LOVE, PSEUDO = 1, 2, 3 local env, we_good @@ -38,15 +38,15 @@ THREAD.setENV({ }) multi:newThread("Scheduler Thread",function() - -- multi:newThread(function() - -- thread.sleep(30) - -- print("Timeout tests took longer than 30 seconds") - -- multi:Stop() - -- os.exit(1) - -- end) + multi:newThread(function() + thread.sleep(30) + print("Timeout tests took longer than 30 seconds") + multi:Stop() + os.exit(1) + end) - queue = multi:newSystemThreadedQueue("Test_Queue"):init() - defer_queue = multi:newSystemThreadedQueue("Defer_Queue"):init() + queue = multi:newSystemThreadedQueue("Test_Queue") + defer_queue = multi:newSystemThreadedQueue("Defer_Queue") multi:newSystemThread("Test_Thread_0", function() defer_queue = THREAD.waitFor("Defer_Queue"):init() @@ -63,13 +63,11 @@ multi:newThread("Scheduler Thread",function() end end) - thread:newThread(function() - if thread.hold(function() - return defer_queue:pop() == "done" - end,{sleep=1}) == nil then - multi.error("Thread.defer didn't work!") - end - end) + if thread.hold(function() + return defer_queue:pop() == "done" + end,{sleep=3}) == nil then + multi.error("Thread.defer didn't work!") + end th1 = multi:newSystemThread("Test_Thread_1", function(a,b,c,d,e,f) queue = THREAD.waitFor("Test_Queue"):init() @@ -135,7 +133,7 @@ multi:newThread("Scheduler Thread",function() local ready = false - jq = multi:newSystemThreadedJobQueue(1) -- Job queue with 4 worker threads + jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads func2 = jq:newFunction("sleep",function(a,b) THREAD.sleep(.2) end) @@ -168,7 +166,7 @@ multi:newThread("Scheduler Thread",function() --print("Test") end, 1) - multi:newSystemThread("Testing proxy copy THREAD",function(tloop) + multi:newSystemThread("PROX_THREAD",function(tloop) local multi, thread = require("multi"):init() tloop = tloop:init() multi.print("tloop type:",tloop.Type) @@ -176,19 +174,21 @@ multi:newThread("Scheduler Thread",function() thread:newThread(function() while true do thread.hold(tloop.OnLoop) - --print(THREAD_NAME,"Loopy") + print(THREAD_NAME,"Loopy") end end) tloop.OnLoop(function(a) - --print(THREAD_NAME, "Got loop...") + print(THREAD_NAME, "Got loop...") end) multi:mainloop() end, tloop:getTransferable()) + local test = tloop:getTransferable() + multi.print("tloop", tloop.Type) multi.print("tloop.OnLoop", tloop.OnLoop.Type) - thread:newThread(function() + thread:newThread("Proxy Test Thread",function() multi.print("Testing holding on a proxy connection!") thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... once") @@ -197,17 +197,22 @@ multi:newThread("Scheduler Thread",function() thread.hold(tloop.OnLoop) multi.print("Held on proxy connection... finally") proxy_test = true - end) + end).OnError(print) thread:newThread(function() + thread.defer(function() + multi.print("Something happened!") + end) while true do thread.hold(tloop.OnLoop) - --print(THREAD_NAME,"Local Loopy") + multi.print(THREAD_NAME,"Local Loopy") end + end).OnError(function(...) + print("Error",...) end) tloop.OnLoop(function() - --print("OnLoop",THREAD_NAME) + print("OnLoop", THREAD_NAME) end) t, val = thread.hold(function() -- 2.43.0 From b4c3708f5039d7ddf553e9e56a3623e5e529e03e Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 24 Feb 2024 23:13:48 -0500 Subject: [PATCH 114/117] Fixed, conf --- tests/conf.lua | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/conf.lua b/tests/conf.lua index 3a072a4..4d2c4ee 100644 --- a/tests/conf.lua +++ b/tests/conf.lua @@ -1,3 +1,39 @@ function love.conf(t) - t.console = true + t.identity = nil -- The name of the save directory (string) + t.version = "12.0" -- The LOVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + + t.window.title = "MultiThreadTest" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 1280 -- The window width (number) + t.window.height = 720 -- The window height (number) + + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = true -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Standard fullscreen or desktop fullscreen mode (string) + t.window.vsync = false -- Enable vertical sync (boolean) + t.window.fsaa = 2 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = false -- Enable the audio module (boolean) + t.modules.event = false -- Enable the event module (boolean) + t.modules.graphics = false -- Enable the graphics module (boolean) + t.modules.image = false -- Enable the image module (boolean) + t.modules.joystick = false -- Enable the joystick module (boolean) + t.modules.keyboard = false -- Enable the keyboard module (boolean) + t.modules.math = false -- Enable the math module (boolean) + t.modules.mouse = false -- Enable the mouse module (boolean) + t.modules.physics = false -- Enable the physics module (boolean) + t.modules.sound = false -- Enable the sound module (boolean) + t.modules.system = false -- Enable the system module (boolean) + t.modules.timer = false -- Enable the timer module (boolean) + t.modules.window = false -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) end \ No newline at end of file -- 2.43.0 From 946217310fa91c9069e2c5d6508d01960d4b479d Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 24 Feb 2024 23:23:35 -0500 Subject: [PATCH 115/117] lanes uses a threaded function like waitfor function --- init.lua | 8 ++++++-- integration/lanesManager/threads.lua | 10 ++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/init.lua b/init.lua index f93a1ae..a197fb4 100644 --- a/init.lua +++ b/init.lua @@ -1226,7 +1226,9 @@ end function multi.hold(func,opt) if thread.isThread() then return thread.hold(func, opt) end local proc = multi.getCurrentTask() - proc:Pause() + if proc then + proc:Pause() + end local rets thread:newThread("Hold_func",function() rets = {thread.hold(func,opt)} @@ -1234,7 +1236,9 @@ function multi.hold(func,opt) while rets == nil do multi:uManager() end - proc:Resume() + if proc then + proc:Resume() + end return multi.unpack(rets) end diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 2dc231c..42433df 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -48,14 +48,12 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) end function THREAD.waitFor(name) - local function wait() + local multi, thread = require("multi"):init() + return multi.hold(function() math.randomseed(os.time()) __SleepingLinda:receive(.001, "__non_existing_variable") - end - repeat - wait() - until __GlobalLinda:get(name) - return __GlobalLinda:get(name) + return __GlobalLinda:get(name) + end) end function THREAD.getCores() -- 2.43.0 From ffaab32d5ee061210575a22620dfe2ca1a34d70c Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 24 Feb 2024 23:27:27 -0500 Subject: [PATCH 116/117] Cleaned up changes.md --- docs/changes.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index 0206192..2e54bf7 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -68,23 +68,14 @@ multi, thread = require("multi"):init{print=true} GLOBAL, THREAD = require("multi.integration.lanesManager"):init() ``` -```lua -package.path = "?/init.lua;?.lua;"..package.path - -local multi, thread = require("multi"):init({print=true, warn=true, error=true}) -local THREAD, GLOBAL = require("multi.integration.effilManager"):init() - --- Code as you would -``` - ## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! -- Allows the creation of custom priorityManagers for example: +- Allows the creation of custom priorityManagers Added --- -- thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a function, when the function returns or errors. +- thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a threaded function, when the function returns or errors. - multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. `delay` can be a function as well and will be processed as if thread.hold was called. - processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function - thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. -- 2.43.0 From 7b0cb7a8536463fb05ba6f0b0fbc4c8b866726f6 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sat, 24 Feb 2024 23:57:42 -0500 Subject: [PATCH 117/117] added priorityManager to rockspec --- README.md | 3 ++- docs/changes.md | 8 -------- integration/priorityManager/init.lua | 9 --------- rockspecs/multi-16.0-0.rockspec | 1 + 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9c1cbc1..01235d1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Multi Version: 16.0.0 - Getting the priorities straight +# Multi Version: 16.0.0 - Connecting the dots **Key Changes** - Expanded connection logic - New integration priorityManager - Tests for threads - Consistent behavior between the threading integrations +- Improved love2d threading - Bug fixes Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! diff --git a/docs/changes.md b/docs/changes.md index 2e54bf7..9ea6f80 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -60,14 +60,6 @@ Table of contents # Update 16.0.0 - Getting the priorities straight -Full Update Showcase ---- - -```lua -multi, thread = require("multi"):init{print=true} -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -``` - ## Added New Integration: **priorityManager** Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index ab5e6da..4c09007 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -45,31 +45,22 @@ end local function getPriority(obj) local avg = average(obj.__profiling) if avg < 0.0002 then - multi.print("Setting priority to: core") return PList[1] elseif avg < 0.0004 then - multi.print("Setting priority to: very high") return PList[2] elseif avg < 0.0008 then - multi.print("Setting priority to: high") return PList[3] elseif avg < 0.001 then - multi.print("Setting priority to: above normal") return PList[4] elseif avg < 0.0025 then - multi.print("Setting priority to: normal") return PList[5] elseif avg < 0.005 then - multi.print("Setting priority to: below normal") return PList[6] elseif avg < 0.008 then - multi.print("Setting priority to: low") return PList[7] elseif avg < 0.01 then - multi.print("Setting priority to: very low") return PList[8] else - multi.print("Setting priority to: idle") return PList[9] end end diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec index 80d91f9..b44f1f9 100644 --- a/rockspecs/multi-16.0-0.rockspec +++ b/rockspecs/multi-16.0-0.rockspec @@ -36,6 +36,7 @@ build = { ["multi.integration.luvitManager"] = "integration/luvitManager.lua", ["multi.integration.threading"] = "integration/threading.lua", ["multi.integration.sharedExtensions"] = "integration/sharedExtensions/init.lua", + ["multi.integration.priorityManager"] = "integration/priorityManager/init.lua", --["multi.integration.networkManager"] = "integration/networkManager.lua", } } \ No newline at end of file -- 2.43.0