Compare commits

...

11 Commits

Author SHA1 Message Date
45095191f4
V16.0.1 (#68)
* Fixed issue with pushstatus

* Fixed bug with pushstatus
2024-03-24 23:29:43 -04:00
e44a3cd98b update working branch 2024-02-26 09:50:40 -05:00
fff3601041
Working on 16.0.0 (#53)
* Fixed spelling, started ideaing for 16.0.0

* Updated files

* Updated readme

* Updated version

* Concat conns now properly transfer events

* Testing types

* Connections can be % with functions

* Updated connections

* Fixed issue with double thread activations (Looking for another solution)

* Working on issue with love threaded functions not waiting when in a thread

* Working on issue where threads created in threads don't work

* Fixed broken threads for love

* Fixed some issues with threads

* removed test

* Updated changes.md

* Plan on testing parity between the threading modules

* Writing tests for system threading

* Added test cases for threading, fixed issues. Todo test love2d

* Fixed love2d to succeed with tests

* All tests working

* Updated files for testing

* Modified tests to make it more seamless

* removed extra __cores in lanes/pseudo

* Working on new priority scheme

* Working on priority management

* Working on custom prioritySchemes

* Fixed issues with missing code

* Threaded processors

* THREAD.exposeENV(), thread:newProcessor()

* Typo in changes.md

* Fixed typo in pseudoManager

* fixing

* Trying to fix exposeENV with pseudoThreading

* Changes to threads

* updated changes.md

* Working on systemthreadedprocess, and experimental newProxy for threading

* newProxy and STP work

* newProxy implemented

* Proxies work with connections now :D

* Added tstep to STP, updated changes.md

* thread.hold(proxy.conn)

* Clean up connection events when holding, working on scheduling tasks/threads to system threaded processors

* Getting loads of processors implemented

* Finished getLoad(type)

* Fixed some bugs

* Added an easy way to share a table, found some limitations with lanes threading.

* THREAD_NAME set for main thread, connections break the rules for proxies

* Testing

* Really close to portable proxies, currently extreamly unstable!

* Debugging what is going on...

* Fixed critical issue with coroutine based threads

* Removed extra bloat, proxies are portable now!

* Started work on the debugManager

* Testing actions, fixing bugs with lanes

* Testing...

* fixing actions

* typo fixed

* Throw an error when things break

* fixing stuff

* Fixed issue with errors not going through

* Removed system threaded connections, soon to be replaced by proxies

* Testing love2d tests

* Test love2d

* Use later love-build

* Use ubuntu for build

* Fixed path

* Use appimage

* use sudo

* No window for love2d

* Fixed love2d tests

* Testing love2d

* Use workspace

* Moved other tests while testing

* actually pull the repo

* packagepath set

* Fixed pull

* Update multi

* Removed link

* Edited symlink

* Added timeout to build

* Rewriting loveManager, too much outdated code

* Still implementing new love2d threading code

* Rewriting love2d threading binding

* Working on adding a Hold method to all objects. Will document how they all work when done.

* jobqueues having isues with stp

* new pack/unpack for tables, current issue is things being turned into strings

* Fixed packing of values into threads, need to fix system proxies and system threaded processors

* testing...

* Not hard crashing when error is encountered

* Should now push non 0 exit codes

* Push error when an error happens

* Closer to getting things working...

* Working on new type system, planning out debugmanager

* Fixed error code issue

* Test for 5.1

* Planning out debugManager

* Some work on the debug manager, proxies working on lanes, todo get pseudo manager and love2d working

* Working on getting pseudoThreading tests to work

* Added function / connection

* Added boost method

* Document new features to conns, todo fix newTask

* Fixed newTask()

* Updated changes.md and fixed some bugs

* Added thread.defer(func)

* Fixed tests on lanes and pseudo threading, todo fix love2d threading

* Fixed paths

* Working on paths

* Testing paths

* Add test for rockspec

* Fixed issues

* Fixed typo

* Added test for defer

* Threading working in love2d

* Fixed, conf

* lanes uses a threaded function like waitfor function

* Cleaned up changes.md

* added priorityManager to rockspec
2024-02-25 00:00:51 -05:00
1639de5d0f
Update README.md 2023-01-04 19:50:13 -05:00
59462df9a6
Merge pull request #52 from rayaman/15.3.1
15.3.1
2023-01-04 10:36:39 -05:00
72733394ac Fixed connections * 2023-01-04 10:33:36 -05:00
dc9cee5a3e Fixing connection * issue 2023-01-04 10:28:02 -05:00
71ab702a75
Updated progress 2022-12-31 02:23:12 -05:00
1bb7410210
Merge pull request #51 from rayaman/v15.3.0
V15.3.0
2022-12-31 02:22:22 -05:00
ec5bf74009 Fixed love2d STCs! Actually before new years :P 2022-12-31 02:21:01 -05:00
7ea6873c1f Fixed issue where fastmode connections wouldn't properly be removed 2022-12-30 15:22:37 -05:00
40 changed files with 5375 additions and 1693 deletions

25
.github/workflows/love.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Build & Run tests Love2d
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
strategy:
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
- name: Run Tests
run: |
./love.AppImage tests

41
.github/workflows/nix_ci.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Build & Run tests Ubuntu
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", "lua 5.4", "luajit 2.1.0-beta3"]
os: ["ubuntu-latest"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Setup env
run: |
pip install hererocks
hererocks lua-pkg --${{ matrix.lua }} -rlatest
- name: Install lanes and multi
run: |
source ${{github.workspace}}/lua-pkg/bin/activate
luarocks install lanes
luarocks install rockspecs/multi-16.0-0.rockspec
- name: Run Tests
run: |
source ${{github.workspace}}/lua-pkg/bin/activate
lua tests/runtests.lua

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.code-workspace *.code-workspace
lua5.4/* lua5.4/*
test.lua

26
NetworkManager.md Normal file
View File

@ -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, ...)

View File

@ -1,8 +1,4 @@
# Multi Version: 15.3.0 A world of Connections # Multi Version: 16.0.1 - Bug fix
**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
Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it! Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it!
@ -10,7 +6,7 @@ My multitasking library for lua. It is a pure lua binding, with exceptions of th
</br> </br>
Progress is being made in [v15.4.0](https://github.com/rayaman/multi/tree/v15.4.0) Progress is being made in [v16.1.0](https://github.com/rayaman/multi/tree/v16.1.0)
--- ---
</br> </br>
@ -18,10 +14,20 @@ Progress is being made in [v15.4.0](https://github.com/rayaman/multi/tree/v15.4.
INSTALLING INSTALLING
---------- ----------
Link to optional dependencies: 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/) - [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</br> To install copy the multi folder into your environment and you are good to go</br>
If you want to use the system threads, then you'll need to install lanes or love2d game engine! If you want to use the system threads, then you'll need to install lanes or love2d game engine!
@ -36,12 +42,18 @@ https://discord.gg/U8UspuA
Planned features/TODO Planned features/TODO
--------------------- ---------------------
- [ ] Create test suite (In progress, mostly done) - [x] ~~Create test suite (In progress, mostly done)~~
- [ ] Network Parallelism rework - [ ] Network Parallelism rework
Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md) Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)
----- -----
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 ```lua
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
GLOBAL, THREAD = require("multi.integration.threading"):init() GLOBAL, THREAD = require("multi.integration.threading"):init()

View File

@ -1,9 +1,637 @@
# Changelog # Changelog
Table of contents Table of contents
--- ---
[Update 16.0.1 - Bug fix](#update-1531---bug-fix)</br>
[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)</br>
[Update 15.3.1 - Bug fix](#update-1531---bug-fix)</br>
[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)</br> [Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)</br>
[Update 15.2.1 - Bug fix](#update-1521---bug-fix)</br> [Update 15.2.1 - Bug fix](#update-1521---bug-fix)</br>
[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)</br>[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)</br>[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)</br>[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)</br>[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)</br>[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)</br>[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)</br>[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)</br>[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)</br>[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)</br>[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)</br>[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)</br>[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)</br>[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)</br>[Update: 1.11.0](#update-1110)</br>[Update: 1.10.0](#update-1100)</br>[Update: 1.9.2](#update-192)</br>[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)</br>[Update: 1.9.0](#update-190)</br>[Update: 1.8.7](#update-187)</br>[Update: 1.8.6](#update-186)</br>[Update: 1.8.5](#update-185)</br>[Update: 1.8.4](#update-184)</br>[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)</br>[Update: 1.8.2](#update-182)</br>[Update: 1.8.1](#update-181)</br>[Update: 1.7.6](#update-176)</br>[Update: 1.7.5](#update-175)</br>[Update: 1.7.4](#update-174)</br>[Update: 1.7.3](#update-173)</br>[Update: 1.7.2](#update-172)</br>[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)</br>[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)</br>[Update: 1.6.0](#update-160)</br>[Update: 1.5.0](#update-150)</br>[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)</br>[Update: 1.4.0 (3/20/2017)](#update-140-3202017)</br>[Update: 1.3.0 (1/29/2017)](#update-130-1292017)</br>[Update: 1.2.0 (12.31.2016)](#update-120-12312016)</br>[Update: 1.1.0](#update-110)</br>[Update: 1.0.0](#update-100)</br>[Update: 0.6.3](#update-063)</br>[Update: 0.6.2](#update-062)</br>[Update: 0.6.1-6](#update-061-6)</br>[Update: 0.5.1-6](#update-051-6)</br>[Update: 0.4.1](#update-041)</br>[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)</br>[Update: EventManager 2.0.0](#update-eventmanager-200)</br>[Update: EventManager 1.2.0](#update-eventmanager-120)</br>[Update: EventManager 1.1.0](#update-eventmanager-110)</br>[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)</br>[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)</br>
[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)</br>
[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)</br>
[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)</br>
[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)</br>
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)</br>
[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)</br>
[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)</br>
[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)</br>
[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)</br>
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)</br>
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)</br>
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)</br>
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)</br>
[Update: 1.11.0](#update-1110)</br>
[Update: 1.10.0](#update-1100)</br>
[Update: 1.9.2](#update-192)</br>
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)</br>
[Update: 1.9.0](#update-190)</br>
[Update: 1.8.7](#update-187)</br>
[Update: 1.8.6](#update-186)</br>
[Update: 1.8.5](#update-185)</br>
[Update: 1.8.4](#update-184)</br>
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)</br>
[Update: 1.8.2](#update-182)</br>
[Update: 1.8.1](#update-181)</br>
[Update: 1.7.6](#update-176)</br>
[Update: 1.7.5](#update-175)</br>
[Update: 1.7.4](#update-174)</br>
[Update: 1.7.3](#update-173)</br>
[Update: 1.7.2](#update-172)</br>
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)</br>
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)</br>
[Update: 1.6.0](#update-160)</br>
[Update: 1.5.0](#update-150)</br>
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)</br>
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)</br>
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)</br>
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)</br>
[Update: 1.1.0](#update-110)</br>
[Update: 1.0.0](#update-100)</br>
[Update: 0.6.3](#update-063)</br>
[Update: 0.6.2](#update-062)</br>
[Update: 0.6.1-6](#update-061-6)</br>
[Update: 0.5.1-6](#update-051-6)</br>
[Update: 0.4.1](#update-041)</br>
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)</br>
[Update: EventManager 2.0.0](#update-eventmanager-200)</br>
[Update: EventManager 1.2.0](#update-eventmanager-120)</br>
[Update: EventManager 1.1.0](#update-eventmanager-110)</br>
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)</br>
[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.1 - Bug fix
Fixed
---
- thread.pushStatus() wasn't properly working when forwarding events from THREAD.pushStatus OnStatus connection. This bug also caused stack overflow errors with the following code
```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)
multi:mainloop()
```
# Update 16.0.0 - Getting the priorities straight
## 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
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 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.
- 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!!!
```
- 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.
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, warn=true, error=true})
THREAD, GLOBAL = require("multi.integration.lanesManager"):init()
stp = multi:newSystemThreadedProcessor(8)
tloop = stp:newTLoop(nil, 1)
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)
print("tloop", tloop.Type)
thread:newThread(function()
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()
while true do
tloop.OnLoop:Hold()
print("OnLoop",THREAD_NAME)
end
end).OnError(multi.error)
multi:mainloop()
```
Output:
```
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 every second)
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!
Currently supporting:
- proxyLoop = STP:newLoop(...)
- proxyTLoop = STP:newTLoop(...)
- proxyUpdater = STP:newUpdater(...)
- proxyEvent = STP:newEvent(...)
- proxyAlarm = STP:newAlarm(...)
- proxyStep = STP:newStep(...)
- proxyTStep = STP:newTStep(...)
- proxyThread = STP:newThread(...)
- proxyService = STP:newService(...)
- threadedFunction = STP:newFunction(...)
Unique:
- STP:newSharedTable(name)
</br>
**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
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()
```
- 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!
- 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`
**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.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
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()
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
```
- 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
__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}
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()
```
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!
```
**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
---
- 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`
- 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. Blue `INFO`
- 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
---
- 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
- connection events can no longer can be chained with connect. Connect only takes a function that you want to connect
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.
- 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.
- 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!
```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
---
- 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
---
- Issue where multiplying connections triggered events improperly
```lua
local multi, thread = require("multi"):init()
conn1 = multi:newConnection()
conn2 = multi:newConnection(); -- To remove function ambiguity
(conn1 * conn2)(function() print("Triggered!") end)
conn1:Fire()
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 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()
```
# Update 15.3.0 - A world of Connections # Update 15.3.0 - A world of Connections
@ -1681,7 +2309,7 @@ L: 2120906
I: 2120506 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:** **Improved:**
- Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. - Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s.

1403
init.lua

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
local multi, thread = require("multi"):init()
multi.defaultSettings.debugging = true
local dbg = {}
dbg.__index = dbg
dbg.processors = {}
-- 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 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)
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()
multi.OnObjectCreated(creation_hook)
tmulti.OnObjectCreated(creation_hook)
multi.OnObjectDestroyed(destroction_hook)
tmulti.OnObjectDestroyed(destroction_hook)
-- We write to a debug interface in the multi namespace
multi.debugging = dbg

View File

View File

@ -0,0 +1,46 @@
local multi, thread = require("multi"):init{error=true}
multi.error("Currntly not supported!")
os.exit(1)
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
}

View File

View File

@ -22,29 +22,57 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] ]]
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
if not (GLOBAL and THREAD) then if not (GLOBAL and THREAD) then
local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD
else else
lanes = require("lanes") lanes = require("lanes")
end end
function multi:newSystemThreadedQueue(name) function multi:newSystemThreadedQueue(name)
local name = name or multi.randomString(16) local name = name or multi.randomString(16)
local c = {} local c = {}
c.Name = name c.Name = name
c.linda = lanes.linda() c.linda = lanes.linda()
c.Type = multi.registerType("s_queue")
function c:push(v) function c:push(v)
self.linda:send("Q", v) self.linda:send("Q", v)
end end
function c:pop() function c:pop()
return ({self.linda:receive(0, "Q")})[2] return ({self.linda:receive(0, "Q")})[2]
end end
function c:peek() function c:peek()
return self.linda:get("Q") return self.linda:get("Q")
end end
function c:init() function c:init()
return self return self
end end
GLOBAL[name or "_"] = c
if multi.isMainThread then
multi.integration.GLOBAL[name] = c
else
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 return c
end end
@ -53,37 +81,60 @@ function multi:newSystemThreadedTable(name)
local c = {} local c = {}
c.link = lanes.linda() c.link = lanes.linda()
c.Name = name c.Name = name
c.Type = multi.registerType("s_table")
function c:init()
return self
end
setmetatable(c,{ setmetatable(c,{
__index = function(t,k) __index = function(t,k)
return c.link:get(k) return c.link:get(k)
end, end,
__newindex = function(t,k,v) __newindex = function(t,k,v)
c.link:set(k,v) c.link:set(k, v)
end end
}) })
function c:init()
return self if multi.isMainThread then
multi.integration.GLOBAL[name] = c
else
GLOBAL[name] = c
end end
GLOBAL[name or "_"] = c
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 return c
end end
function multi:newSystemThreadedJobQueue(n) function multi:newSystemThreadedJobQueue(n)
local c = {} local c = {}
c.cores = n or THREAD.getCores()*2 c.cores = n or THREAD.getCores()*2
c.Type = multi.registerType("s_jobqueue")
c.OnJobCompleted = multi:newConnection() c.OnJobCompleted = multi:newConnection()
local funcs = multi:newSystemThreadedTable():init() local funcs = multi:newSystemThreadedTable()
local queueJob = multi:newSystemThreadedQueue():init() local queueJob = multi:newSystemThreadedQueue()
local queueReturn = multi:newSystemThreadedQueue():init() local queueReturn = multi:newSystemThreadedQueue()
local doAll = multi:newSystemThreadedQueue():init() local doAll = multi:newSystemThreadedQueue()
local ID=1 local ID=1
local jid = 1 local jid = 1
function c:isEmpty() function c:isEmpty()
return queueJob:peek()==nil return queueJob:peek()==nil
end end
function c:doToAll(func) function c:doToAll(func,...)
for i=1,c.cores do for i=1,c.cores do
doAll:push{ID,func} doAll:push{ID,func,...}
end end
ID = ID + 1 ID = ID + 1
return self return self
@ -93,12 +144,12 @@ function multi:newSystemThreadedJobQueue(n)
return self return self
end end
function c:pushJob(name,...) function c:pushJob(name,...)
queueJob:push{name,jid,{...}} queueJob:push{name,jid,multi.pack(...)}
jid = jid + 1 jid = jid + 1
return jid-1 return jid-1
end end
local nFunc = 0 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 if type(name)=="function" then
holup = func holup = func
func = name func = name
@ -112,32 +163,38 @@ function multi:newSystemThreadedJobQueue(n)
local rets local rets
link = c.OnJobCompleted(function(jid,...) link = c.OnJobCompleted(function(jid,...)
if id==jid then if id==jid then
rets = {...} rets = multi.pack(...)
link:Destroy()
end end
end) end)
return thread.hold(function() return thread.hold(function()
if rets then if rets then
return unpack(rets) or multi.NIL if #rets == 0 then
return multi.NIL
else
return multi.unpack(rets)
end
end end
end) end)
end,holup),name end, holup), name
end end
thread:newThread("JobQueueManager",function() thread:newThread("JobQueueManager",function()
while true do while true do
local job = thread.hold(function() local job = thread.hold(function()
return queueReturn:pop() return queueReturn:pop()
end) end)
if job then
local id = table.remove(job,1) local id = table.remove(job,1)
c.OnJobCompleted:Fire(id,unpack(job)) c.OnJobCompleted:Fire(id,multi.unpack(job))
end
end end
end) end)
for i=1,c.cores do for i=1,c.cores do
multi:newSystemThread("SystemThreadedJobQueue",function(queue) multi:newSystemThread("STJQ_"..multi.randomString(8),function(queue)
local multi,thread = require("multi"):init() local multi, thread = require("multi"):init()
local idle = os.clock() local idle = os.clock()
local clock = os.clock local clock = os.clock
local ref = 0 local ref = 0
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs}) setmetatable(_G,{__index = funcs})
thread:newThread("JobHandler",function() thread:newThread("JobHandler",function()
while true do while true do
@ -145,10 +202,12 @@ function multi:newSystemThreadedJobQueue(n)
return queueJob:pop() return queueJob:pop()
end) end)
idle = clock() idle = clock()
local name = table.remove(dat,1) thread:newThread("JobQueue-Spawn",function()
local jid = table.remove(dat,1) local name = table.remove(dat, 1)
local args = table.remove(dat,1) local jid = table.remove(dat, 1)
queueReturn:push{jid, funcs[name](unpack(args)),queue} 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)
end end
end) end)
thread:newThread("DoAllHandler",function() thread:newThread("DoAllHandler",function()
@ -158,9 +217,10 @@ function multi:newSystemThreadedJobQueue(n)
end) end)
if dat then if dat then
if dat[1]>ref then if dat[1]>ref then
ref = table.remove(dat, 1)
func = table.remove(dat, 1)
idle = clock() idle = clock()
ref = dat[1] func(unpack(dat))
dat[2]()
doAll:pop() doAll:pop()
end end
end end
@ -175,14 +235,22 @@ function multi:newSystemThreadedJobQueue(n)
end end
end) end)
multi:mainloop() multi:mainloop()
end,i).priority = thread.Priority_Core end,i)
end end
function c:Hold(opt)
return thread.hold(self.OnJobCompleted)
end
self:create(c)
return c return c
end end
function multi:newSystemThreadedConnection(name) function multi:newSystemThreadedConnection(name)
local name = name or multi.randomString(16) local name = name or multi.randomString(16)
local c = {} local c = {}
c.Type = multi.registerType("s_connection")
c.CONN = 0x00 c.CONN = 0x00
c.TRIG = 0x01 c.TRIG = 0x01
c.PING = 0x02 c.PING = 0x02
@ -196,7 +264,7 @@ function multi:newSystemThreadedConnection(name)
end end
return r return r
end end
c.CID = THREAD.getID() c.CID = THREAD_ID
c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init() c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init()
c.Name = name 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. 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.
@ -233,7 +301,7 @@ function multi:newSystemThreadedConnection(name)
local function fire(...) local function fire(...)
for _, link in pairs(c.links) do for _, link in pairs(c.links) do
link:push {c.TRIG, {...}} link:push {c.TRIG, multi.pack(...)}
end end
end end
@ -253,16 +321,16 @@ function multi:newSystemThreadedConnection(name)
end) end)
c.links[#c.links+1] = item[2] c.links[#c.links+1] = item[2]
elseif item[1] == c.TRIG then elseif item[1] == c.TRIG then
fire(unpack(item[2])) fire(multi.unpack(item[2]))
c.proxy_conn:Fire(unpack(item[2])) c.proxy_conn:Fire(multi.unpack(item[2]))
end end
end end
end) end)
--- ^^^ This will only exist in the init thread --- ^^^ This will only exist in the init thread
function c:Fire(...) function c:Fire(...)
local args = {...} local args = multi.pack(...)
if self.CID == THREAD.getID() then -- Host Call if self.CID == THREAD_ID then -- Host Call
for _, link in pairs(self.links) do for _, link in pairs(self.links) do
link:push {self.TRIG, args} link:push {self.TRIG, args}
end end
@ -277,8 +345,14 @@ function multi:newSystemThreadedConnection(name)
self.links = {} self.links = {}
self.proxy_conn = multi:newConnection() self.proxy_conn = multi:newConnection()
local mt = getmetatable(self.proxy_conn) 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 = {}
if self.CID == THREAD.getID() then return self end 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_ID then return self end
thread:newThread("STC_CONN_MAN"..name,function() thread:newThread("STC_CONN_MAN"..name,function()
local item local item
local link_self_ref = multi:newSystemThreadedQueue() local link_self_ref = multi:newSystemThreadedQueue()
@ -296,7 +370,7 @@ function multi:newSystemThreadedConnection(name)
end end
link_self_ref:pop() link_self_ref:pop()
elseif item[1] == self.TRIG then 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() link_self_ref:pop()
else else
-- This shouldn't be the case -- This shouldn't be the case
@ -306,7 +380,14 @@ function multi:newSystemThreadedConnection(name)
return self return self
end end
if multi.isMainThread then
multi.integration.GLOBAL[name] = c
else
GLOBAL[name] = c GLOBAL[name] = c
end
self:create(c)
return c return c
end end
require("multi.integration.sharedExtensions")

View File

@ -36,6 +36,9 @@ lanes = require("lanes").configure()
multi.SystemThreads = {} multi.SystemThreads = {}
multi.isMainThread = true multi.isMainThread = true
_G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
function multi:canSystemThread() function multi:canSystemThread()
return true return true
end end
@ -55,10 +58,10 @@ local count = 1
local started = false local started = false
local livingThreads = {} local livingThreads = {}
function THREAD:newFunction(func,holdme) function THREAD:newFunction(func, holdme)
return thread:newFunctionBase(function(...) return thread:newFunctionBase(function(...)
return multi:newSystemThread("TempSystemThread",func,...) return multi:newSystemThread("TempSystemThread",func,...)
end,holdme)() end, holdme, multi.registerType("s_function"))()
end end
function multi:newSystemThread(name, func, ...) function multi:newSystemThread(name, func, ...)
@ -67,35 +70,43 @@ function multi:newSystemThread(name, func, ...)
local rand = math.random(1, 10000000) local rand = math.random(1, 10000000)
local return_linda = lanes.linda() local return_linda = lanes.linda()
c = {} c = {}
c.name = name
c.Name = name c.Name = name
c.Id = count c.ID = count
c.loadString = {"base","package","os","io","math","table","string","coroutine"} c.loadString = {"base","package","os","io","math","table","string","coroutine"}
livingThreads[count] = {true, name} livingThreads[count] = {true, name}
c.returns = return_linda c.returns = return_linda
c.Type = "sthread" c.Type = multi.registerType("s_thread")
c.creationTime = os.clock() c.creationTime = os.clock()
c.alive = true c.alive = true
c.priority = THREAD.Priority_Normal c.priority = THREAD.Priority_Normal
local multi_settings = multi.defaultSettings local multi_settings = multi.defaultSettings
for i,v in pairs(multi_settings) do local globe = {
print(i,v)
end
c.thread = lanes.gen("*",
{
globals={ -- Set up some globals
THREAD_NAME = name, THREAD_NAME = name,
THREAD_ID = count, THREAD_ID = count,
THREAD = THREAD, THREAD = THREAD,
GLOBAL = GLOBAL, GLOBAL = GLOBAL,
_Console = __ConsoleLinda _Console = __ConsoleLinda,
}, _DEFER = {}
priority=c.priority }
if GLOBAL["__env"] then
for i,v in pairs(GLOBAL["__env"]) do
globe[i] = v
end
end
c.thread = lanes.gen("*",
{
globals = globe,
priority = c.priority
},function(...) },function(...)
require("multi"):init(multi_settings) multi, thread = require("multi"):init(multi_settings)
require("multi.integration.lanesManager.extensions") require("multi.integration.lanesManager.extensions")
require("multi.integration.sharedExtensions")
local has_error = true local has_error = true
return_linda:set("returns",{func(...)}) returns = {pcall(func, ...)}
return_linda:set("returns", returns)
for i,v in pairs(_DEFER) do
pcall(v)
end
has_error = false has_error = false
end)(...) end)(...)
count = count + 1 count = count + 1
@ -110,10 +121,20 @@ function multi:newSystemThread(name, func, ...)
c.OnDeath = multi:newConnection() c.OnDeath = multi:newConnection()
c.OnError = multi:newConnection() c.OnError = multi:newConnection()
GLOBAL["__THREADS__"] = livingThreads GLOBAL["__THREADS__"] = livingThreads
c.OnError(multi.error)
if self.isActor then
self:create(c)
else
multi.create(multi, c)
end
return c return c
end end
THREAD.newSystemThread = multi.newSystemThread THREAD.newSystemThread = function(...)
multi:newSystemThread(...)
end
function multi.InitSystemThreadErrorHandler() function multi.InitSystemThreadErrorHandler()
if started == true then if started == true then
@ -126,18 +147,26 @@ function multi.InitSystemThreadErrorHandler()
while true do while true do
thread.yield() thread.yield()
_,data = __ConsoleLinda:receive(0, "Q") _,data = __ConsoleLinda:receive(0, "Q")
if data then print(unpack(data)) end if data then
--print(data[1])
end
for i = #threads, 1, -1 do for i = #threads, 1, -1 do
temp = threads[i] temp = threads[i]
status = temp.thread.status status = temp.thread.status
push = __StatusLinda:get(temp.Id) push = __StatusLinda:get(temp.ID)
if push then 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 end
if status == "done" or temp.returns:get("returns") then if status == "done" or temp.returns:get("returns") then
livingThreads[temp.Id] = {false, temp.Name} returns = ({temp.returns:receive(0, "returns")})[2]
livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false 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(multi.unpack(returns))
end
GLOBAL["__THREADS__"] = livingThreads GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i) table.remove(threads, i)
elseif status == "running" then elseif status == "running" then
@ -145,19 +174,15 @@ function multi.InitSystemThreadErrorHandler()
elseif status == "waiting" then elseif status == "waiting" then
-- --
elseif status == "error" then elseif status == "error" then
livingThreads[temp.Id] = {false, temp.Name} -- The thread never really errors, we handle this through our linda object
temp.alive = false
temp.OnError:Fire(temp,unpack(temp.returns:receive(0,"returns") or {"Thread Killed!"}))
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
elseif status == "cancelled" then elseif status == "cancelled" then
livingThreads[temp.Id] = {false, temp.Name} livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false temp.alive = false
temp.OnError:Fire(temp,"thread_cancelled") temp.OnError:Fire(temp,"thread_cancelled")
GLOBAL["__THREADS__"] = livingThreads GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i) table.remove(threads, i)
elseif status == "killed" then elseif status == "killed" then
livingThreads[temp.Id] = {false, temp.Name} livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false temp.alive = false
temp.OnError:Fire(temp,"thread_killed") temp.OnError:Fire(temp,"thread_killed")
GLOBAL["__THREADS__"] = livingThreads GLOBAL["__THREADS__"] = livingThreads
@ -165,10 +190,10 @@ function multi.InitSystemThreadErrorHandler()
end end
end end
end end
end) end).OnError(multi.error)
end end
multi.print("Integrated Lanes!") multi.print("Integrated Lanes Threading!")
multi.integration = {} -- for module creators multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD multi.integration.THREAD = THREAD

View File

@ -48,20 +48,12 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
end end
function THREAD.waitFor(name) function THREAD.waitFor(name)
local function wait() local multi, thread = require("multi"):init()
return multi.hold(function()
math.randomseed(os.time()) math.randomseed(os.time())
__SleepingLinda:receive(.001, "__non_existing_variable") __SleepingLinda:receive(.001, "__non_existing_variable")
end
repeat
wait()
until __GlobalLinda:get(name)
return __GlobalLinda:get(name) return __GlobalLinda:get(name)
end end)
if getOS() == "windows" then
THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else
THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
end end
function THREAD.getCores() function THREAD.getCores()
@ -72,11 +64,11 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
local c = {} local c = {}
c.queue = __Console c.queue = __Console
function c.print(...) function c.print(...)
c.queue:send("Q", {...}) c.queue:push("Q", table.concat(multi.pack(...), "\t"))
end end
function c.error(err) function c.error(err)
c.queue:push("Q",{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}) c.queue:push("Q", "Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err)
error(err) multi.error(err)
end end
return c return c
end end
@ -95,16 +87,12 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
error("Thread was killed!\1") error("Thread was killed!\1")
end end
function THREAD.getName() function THREAD.sync()
return THREAD_NAME -- Maybe do something...
end
function THREAD.getID()
return THREAD_ID
end end
function THREAD.pushStatus(...) function THREAD.pushStatus(...)
local args = {...} local args = multi.pack(...)
__StatusLinda:send(nil,THREAD_ID, args) __StatusLinda:send(nil,THREAD_ID, args)
end end
@ -134,9 +122,30 @@ local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
__GlobalLinda:set(k, v) __GlobalLinda:set(k, v)
end 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
function THREAD.defer(func)
table.insert(_DEFER, func)
end
return GLOBAL, THREAD return GLOBAL, THREAD
end end
return {init = function(g,s,st,c) return {init = function(g,s,st,c,onexit)
return INIT(g,s,st,c) return INIT(g,s,st,c,onexit)
end} end}

View File

@ -1,105 +1,131 @@
--[[
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 if not ISTHREAD then
multi, thread = require("multi").init() multi, thread = require("multi").init()
GLOBAL = multi.integration.GLOBAL GLOBAL = multi.integration.GLOBAL
THREAD = multi.integration.THREAD THREAD = multi.integration.THREAD
else
end end
function multi:newSystemThreadedQueue(name) function multi:newSystemThreadedQueue(name)
local name = name or multi.randomString(16) local name = name or multi.randomString(16)
local c = {} local c = {}
c.Name = name c.Name = name
local fRef = {"func",nil} c.Type = multi.registerType("s_queue")
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() function c:init()
local q = {} self.chan = love.thread.getChannel(self.Name)
q.chan = love.thread.getChannel(self.Name) return self
function q:push(dat) end
if type(dat) == "function" then
fRef[2] = THREAD.dump(dat) function c:Hold(opt)
self.chan:push(fRef) local multi, thread = require("multi"):init()
return if opt.peek then
return thread.hold(function()
return self:peek()
end)
else else
self.chan:push(dat) return thread.hold(function()
return self:pop()
end)
end end
end end
function q:pop()
local dat = self.chan:pop() GLOBAL[name] = c
if type(dat)=="table" and dat[1]=="func" then
return THREAD.loadDump(dat[2]) self:create(c)
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)
return c return c
end end
function multi:newSystemThreadedTable(name) function multi:newSystemThreadedTable(name)
local name = name or multi.randomString(16) local name = name or multi.randomString(16)
local c = {} local c = {}
c.Name = name c.Name = name
c.Type = multi.registerType("s_table")
c.tab = THREAD.createTable(name)
function c:init() function c:init()
return THREAD.createTable(self.Name) self.tab = THREAD.createTable(self.Name)
setmetatable(self,{
__index = function(t, k)
return self.tab[k]
end,
__newindex = function(t,k,v)
self.tab[k] = v
end end
THREAD.package(name,c) })
return self
end
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]
end)
else
multi.error("Must provide a key to check opt.key = 'key'")
end
end
setmetatable(c,{
__index = function(t, k)
return c.tab[k]
end,
__newindex = function(t,k,v)
c.tab[k] = v
end
})
GLOBAL[name] = c
self:create(c)
return c return c
end end
local jqc = 1 local jqc = 1
function multi:newSystemThreadedJobQueue(n) function multi:newSystemThreadedJobQueue(n)
local c = {} local c = {}
c.cores = n or THREAD.getCores() c.cores = n or THREAD.getCores()
c.registerQueue = {} c.registerQueue = {}
c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") c.Type = multi.registerType("s_jobqueue")
c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") c.funcs = THREAD.createTable("__JobQueue_"..jqc.."_table")
c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue")
c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn")
c.queueAll = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueAll")
c.id = 0 c.id = 0
c.OnJobCompleted = multi:newConnection() c.OnJobCompleted = multi:newConnection()
local allfunc = 0 local allfunc = 0
function c:doToAll(func) function c:doToAll(func)
local f = THREAD.dump(func)
for i = 1, self.cores do for i = 1, self.cores do
self.queueAll:push({allfunc,f}) self.queueAll:push({allfunc, func})
end end
allfunc = allfunc + 1 allfunc = allfunc + 1
end end
function c:registerFunction(name,func) function c:registerFunction(name, func)
if self.funcs[name] then if self.funcs[name] then
error("A function by the name "..name.." has already been registered!") multi.error("A function by the name "..name.." has already been registered!")
end end
self.funcs[name] = func self.funcs[name] = func
end end
@ -126,13 +152,12 @@ function multi:newSystemThreadedJobQueue(n)
local rets local rets
link = c.OnJobCompleted(function(jid,...) link = c.OnJobCompleted(function(jid,...)
if id==jid then if id==jid then
rets = {...} rets = multi.pack(...)
link:Destroy()
end end
end) end)
return thread.hold(function() return thread.hold(function()
if rets then if rets then
return unpack(rets) or multi.NIL return multi.unpack(rets) or multi.NIL
end end
end) end)
end,holup),name end,holup),name
@ -142,7 +167,7 @@ function multi:newSystemThreadedJobQueue(n)
thread.yield() thread.yield()
local dat = c.queueReturn:pop() local dat = c.queueReturn:pop()
if dat then if dat then
c.OnJobCompleted:Fire(unpack(dat)) c.OnJobCompleted:Fire(multi.unpack(dat))
end end
end end
end) end)
@ -150,16 +175,15 @@ function multi:newSystemThreadedJobQueue(n)
multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc) multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc)
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
require("love.timer") require("love.timer")
local function atomic(channel) love.timer.sleep(1)
return channel:pop()
end
local clock = os.clock local clock = os.clock
local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") local funcs = THREAD.createTable("__JobQueue_"..jqc.."_table")
local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue") local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue")
local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn")
local lastProc = clock() local lastProc = clock()
local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local queueAll = THREAD.waitFor("__JobQueue_"..jqc.."_queueAll")
local registry = {} local registry = {}
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs}) setmetatable(_G,{__index = funcs})
thread:newThread("startUp",function() thread:newThread("startUp",function()
while true do while true do
@ -167,7 +191,7 @@ function multi:newSystemThreadedJobQueue(n)
local all = queueAll:peek() local all = queueAll:peek()
if all and not registry[all[1]] then if all and not registry[all[1]] then
lastProc = os.clock() lastProc = os.clock()
THREAD.loadDump(queueAll:pop()[2])() queueAll:pop()[2]()
end end
end end
end) end)
@ -178,20 +202,21 @@ function multi:newSystemThreadedJobQueue(n)
local all = queueAll:peek() local all = queueAll:peek()
if all and not registry[all[1]] then if all and not registry[all[1]] then
lastProc = os.clock() lastProc = os.clock()
THREAD.loadDump(queueAll:pop()[2])() queueAll:pop()[2]()
end end
local dat = queue:performAtomic(atomic) local dat = thread.hold(queue)
if dat then if dat then
multi:newThread("Test",function()
lastProc = os.clock() lastProc = os.clock()
local name = table.remove(dat,1) local name = table.remove(dat,1)
local id = 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) table.insert(tab,1,id)
--local test = queueReturn.push
queueReturn:push(tab) queueReturn:push(tab)
end)
end end
end end
end):OnError(function(...)
error(...)
end) end)
thread:newThread("Idler",function() thread:newThread("Idler",function()
while true do while true do
@ -206,144 +231,14 @@ function multi:newSystemThreadedJobQueue(n)
multi:mainloop() multi:mainloop()
end,jqc) end,jqc)
end end
function c:Hold(opt)
return thread.hold(self.OnJobCompleted)
end
jqc = jqc + 1 jqc = jqc + 1
return c
end
function multi:newSystemThreadedConnection(name) self:create(c)
local name = name or multi.randomString(16)
local c = {}
c.CONN = 0x00
c.TRIG = 0x01
c.PING = 0x02
c.PONG = 0x03
local subscribe = multi:newSystemThreadedQueue("SUB_STC_" .. name):init()
function c:init()
self.subscribe = THREAD.waitFor("SUB_STC_" .. self.Name):init()
function self:Fire(...)
local args = {...}
if self.CID == THREAD.getID() then -- Host Call
for _, link in pairs(self.links) do
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.getID() 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 = multi:newSystemThreadedQueue():init()
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, THREAD.waitFor(item[2]):init())
end
link_self_ref:pop()
elseif item[1] == self.TRIG then
self.proxy_conn:Fire(unpack(item[2]))
link_self_ref:pop()
else
-- This shouldn't be the case
end
end
end).OnError(print)
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.getID()
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 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
link:pop()
end
end
ping = thread:newFunction(function(self)
ping:Pause()
multi.ForEach(self.links, function(link) -- Sync new connections
link:push{self.PING}
multi:newThread("pong Thread", pong, link, links)
end)
thread.sleep(3)
ping:Resume()
end,false)
local function fire(...)
for _, link in pairs(c.links) do
link:push {c.TRIG, {...}}
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
THREAD.waitFor(item[2]):init():push{c.CONN, link.Name}
end)
print("Adding link")
c.links[#c.links+1] = THREAD.waitFor(item[2]):init()
elseif item[1] == c.TRIG then
fire(unpack(item[2]))
c.proxy_conn:Fire(unpack(item[2]))
end
end
end).OnError(print)
--- ^^^ This will only exist in the init thread
THREAD.package(name,c)
return c return c
end end

View File

@ -1,116 +1,137 @@
--[[
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 if ISTHREAD then
error("You cannot require the loveManager from within a thread!") error("You cannot require the loveManager from within a thread!")
end end
local ThreadFileData = [[ local ThreadFileData = [[
ISTHREAD = true ISTHREAD = true
THREAD = require("multi.integration.loveManager.threads") -- order is important! args = {...}
sThread = THREAD THREAD_ID = args[1]
__IMPORTS = {...} THREAD_NAME = args[2]
__FUNC__=table.remove(__IMPORTS,1) GLOBAL, THREAD, DEFER = require("multi.integration.loveManager.threads"):init()
__THREADID__=table.remove(__IMPORTS,1) __FUNC = THREAD.unpackValue(args[3])
__THREADNAME__=table.remove(__IMPORTS,1) ARGS = THREAD.unpackValue(args[4])
stab = THREAD.createStaticTable(__THREADNAME__) settings = args[5]
GLOBAL = THREAD.getGlobal() if ARGS == nil then ARGS = {} end
multi, thread = require("multi").init() math.randomseed(THREAD_ID)
print(pcall(require,"multi.integration.loveManager.extensions")) math.random()
stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))} math.random()
math.random()
stab = THREAD.createTable(THREAD_NAME .. THREAD_ID)
if GLOBAL["__env"] then
local env = THREAD.getENV()
for i,v in pairs(env) do
_G[i] = v
end
end
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")
local returns = {pcall(__FUNC, multi.unpack(ARGS))}
table.remove(returns,1)
stab["returns"] = returns
for i,v in pairs(DEFER) do
pcall(v)
end
]] ]]
local multi, thread = require("multi"):init()
local THREAD = {}
__THREADID__ = 0
__THREADNAME__ = "MainThread"
multi.integration={}
multi.integration.love2d={}
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,...) _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.registerType("s_function")
multi.registerType("s_thread")
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 = {} local c = {}
c.name = name c.Type = multi.STHREAD
c.ID=THREAD_ID c.Name = name
c.thread=love.thread.newThread(ThreadFileData) c.ID = tid
c.thread:start(THREAD.dump(func),c.ID,c.name,...) c.thread = love.thread.newThread(ThreadFileData)
c.stab = THREAD.createStaticTable(name) 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() c.OnDeath = multi:newConnection()
c.OnError = multi:newConnection() c.OnError = multi:newConnection()
GLOBAL["__THREAD_"..c.ID] = {ID=c.ID, Name=c.name, Thread=c.thread} c.status_channel = love.thread.getChannel("__status_channel__" .. c.ID)
GLOBAL["__THREAD_COUNT"] = THREAD_ID
THREAD_ID=THREAD_ID + 1 function c:getName() return c.name end
function c:getName()
return c.name table.insert(threads, c)
end
thread:newThread(function() c.OnError(multi.error)
if name:find("TempSystemThread") then
local status_channel = love.thread.getChannel("__"..c.ID.."__MULTI__STATUS_CHANNEL__") if self.isActor then
thread.hold(function() self:create(c)
-- While the thread is running we might as well do something in the loop
local status = status_channel
if status:peek()~=nil then
c.statusconnector:Fire(unpack(status:pop()))
end
return not c.thread:isRunning()
end)
else else
thread.hold(function() multi.create(multi, c)
return not c.thread:isRunning()
end)
end end
-- If the thread is not running let's handle that.
local thread_err = c.thread:getError()
if thread_err == "Thread Killed!\1" then
c.OnDeath:Fire("Thread Killed!")
elseif thread_err then
c.OnError:Fire(c,thread_err)
elseif c.stab.returns then
c.OnDeath:Fire(unpack(c.stab.returns))
c.stab.returns = nil
end
end)
return c return c
end end
function THREAD:newFunction(func) local started = false
local console_channel = love.thread.getChannel("__console_channel__")
function THREAD:newFunction(func, holdme)
return thread:newFunctionBase(function(...) return thread:newFunctionBase(function(...)
return multi:newSystemThread("TempSystemThread"..THREAD_ID,func,...) return multi:newSystemThread("SystemThreaded Function Handler", func, ...)
end)() end, holdme, multi.SFUNCTION)()
end end
THREAD.newSystemThread = multi.newSystemThread
function love.threaderror(thread, errorstr) function love.threaderror(thread, errorstr)
multi.print("Thread error!\n"..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 end
multi.integration.GLOBAL = GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD multi.integration.THREAD = THREAD
require("multi.integration.loveManager.extensions") require("multi.integration.loveManager.extensions")
require("multi.integration.sharedExtensions")
multi.print("Integrated Love Threading!") multi.print("Integrated Love Threading!")
return {init=function()
return GLOBAL,THREAD return {
end} init = function()
return GLOBAL, THREAD
end
}

View File

@ -25,144 +25,96 @@ require("love.timer")
require("love.system") require("love.system")
require("love.data") require("love.data")
require("love.thread") require("love.thread")
local socket = require("socket") local multi, thread = require("multi"):init()
local multi, thread = require("multi").init()
local threads = {}
function threads.loadDump(d) -- 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
return loadstring(d:getString()) 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 end
function threads.dump(func) -- Converts any function values in a table to a string with the value "\1\2:func:<function_string>" where <function_string> is the Lua stringified version of the function
return love.data.newByteData(string.dump(func)) function tableToFunctionString(t)
end if type(t) == "nil" then return "\1\2:nil:" end
if type(t) == "function" then return "\1\2:func:"..string.dump(t) end
local fRef = {"func",nil} if type(t) ~= "table" then return t end
local function manage(channel, value) local newtable = {}
channel:clear() for k, v in pairs(t) do
if type(value) == "function" then if type(v) == "function" then
fRef[2] = THREAD.dump(value) newtable[k] = "\1\2:func:"..string.dump(v)
channel:push(fRef) elseif type(v) == "table" then
return newtable[k] = tableToFunctionString(v)
elseif isLoveObject(v) then
newtable[k] = v
elseif type(v) == "userdata" then
newtable[k] = tostring(v)
else else
channel:push(value) newtable[k] = v
end 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 end
return table.concat(res) return newtable
end end
local GNAME = "__GLOBAL_" -- Converts strings with the value "\1\2:func:<function_string>" back to functions
local proxy = {} function functionStringToTable(t)
function threads.set(name,val) if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end
if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end
proxy[name]:performAtomic(manage, val) if type(t) ~= "table" then return t end
end for k, v in pairs(t) do
if type(v) == "string" then
function threads.get(name) if v:sub(1, 8) == "\1\2:func:" then
if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end t[k] = loadstring(v:sub(9, -1))
local dat = proxy[name]:peek()
if type(dat)=="table" and dat[1]=="func" then
return THREAD.loadDump(dat[2])
else else
return dat t[k] = v
end end
end elseif type(v) == "table" then
t[k] = functionStringToTable(v)
function threads.waitFor(name) else
if thread.isThread() then t[k] = v
return thread.hold(function()
return threads.get(name)
end)
end end
while threads.get(name)==nil do
love.timer.sleep(.001)
end end
local dat = threads.get(name) if t.init then
if type(dat) == "table" and dat.init then t:init()
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("__"..__THREADID__.."__MULTI__STATUS_CHANNEL__")
local args = {...}
status_channel:push(__THREADID__, args)
end
function threads.getThreads()
local t = {}
for i=1,GLOBAL["__THREAD_COUNT"] do
t[#t+1]=GLOBAL["__THREAD_"..i]
end end
return t return t
end end
function threads.getThread(n) local function packValue(t)
return GLOBAL["__THREAD_"..n] return tableToFunctionString(t)
end end
function threads.getName() local function unpackValue(t)
return __THREADNAME__ return functionStringToTable(t)
end end
function threads.getID() local function createTable(n)
return __THREADID__ if not n then
end n = "STAB"..multi.randomString(8)
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
} local __proxy = {}
) local function set(name, val)
end local chan = love.thread.getChannel(n .. name)
if chan:getCount() == 1 then chan:pop() end
function threads.createTable(n) __proxy[name] = true
local _proxy = {} chan:push(packValue(val))
local function set(name,val)
if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end
_proxy[name]:performAtomic(manage, val)
end end
local function get(name) local function get(name)
if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end return unpackValue(love.thread.getChannel(n .. name):peek())
local dat = _proxy[name]:peek() -- if type(data) == "table" and data.init then
if type(dat)=="table" and dat[1]=="func" then -- return data:init()
return THREAD.loadDump(dat[2]) -- else
else -- return data
return dat -- end
end
end end
return setmetatable({}, return setmetatable({},
{ {
@ -176,75 +128,101 @@ function threads.createTable(n)
) )
end end
function threads.getConsole() function INIT()
local GLOBAL, THREAD, DEFER = 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
THREAD.unpackValue = unpackValue
THREAD.createTable = createTable
function THREAD.set(name, val)
GLOBAL[name] = val
end
function THREAD.get(name, val)
return GLOBAL[name]
end
THREAD.waitFor = thread:newFunction(function(name)
local function wait()
math.randomseed(os.time())
thread.yield()
end
repeat
wait()
until GLOBAL[name] ~= nil
return GLOBAL[name]
end, true)
function THREAD.getCores()
return love.system.getProcessorCount()
end
function THREAD.getConsole()
local c = {} local c = {}
c.queue = love.thread.getChannel("__CONSOLE__") c.queue = console_channel
function c.print(...) function c.print(...)
c.queue:push{...} c.queue:push(table.concat(multi.pack(...), "\t"))
end end
function c.error(err) function c.error(err)
c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} c.queue:push("Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err)
error(err) multi.error(err)
end end
return c 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(multi.pack(...))
end
function THREAD.sleep(n)
love.timer.sleep(n)
end
THREAD.hold = thread:newFunction(function(n)
thread.hold(n)
end, true)
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
function THREAD.defer(func)
table.insert(DEFER, func)
end
function THREAD.sync()
-- Maybe do something...
end
return GLOBAL, THREAD, DEFER
end end
if not ISTHREAD then return {
local clock = os.clock init = function()
local lastproc = clock() return INIT()
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 end
if clock()-lastproc>2 then }
thread.sleep(.1)
end
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] = THREAD.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

View File

@ -118,13 +118,13 @@ function multi:newSystemThreadedJobQueue(n)
local rets local rets
link = c.OnJobCompleted(function(jid,...) link = c.OnJobCompleted(function(jid,...)
if id==jid then if id==jid then
rets = {...} rets = multi.pack(...)
link:Destroy() link:Destroy()
end end
end) end)
return thread.hold(function() return thread.hold(function()
if rets then if rets then
return unpack(rets) or multi.NIL return multi.unpack(rets) or multi.NIL
end end
end) end)
end,holup),name end,holup),name
@ -134,7 +134,7 @@ function multi:newSystemThreadedJobQueue(n)
thread.yield() thread.yield()
local dat = c.queueReturn:pop() local dat = c.queueReturn:pop()
if dat then if dat then
c.OnJobCompleted:Fire(unpack(dat)) c.OnJobCompleted:Fire(multi.unpack(dat))
end end
end end
end) end)
@ -152,6 +152,7 @@ function multi:newSystemThreadedJobQueue(n)
local lastProc = clock() local lastProc = clock()
local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll")
local registry = {} local registry = {}
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs}) setmetatable(_G,{__index = funcs})
thread:newThread("startUp",function() thread:newThread("startUp",function()
while true do while true do
@ -177,7 +178,7 @@ function multi:newSystemThreadedJobQueue(n)
lastProc = os.clock() lastProc = os.clock()
local name = table.remove(dat,1) local name = table.remove(dat,1)
local id = 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) table.insert(tab,1,id)
queueReturn:push(tab) queueReturn:push(tab)
end end

View File

@ -36,12 +36,14 @@ __THREADNAME__=table.remove(__IMPORTS,1)
stab = THREAD.createStaticTable(__THREADNAME__) stab = THREAD.createStaticTable(__THREADNAME__)
GLOBAL = THREAD.getGlobal() GLOBAL = THREAD.getGlobal()
multi, thread = require("multi").init() 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 multi, thread = require("multi.compat.lovr2d"):init()
local THREAD = {} local THREAD = {}
__THREADID__ = 0 __THREADID__ = 0
__THREADNAME__ = "MainThread" __THREADNAME__ = "MainThread"
_G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
multi.integration={} multi.integration={}
multi.integration.lovr2d={} multi.integration.lovr2d={}
local THREAD = require("multi.integration.lovrManager.threads") local THREAD = require("multi.integration.lovrManager.threads")
@ -58,7 +60,7 @@ function THREAD:newFunction(func,holup)
if t.stab["returns"] then if t.stab["returns"] then
local dat = t.stab.returns local dat = t.stab.returns
t.stab.returns = nil t.stab.returns = nil
return unpack(dat) return multi.unpack(dat)
end end
end) end)
end,holup)() end,holup)()
@ -74,16 +76,23 @@ function multi:newSystemThread(name,func,...)
GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread} GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread}
GLOBAL["__THREAD_COUNT"] = THREAD_ID GLOBAL["__THREAD_COUNT"] = THREAD_ID
THREAD_ID=THREAD_ID+1 THREAD_ID=THREAD_ID+1
if self.isActor then
self:create(c)
else
multi.create(multi, c)
end
return c return c
end end
THREAD.newSystemThread = multi.newSystemThread THREAD.newSystemThread = multi.newSystemThread
function lovr.threaderror(thread, errorstr) function lovr.threaderror(thread, errorstr)
print("Thread error!\n"..errorstr) multi.print("Thread error!\n"..errorstr)
end end
multi.integration.GLOBAL = GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD multi.integration.THREAD = THREAD
require("multi.integration.lovrManager.extensions") require("multi.integration.lovrManager.extensions")
print("Integrated lovr Threading!") multi.print("Integrated lovr Threading!")
return {init=function() return {init=function()
return GLOBAL,THREAD return GLOBAL,THREAD
end} end}

View File

@ -156,7 +156,7 @@ function threads.getConsole()
local c = {} local c = {}
c.queue = lovr.thread.getChannel("__CONSOLE__") c.queue = lovr.thread.getChannel("__CONSOLE__")
function c.print(...) function c.print(...)
c.queue:push{...} c.queue:push(multi.pack(...))
end end
function c.error(err) function c.error(err)
c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}
@ -174,7 +174,7 @@ if not ISTHREAD then
dat = queue:pop() dat = queue:pop()
if dat then if dat then
lastproc = clock() lastproc = clock()
print(unpack(dat)) print(multi.unpack(dat))
end end
if clock()-lastproc>2 then if clock()-lastproc>2 then
thread.sleep(.1) thread.sleep(.1)

View File

@ -35,7 +35,7 @@ local function _INIT(luvitThread, timer)
end end
-- Step 1 get setup threads on luvit... Sigh how do i even... -- Step 1 get setup threads on luvit... Sigh how do i even...
local multi, thread = require("multi").init() local multi, thread = require("multi").init()
isMainThread = true multi.isMainThread = true
function multi:canSystemThread() function multi:canSystemThread()
return true return true
end end
@ -107,7 +107,7 @@ local function _INIT(luvitThread, timer)
local c = {} local c = {}
local __self = c local __self = c
c.name = name c.name = name
c.Type = "sthread" c.Type = multi.STHREAD
c.thread = {} c.thread = {}
c.func = string.dump(func) c.func = string.dump(func)
function c:kill() function c:kill()

View File

@ -1,141 +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.
]]
local multi, thread = require("multi"):init()
local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD
local function stripUpValues(func)
local dmp = string.dump(func)
if setfenv then
return loadstring(dmp,"IsolatedThread_PesudoThreading")
else
return load(dmp,"IsolatedThread_PesudoThreading","bt")
end
end
function multi:newSystemThreadedQueue(name)
local c = {}
function c:push(v)
table.insert(self,v)
end
function c:pop()
return table.remove(self,1)
end
function c:peek()
return self[1]
end
function c:init()
return self
end
GLOBAL[name or "_"] = c
return c
end
function multi:newSystemThreadedTable(name)
local c = {}
function c:init()
return self
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
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 = {}
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()
print(#jobs)
return #jobs == 0
end
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
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 = {...}
link:Destroy()
end
end)
return thread.hold(function()
if rets then
return unpack(rets) or multi.NIL
end
end)
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]](unpack(j[3])))
else
thread.sleep(.05)
end
end
end)
end
return c
end

View File

@ -0,0 +1,213 @@
-- 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
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
return PList[1]
elseif avg < 0.0004 then
return PList[2]
elseif avg < 0.0008 then
return PList[3]
elseif avg < 0.001 then
return PList[4]
elseif avg < 0.0025 then
return PList[5]
elseif avg < 0.005 then
return PList[6]
elseif avg < 0.008 then
return PList[7]
elseif avg < 0.01 then
return PList[8]
else
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.registerType("process", "processes") or not(obj.IsAnActor) then return end
obj.__restoreProc = proc
obj.__profiling = {}
obj:reallocate(priorityManager)
end
local function init()
local registry = {}
multi.priorityScheme = {
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)
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
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
}
multi.priorityScheme[name] = name
return true
end
function multi:setPriorityScheme(scheme)
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
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
self.error("Invalid priority scheme selected!")
end
end
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()
priorityManager.run()
end
end)
end
if chronos then
init_chronos()
else
multi.warn("In order to have time based priority management, you need to install the chronos library!")
end
init()

View File

@ -0,0 +1,221 @@
--[[
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.
]]
local multi, thread = require("multi"):init()
local GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD
local function stripUpValues(func)
local dmp = string.dump(func)
if setfenv then
return loadstring(dmp,"IsolatedThread_PesudoThreading")
else
return load(dmp,"IsolatedThread_PesudoThreading","bt")
end
end
function multi:newSystemThreadedQueue(name)
local c = {}
c.data = {}
c.Type = multi.registerType("s_queue")
function c:push(v)
table.insert(self.data,v)
end
function c:pop()
return table.remove(self.data,1)
end
function c:peek()
return self.data[1]
end
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
function multi:newSystemThreadedTable(name)
local c = {}
c.Type = multi.registerType("s_table")
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 = multi.isolateFunction
local jqc = 1
function multi:newSystemThreadedJobQueue(n)
local c = {}
c.cores = n or THREAD.getCores()
c.registerQueue = {}
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")
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("STJQ_"..multi.randomString(8),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")
local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue")
local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn")
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
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
queueAll:pop()[2]()
end
local dat = thread.hold(queue)
if dat then
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))}
table.insert(tab,1,id)
queueReturn:push(tab)
end)
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
function multi:newSystemThreadedConnection(name)
local conn = multi:newConnection()
conn.init = function(self) return self end
GLOBAL[name or "_"] = conn
return conn
end
require("multi.integration.sharedExtensions")

View File

@ -24,6 +24,8 @@ SOFTWARE.
package.path = "?/init.lua;?.lua;" .. package.path package.path = "?/init.lua;?.lua;" .. package.path
local multi, thread = require("multi"):init() local multi, thread = require("multi"):init()
local pseudoProcessor = multi:newProcessor()
if multi.integration then if multi.integration then
return { return {
init = function() init = function()
@ -31,8 +33,12 @@ if multi.integration then
end end
} }
end end
multi.isMainThread = true
local activator = require("multi.integration.pseudoManager.threads")
local GLOBAL, THREAD = activator.init(thread)
local GLOBAL, THREAD = require("multi.integration.pesudoManager.threads").init(thread) _G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
function multi:canSystemThread() -- We are emulating system threading function multi:canSystemThread() -- We are emulating system threading
return true return true
@ -50,29 +56,44 @@ local function split(str)
return tab return tab
end 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) tab = split(tab)
local id = 0 local id = 0
function multi:newSystemThread(name,func,...)
GLOBAL["$THREAD_NAME"] = name function multi:newSystemThread(name, func, ...)
GLOBAL["$__THREADNAME__"] = name local env
GLOBAL["$THREAD_ID"] = id env = {
GLOBAL["$thread"] = thread
local env = {
GLOBAL = GLOBAL, GLOBAL = GLOBAL,
THREAD = THREAD, THREAD = THREAD,
THREAD_NAME = name, THREAD_NAME = tostring(name),
__THREADNAME__ = name, __THREADNAME__ = tostring(name),
THREAD_ID = id, THREAD_ID = id,
thread = thread thread = thread,
multi = multi,
} }
for i = 1,#tab do for i, v in pairs(_G) do
env[tab[i]] = _G[tab[i]] if not(env[i]) and not(i == "_G") and not(i == "local_global") then
env[i] = v
else
multi.warn("skipping:",i)
end
end end
local th = thread:newISOThread(name,func,env,...) if GLOBAL["__env"] then
for i,v in pairs(GLOBAL["__env"]) do
env[i] = v
end
end
env._G = env
local GLOBAL, THREAD = activator.init(thread, env)
local th = pseudoProcessor:newISOThread(name, func, env, ...)
th.Type = multi.registerType("s_thread", "pseudoThreads")
th.OnError(multi.error)
id = id + 1 id = id + 1
@ -86,14 +107,15 @@ THREAD.newSystemThread = multi.newSystemThread
function THREAD:newFunction(func,holdme) function THREAD:newFunction(func,holdme)
return thread:newFunctionBase(function(...) return thread:newFunctionBase(function(...)
return multi:newSystemThread("TempSystemThread",func,...) return multi:newSystemThread("TempSystemThread",func,...)
end,holdme)() end, holdme, multi.registerType("s_function", "pseudoFunctions"))()
end end
multi.print("Integrated Pesudo Threading!") multi.print("Integrated Pesudo Threading!")
multi.integration = {} -- for module creators multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD multi.integration.THREAD = THREAD
require("multi.integration.pesudoManager.extensions") require("multi.integration.pseudoManager.extensions")
require("multi.integration.sharedExtensions")
return { return {
init = function() init = function()
return GLOBAL, THREAD return GLOBAL, THREAD

View File

@ -33,6 +33,7 @@ end
local function INIT(thread) local function INIT(thread)
local THREAD = {} local THREAD = {}
local GLOBAL = {} local GLOBAL = {}
THREAD.Priority_Core = 3 THREAD.Priority_Core = 3
THREAD.Priority_High = 2 THREAD.Priority_High = 2
THREAD.Priority_Above_Normal = 1 THREAD.Priority_Above_Normal = 1
@ -80,31 +81,42 @@ local function INIT(thread)
THREAD.pushStatus = thread.pushStatus 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() function THREAD.kill()
error("Thread was killed!") error("Thread was killed!")
end end
function THREAD.getName()
return GLOBAL["$THREAD_NAME"]
end
function THREAD.getID()
return GLOBAL["$THREAD_ID"]
end
THREAD.sleep = thread.sleep THREAD.sleep = thread.sleep
THREAD.hold = thread.hold THREAD.hold = thread.hold
THREAD.defer = thread.defer
function THREAD.setENV(env, name)
name = name or "__env"
GLOBAL[name] = env
end
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!
local_global[i] = v
end
end
function THREAD.sync()
thread.sleep(.5)
end
return GLOBAL, THREAD return GLOBAL, THREAD
end end
return {init = function(thread) return {init = function(thread, global)
return INIT(thread) return INIT(thread, global)
end} end}

View File

@ -0,0 +1,336 @@
--[[ todo finish the targeted job!
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: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" or type(v) == "table" and v.Type == multi.registerType("s_function") then
table.insert(list, i)
elseif type(v) == "table" and v.Type == multi.registerType("connector", "connections") then
table.insert(list, {i, multi:newProxy(multi:chop(v)):init()})
end
end
return list
end
function multi:newProxy(list)
local c = {}
c.name = multi.randomString(12)
c.is_init = false
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(self.is_init) then
THREAD.sync()
self.is_init = true
local multi, thread = require("multi"):init()
self.proxy_link = "PL" .. multi.randomString(12)
if multi.integration then
GLOBAL = multi.integration.GLOBAL
THREAD = multi.integration.THREAD
end
GLOBAL[self.proxy_link] = self
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.registerType("proxy", "proxies")
self.TID = THREAD_ID
thread:newThread("Proxy_Handler_" .. multi.randomString(4), function()
while true do
local data = thread.hold(check)
if data then
-- 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
for i = 1,#ret do
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
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
table.insert(ret, 1, func)
self.recv:push(ret)
end)
end
end
end)
return self
else
THREAD.sync()
if not self.funcs then return self 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
end
local multi, thread = require("multi"):init()
local me = self
local funcs = copy(self.funcs)
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.registerType("proxy", "proxies")
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]
v[2].Parent = self
setmetatable(v[2],getmetatable(multi:newConnection()))
else
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)
for i=1,#data do
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)
end
end)
end, true)
end
end
return self
end
end
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
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
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 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 = {}
setmetatable(c,{__index = multi})
c.Type = multi.registerType("s_process", "s_processes")
c.threads = {}
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)
function c:pushJob(ID, name, ...)
local tq = THREAD.waitFor(self.Name .. "_target_tq_" .. ID):init()
tq:push{name, jid, multi.pack(...)}
jid = jid - 1
return jid + 1
end
c.jobqueue:registerFunction("packObj",function(obj)
local multi, thread = require("multi"):init()
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 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, ...)
return packObj(obj)
end, true)
local implement = {
"newLoop",
"newTLoop",
"newUpdater",
"newEvent",
"newAlarm",
"newStep",
"newTStep",
"newService"
}
for _, method in pairs(implement) do
c[method] = thread:newFunction(function(self, ...)
proxy = self.spawnTask(method, ...)
proxy:init()
references[proxy] = self
return proxy
end, true)
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: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)
self.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()
return function() end -- return empty function
end
function c:getThreads()
return self.threads
end
function c:getFullName()
return self.parent:getFullName() .. "." .. c.Name
end
function c:getName()
return self.Name
end
function c.run()
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
return c
end

20
lovethreads/conf.lua Normal file
View File

@ -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

75
lovethreads/main.lua Normal file
View File

@ -0,0 +1,75 @@
package.path = "../?/init.lua;../?.lua;"..package.path
local multi, thread = require("multi"):init{print=true, warning = true, error=true}
local utils = require("multi.integration.loveManager.utils")
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) .. '('..type(v):sub(1,1)..'),'
end
return s .. '} '
else
return tostring(o)
end
end
local fpeople = utils.pack(people)
print("Pack:", dump(fpeople))
local people = utils.unpack(fpeople)
print("Unpack:", dump(people))
print(type(people[3]))
-- 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

1
lovethreads/multi Normal file
View File

@ -0,0 +1 @@
../

View File

@ -0,0 +1,39 @@
package = "multi"
version = "15.3-1"
source = {
url = "git://github.com/rayaman/multi.git",
tag = "15.3.1",
}
description = {
summary = "Lua Multi tasking library",
detailed = [[
This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration).
Check github for documentation.
]],
homepage = "https://github.com/rayaman/multi",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["multi"] = "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.pesudoManager"] = "integration/pesudoManager/init.lua",
["multi.integration.pesudoManager.extensions"] = "integration/pesudoManager/extensions.lua",
["multi.integration.pesudoManager.threads"] = "integration/pesudoManager/threads.lua",
["multi.integration.luvitManager"] = "integration/luvitManager.lua",
["multi.integration.threading"] = "integration/threading.lua",
--["multi.integration.networkManager"] = "integration/networkManager.lua",
}
}

View File

@ -0,0 +1,42 @@
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.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",
["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.sharedExtensions"] = "integration/sharedExtensions/init.lua",
["multi.integration.priorityManager"] = "integration/priorityManager/init.lua",
--["multi.integration.networkManager"] = "integration/networkManager.lua",
}
}

View File

@ -0,0 +1,42 @@
package = "multi"
version = "16.0-1"
source = {
url = "git://github.com/rayaman/multi.git",
tag = "v16.0.1",
}
description = {
summary = "Lua Multi tasking library",
detailed = [[
This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration).
Check github for documentation.
]],
homepage = "https://github.com/rayaman/multi",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["multi"] = "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.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",
["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.sharedExtensions"] = "integration/sharedExtensions/init.lua",
["multi.integration.priorityManager"] = "integration/priorityManager/init.lua",
--["multi.integration.networkManager"] = "integration/networkManager.lua",
}
}

39
tests/conf.lua Normal file
View File

@ -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 = "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

10
tests/main.lua Normal file
View File

@ -0,0 +1,10 @@
package.path = "../?/init.lua;../?.lua;"..package.path
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
require("lldebugger").start()
end
GLOBAL, THREAD = require("multi.integration.loveManager"):init()
require("runtests")
require("threadtests")

1
tests/multi Symbolic link
View File

@ -0,0 +1 @@
../

View File

@ -1,35 +1,18 @@
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then package.path = "../?/init.lua;../?.lua;./init.lua;./?.lua;"..package.path
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 local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true}
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 good = false local good = false
local proc = multi:newProcessor("Test",true) local proc = multi:newProcessor("Test")
proc.Start()
proc:newAlarm(3):OnRing(function() proc:newAlarm(3):OnRing(function()
good = true good = true
end) end)
runTest = thread:newFunction(function() runTest = thread:newFunction(function()
local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false 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) proc:newAlarm(2):OnRing(function(a)
alarms = true alarms = true
a:Destroy() a:Destroy()
@ -59,18 +42,18 @@ runTest = thread:newFunction(function()
event.OnEvent(function(evnt) event.OnEvent(function(evnt)
evnt:Destroy() evnt:Destroy()
events = true events = true
print("Alarms: Ok") multi.success("Alarms: Ok")
print("Events: Ok") multi.success("Events: Ok")
if tsteps == 10 then print("TSteps: Ok") else print("TSteps: Bad!") end if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end
if steps == 10 then print("Steps: Ok") else print("Steps: Bad!") end if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end
if loops > 100 then print("Loops: Ok") else print("Loops: Bad!") end if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end
if tloops > 10 then print("TLoops: Ok") else print("TLoops: Bad!") end if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end
if updaters > 100 then print("Updaters: Ok") else print("Updaters: Bad!") end if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end
end) end)
thread.hold(event.OnEvent) thread.hold(event.OnEvent)
print("Starting Connection and Thread tests!") multi.print("Starting Connection and Thread tests!")
func = thread:newFunction(function(count) func = thread:newFunction(function(count)
print("Starting Status test: ",count) multi.print("Starting Status test: ",count)
local a = 0 local a = 0
while true do while true do
a = a + 1 a = a + 1
@ -85,13 +68,13 @@ runTest = thread:newFunction(function()
local ret3 = func(20) local ret3 = func(20)
local s1,s2,s3 = 0,0,0 local s1,s2,s3 = 0,0,0
ret.OnError(function(...) ret.OnError(function(...)
print("Func 1:",...) multi.error("Func 1:",...)
end) end)
ret2.OnError(function(...) ret2.OnError(function(...)
print("Func 2:",...) multi.error("Func 2:",...)
end) end)
ret3.OnError(function(...) ret3.OnError(function(...)
print("Func 3:",...) multi.error("Func 3:",...)
end) end)
ret.OnStatus(function(part,whole) ret.OnStatus(function(part,whole)
s1 = math.ceil((part/whole)*1000)/10 s1 = math.ceil((part/whole)*1000)/10
@ -104,31 +87,31 @@ runTest = thread:newFunction(function()
end) end)
ret.OnReturn(function(...) ret.OnReturn(function(...)
print("Done 1",...) multi.success("Done 1",...)
end) end)
ret2.OnReturn(function(...) ret2.OnReturn(function(...)
print("Done 2",...) multi.success("Done 2",...)
end) end)
ret3.OnReturn(function(...) ret3.OnReturn(function(...)
print("Done 3",...) multi.success("Done 3",...)
end) end)
local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn) local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn)
if s1 == 100 and s2 == 100 and s3 == 100 then if s1 == 100 and s2 == 100 and s3 == 100 then
print("Threads: All tests Ok") multi.success("Threads: All tests Ok")
else else
if s1>0 and s2>0 and s3 > 0 then if s1>0 and s2>0 and s3 > 0 then
print("Thread OnStatus: Ok") multi.success("Thread OnStatus: Ok")
else else
print("Threads OnStatus or thread.hold(conn) Error!") multi.error("Threads OnStatus or thread.hold(conn) Error!")
end end
if timeout then if timeout then
print("Connection Error!") multi.error("Connection Error!")
else else
print("Connection Test 1: Ok") multi.success("Connection Test 1: Ok")
end end
print("Connection holding Error!") multi.error("Connection holding Error!")
end end
conn1 = proc:newConnection() conn1 = proc:newConnection()
@ -157,29 +140,54 @@ runTest = thread:newFunction(function()
conn3:Fire() conn3:Fire()
if c1 and c2 and c3 and c4 then if c1 and c2 and c3 and c4 then
print("Connection Test 2: Ok") multi.success("Connection Test 2: Ok")
else else
print("Connection Test 2: Error") multi.error("Connection Test 2: Error")
end end
c3 = false c3 = false
c4 = false c4 = false
d:Destroy() conn3:Unconnect(d)
conn3:Fire() conn3:Fire()
if c3 and not(c4) then if c3 and not(c4) then
print("Connection Test 3: Ok") multi.success("Connection Test 3: Ok")
else else
print("Connection Test 3: Error removing connection") multi.error("Connection Test 3: Error removing connection")
end
if not love then
local ec = 0
multi.print("Testing pseudo threading")
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 end
os.exit() -- End of tests
end) end)
print(runTest().OnError(function(...) local handle = runTest()
print("Error: Something went wrong with the test!")
print(...)
os.exit(1)
end))
print("Pumping proc") handle.OnError(function(...)
while true do multi.error("Something went wrong with the test!")
proc.run() 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 end

View File

@ -1,45 +1,179 @@
package.path = "../?/init.lua;../?.lua;"..package.path 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,debugging=true}
GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -- for i,v in pairs(thread) do
multi:getOptimizationConnection()(function(msg) -- print(i,v)
print(msg) -- end
end)
local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection():fastMode(), multi:newConnection() -- require("multi.integration.priorityManager")
local link = conn1(function() -- multi.debugging.OnObjectCreated(function(obj, process)
print("Conn1, first") -- multi.print("Created:", obj.Type, "in", process.Type, process:getFullName())
end) -- end)
local link2 = conn1(function() -- multi.debugging.OnObjectDestroyed(function(obj, process)
print("Conn1, second") -- multi.print("Destroyed:", obj.Type, "in", process.Type, process:getFullName())
end) -- end)
local link3 = conn1(function()
print("Conn1, third")
end)
local link4 = conn2(function() -- test = multi:newProcessor("Test")
print("Conn2, first") -- test:setPriorityScheme(multi.priorityScheme.TimeBased)
end)
local link5 = conn2(function() -- test:newUpdater(10000000):OnUpdate(function()
print("Conn2, second") -- print("Print is slowish")
end) -- end)
local link6 = conn2(function() -- print("Running...")
print("Conn2, third")
end)
print("All conns\n-------------") -- local conn1, conn2 = multi:newConnection(), multi:newConnection()
conn1:Fire() -- conn3 = conn1 + conn2
conn2:Fire()
conn1:Unconnect(link3) -- conn1(function()
conn2:Unconnect(link6) -- print("Hi 1")
print("All conns Edit\n---------------------") -- end)
conn1:Fire()
conn2:Fire() -- 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)
-- proc:newAlarm(5):OnRing(function(a)
-- multi.print(";) Goodbye")
-- a:Destroy()
-- end)
-- local func = thread:newFunction(function()
-- thread.sleep(4)
-- print("Hello!")
-- end)
-- multi:newTLoop(func, 1)
-- multi:mainloop()
-- 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)
-- print("Fire 1")
-- conn:Fire()
-- conn = -conn
-- print("Fire 2")
-- 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()
-- 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() -- thread:newThread(function()
-- print("Awaiting status") -- print("Awaiting status")
@ -58,8 +192,10 @@ conn2:Fire()
-- multi:newAlarm(3):OnRing(function() -- multi:newAlarm(3):OnRing(function()
-- print("Conn3") -- print("Conn3")
-- conn3:Fire() -- conn3:Fire()
-- os.exit()
-- end) -- end)
-- local conn = multi:newSystemThreadedConnection("conn"):init() -- local conn = multi:newSystemThreadedConnection("conn"):init()
-- multi:newSystemThread("Thread_Test_1", function() -- multi:newSystemThread("Thread_Test_1", function()
@ -131,3 +267,32 @@ conn2:Fire()
-- end) -- end)
-- multi:mainloop() -- 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
]]

246
tests/threadtests.lua Normal file
View File

@ -0,0 +1,246 @@
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
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()
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")
defer_queue = multi:newSystemThreadedQueue("Defer_Queue")
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)
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()
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 '".. 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)
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")
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=2})
if val == multi.TIMEOUT then
multi.error("SystemThreadedTables: Failed")
os.exit(1)
end
multi.success("SystemThreadedTables: Ok")
local ready = false
jq = multi:newSystemThreadedJobQueue(4) -- Job queue with 4 worker threads
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
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=3})
if val == multi.TIMEOUT then
multi.error("SystemThreadedJobQueues: Failed")
os.exit(1)
end
multi.success("SystemThreadedJobQueues: Ok")
local proxy_test = false
local stp = multi:newSystemThreadedProcessor(5)
local tloop = stp:newTLoop(function()
--print("Test")
end, 1)
multi:newSystemThread("PROX_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())
local test = tloop:getTransferable()
multi.print("tloop", tloop.Type)
multi.print("tloop.OnLoop", tloop.OnLoop.Type)
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")
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).OnError(print)
thread:newThread(function()
thread.defer(function()
multi.print("Something happened!")
end)
while true do
thread.hold(tloop.OnLoop)
multi.print(THREAD_NAME,"Local Loopy")
end
end).OnError(function(...)
print("Error",...)
end)
tloop.OnLoop(function()
print("OnLoop", THREAD_NAME)
end)
t, val = thread.hold(function()
return proxy_test
end,{sleep=10})
if val == multi.TIMEOUT then
multi.error("SystemThreadedProcessor/Proxies: Failed")
os.exit(1)
else
multi.success("SystemThreadedProcessor: OK")
end
thread.sleep(2)
we_good = true
multi:Stop() -- Needed in love2d tests to stop the main runner
os.exit(0)
end)
multi.OnExit(function(err_or_errorcode)
multi.print("EC: ", err_or_errorcode)
if not we_good then
multi.print("There was an error running some tests!")
return
else
multi.success("Tests complete!")
end
end)
multi:mainloop()

1102
tests/vscode-debuggee.lua Normal file

File diff suppressed because it is too large Load Diff