Compare commits

..

183 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
8c3d66a7b1 Added Unconnect to connections. Allows fastmode connections to be removed 2022-12-25 21:14:34 -05:00
f5e95b8c9f Removed print statement 2022-12-20 15:11:00 -05:00
2f11a80a28 Fixed > 1 which prevented tasks from being processed if only on task was in queue 2022-12-20 14:35:13 -05:00
8b6b56164a Merge branch 'v15.3.0' of https://github.com/rayaman/multi into v15.3.0 2022-12-10 22:48:29 -05:00
bbc83acc0f Modified changes 2022-12-10 22:48:26 -05:00
=
0d6a9f92ad Working on getting STC working in love2d 2022-12-06 00:53:03 -05:00
=
4cf80517eb Updated docs 2022-12-06 00:03:27 -05:00
35a86a6cda Fixed code hanging due to thread.chain not processing varargs properly 2022-11-20 13:19:52 -05:00
019855d81f Fixed #args when args was a number 2022-11-20 12:31:37 -05:00
2a47de9fd8 Added thread.chain(...) 2022-11-20 12:17:55 -05:00
0d3de6e31d Fixed multiplying connections together 2022-11-07 10:01:22 -05:00
d6d4934a7e Tests failing with connections * connections, not counting triggers properly 2022-11-06 01:00:04 -04:00
6369450d2f Merge branch 'v15.3.0' of https://github.com/rayaman/multi into v15.3.0 2022-11-05 23:20:47 -04:00
a90b1d09b1 testing 2022-11-05 23:20:44 -04:00
92090f0a0a Merge branch 'v15.3.0' of https://github.com/rayaman/multi into v15.3.0 2022-11-05 23:19:36 -04:00
5cfd155241 Updated rockspec 2022-11-05 23:19:20 -04:00
0b2b2f9613
Merge pull request #49 from rayaman/testing_restruct
Restructure to allow submodules
2022-11-05 23:16:17 -04:00
a5b59ee601 testing 2022-11-05 22:21:19 -04:00
f240588edd Added changes to changelog 2022-10-31 00:09:18 -04:00
6abfb9c3d9 Fixed issue with opt_finding thread.hold 2022-10-30 23:54:56 -04:00
e5ac4d3d9e Testing new optimization features 2022-10-24 23:24:24 -04:00
7353fde799 Working on some nice features 2022-10-06 18:36:45 -04:00
4c088b9080 Working on new features 2022-09-29 23:10:39 -04:00
b727fb15b5 Reverted changes with code structure 2022-09-29 12:29:05 -04:00
57b4871a85 added missing files 2022-09-29 12:14:38 -04:00
3ae2acbd78 how did pseudothreading go away 2022-09-29 12:13:33 -04:00
df429b617e Changed the structure of the code 2022-09-29 12:12:24 -04:00
0d3a7b9393 Fixed some buges with love2d support 2022-09-29 11:57:37 -04:00
6de93c10af Fixed issue where the Connection Host wouldn't trigger itself when a non host calls Fire 2022-09-22 23:06:45 -04:00
a14cbb45d3 working on STC 2022-09-22 10:29:11 -04:00
827d3d8c45
Merge pull request #48 from rayaman/15.3.0-SystemThreadedConnections
15.3.0 system threaded connections
2022-09-22 10:28:12 -04:00
7bbb217018 Working with lanes, todo implement for love2d and test. 'Should work' the way it is with love2d 2022-09-21 23:16:38 -04:00
62e05788d2 Working on STC 2022-09-21 00:06:48 -04:00
772095431f Some changes to extensions, working on STC 2022-09-20 16:52:51 -04:00
bb7fab0857 working on systemthreadedconnections 2022-09-18 13:56:00 -04:00
1546076c80 Protection is handled by Fire Funcction 2022-09-18 12:22:34 -04:00
8f3eb6d9a3 Fixed changelog order 2022-09-18 09:36:47 -04:00
ccc4a15121 merge comflict 2022-09-18 09:35:41 -04:00
9050e65d93 Back and making progress 2022-09-18 09:33:13 -04:00
8f7183f998
Merge branch 'master' into 15.3.0-SystemThreadedConnections 2022-06-11 23:46:35 -04:00
3d07e774a0
Merge pull request #34 from rayaman/network_parallelism_test_branch
Network parallelism
2022-06-11 23:44:59 -04:00
99ba411781
Merge branch 'master' into network_parallelism_test_branch 2022-06-11 23:44:11 -04:00
23eaf42cad
Merge branch 'v15.3.0' into 15.3.0-SystemThreadedConnections 2022-06-11 23:42:07 -04:00
5bca4e3383
Merge pull request #46 from rayaman/v15.3.0
V15.3.0
2022-06-11 23:41:07 -04:00
=
e2bb964423 Removed print statement 2022-06-11 23:24:28 -04:00
=
0e2119a87d Added .zip to gitignore 2022-06-06 22:43:36 -04:00
=
7dfbf16bc1 Removed multi.zip 2022-06-06 22:42:53 -04:00
=
bac91574a2 Changed readme, added rockspec for 15.2.1 and 15.3.0 2022-06-06 22:42:30 -04:00
bf2f495812
Merge pull request #45 from rayaman/15.2.1
15.2.1
2022-06-06 19:29:40 -04:00
beddecc742
Merge pull request #44 from rayaman/master
Added rockspec and modified version number
2022-06-06 19:29:09 -04:00
86f31bbe52
Update README.md 2022-06-06 19:28:06 -04:00
d8f3db6972
Merge pull request #43 from rayaman/master
Merge pull request #42 from rayaman/15.2.1
2022-06-06 19:27:29 -04:00
=
494baf1e32 Added rockspec and modified version number 2022-06-06 19:26:41 -04:00
e14fa83681
Merge pull request #42 from rayaman/15.2.1
15.2.1
2022-06-06 19:24:35 -04:00
=
278b982aff Bugfix in 15.3.0 fixed in 15.2.1 2022-06-06 19:23:31 -04:00
=
89e76ce31d Fixed issue where threaded functions were exiting improperly 2022-06-06 19:19:35 -04:00
=
831abeaccf Testing functions 2022-05-28 23:04:51 -04:00
=
f74f85759b Cleaned up test files 2022-05-28 22:52:20 -04:00
7b70838567 Planning stuff 2022-04-23 21:07:56 -04:00
fcc0717115
Update README.md 2022-04-19 18:46:38 -04:00
47d8ac30c4
Merge pull request #33 from rayaman/v15.2.0
V15.2.0
2022-04-19 18:45:52 -04:00
3646a1a074 Syntax changes 2022-04-19 18:45:14 -04:00
b0ab40d410 Fixed some bugs, a little more testing needed 2022-04-19 18:39:30 -04:00
440995b1c8 Cleaned up rockspec file 2022-04-18 22:56:55 -04:00
8da4c4c1de made lanes optional(Removed it from the dependency list) 2022-04-18 22:51:38 -04:00
2cc2a57a46 Fixed system threads calling OnDeath incorrectly, finished full update showcase 2022-04-18 22:44:08 -04:00
6527dc1aaa Fixed test script to not use globally installed version and the dev version 2022-04-18 21:25:36 -04:00
bd49805c25 testing... 2022-04-18 21:04:44 -04:00
40dd293bf8 Fixed issue with pesudo threading not working properly 2022-04-18 21:03:14 -04:00
568c95fa73 Issue with love2d system threaded functions pushstatus fixed 2022-04-16 00:02:54 -04:00
d30ee3788e THREAD.pushStatus for lanes works, todo love 2022-04-11 23:48:45 -04:00
79f58a79f9 Love2d newFunction working 2022-04-10 20:00:20 -04:00
d8aeefd202 Working on love2d threaded functions 2022-04-02 23:06:06 -04:00
c0fb94ddbb Fixed and documented the newProcessor changes. Rockspec fix 2022-04-02 00:22:34 -04:00
e8a3cd731d Added ST to THD namespace, old way still works 2022-04-02 00:07:06 -04:00
03ffb6bc0a Removed locking 2022-02-25 14:36:21 -05:00
4240737e00 Working on taskmanager features 2022-02-20 21:07:04 -05:00
5172dcdf01 Reworking the taskdetails method 2022-02-17 10:00:55 -05:00
fc18a303dd Fixed issue with how the thread scheduler spawned threads 2022-02-15 21:47:07 -05:00
04d5500374 Fixed issue with the new thread scheduler(again) connections and hold get a buff 2022-02-15 15:11:28 -05:00
72e24e8a9b Added connection:hasConnections() 2022-02-14 18:00:02 -05:00
48bba84c08 Fix issue where I forgot to implement the handler for thread.yield 2022-02-13 00:16:00 -05:00
e9a0e7bbf7 Fixed an issue with the new thread namespace 2022-02-09 22:19:27 -05:00
264867a0da Fixed issue with threads not returning values properly 2022-02-09 19:29:06 -05:00
3f046afaa1 Fixed: missing a yield 2022-02-09 17:05:30 -05:00
2acce5001d Small issue with functions attached to processes 2022-02-09 16:30:19 -05:00
78cd15681a Removed actions for now 2022-02-08 22:51:46 -05:00
3401a8ac61 fixing issue with actions 2022-02-08 22:48:27 -05:00
19ac257204 Testing actions 2022-02-08 22:47:16 -05:00
c14a469069 Each processor has it's own thread handler, all processes now trigger the default thread handler. 2022-02-08 22:40:38 -05:00
03cea2d71a Processors are working nicely, mostly done with the library 2022-02-05 10:56:03 -05:00
593bfd0d8c Threads now working, can create many more threads without a performance hit 2022-02-05 00:55:11 -05:00
bf60a354d4 So the bug wasn't a bug all along 2022-02-04 17:27:13 -05:00
6c73220a52 working on scheduler, nothing fix yet 2022-01-31 17:11:14 -05:00
c3a9ddfdbd Reverted the extra space 2022-01-31 10:51:44 -05:00
e05f2ea400 last test 2022-01-31 10:50:45 -05:00
a60aae02c6 Cleanup spaces 2022-01-31 10:45:26 -05:00
207c5b8d69 test2 2022-01-31 09:52:43 -05:00
a9111f2fa3 test 2022-01-31 09:49:12 -05:00
b572bf218d Still debugging, not sure what is causing the thread to not yield properly 2022-01-31 09:47:37 -05:00
49c0bd3930 Fixing issues with the new thread scheduler, nested yields need handling 2022-01-31 08:31:38 -05:00
cdb4bfda11 Error handling not working if its on the first step 2022-01-28 17:50:19 -05:00
3fbead60d9 connections working, todo: error catching and return catching 2022-01-26 16:52:17 -05:00
2b122f5c77 scheduler is much faster, missing connection holding and error handling 2022-01-26 00:03:32 -05:00
472d1748ee Working on thread scheduler rework 2022-01-24 14:25:23 -05:00
47178dd3b3 Started to work on the scheduler rework 2022-01-24 09:00:51 -05:00
3fcba8825b Reworked priorities 2022-01-23 17:23:32 -05:00
e194a06427 Cleaned up the mainloop/umanager 2022-01-23 12:14:50 -05:00
32f7b4492b Improving performance in the mainloop 2022-01-22 23:34:15 -05:00
913745a1bc Merge branch 'v15.2.0' of https://github.com/rayaman/multi into v15.2.0 2022-01-20 13:07:01 -05:00
889dc6ca68 Removed unneeded files 2022-01-20 13:06:53 -05:00
2805137648
Merge branch 'master' into v15.2.0 2022-01-20 12:31:09 -05:00
f7167cf972 Current progress in v15.2.0 updated ignore file 2022-01-20 12:29:39 -05:00
9e1ecb3583 Testing... 2022-01-20 09:53:56 -05:00
b9b9b51d12 Taking testing seriously 2022-01-20 09:49:48 -05:00
a708fb3f83 testing stuff 2022-01-19 23:13:58 -05:00
4335f3ed14 Testing 2022-01-19 23:12:16 -05:00
8580d92c9c Fixing issues with priority 2022-01-19 23:09:47 -05:00
f1f6e30a98 testing 2022-01-19 13:41:29 -05:00
6c1e9f26f0 Reworking the loops 2022-01-18 23:39:17 -05:00
d98f353936 Tweaked processor object, added lightloop and lmanager 2022-01-17 23:23:56 -05:00
14c8665910
Update README.md 2022-01-16 16:30:45 -05:00
f7452db3ec spaces to tabs 2022-01-16 16:30:03 -05:00
588923e1b7 spaces to tabs 2022-01-16 16:26:37 -05:00
4877f64ca1 Fix some bugs, added connection/thread tests 2022-01-16 16:18:17 -05:00
b8b31253d4 Added key feature 2022-01-11 23:16:33 -05:00
af4672245e Removed some tests 2022-01-11 23:04:35 -05:00
609613dbe9 Fixed lua 5.4 issue and the taskstatus 2022-01-11 18:59:53 -05:00
b16593425b Fixed typo in changes.md 2022-01-09 23:32:54 -05:00
997ea48b54 Writing tests, fixed some bugs with the library, testing luajit support 2022-01-09 23:23:54 -05:00
537dcf0db1 Working on performance 2022-01-04 18:01:02 -05:00
b74a6c006e Updated lanes integration 2022-01-02 12:18:35 -05:00
b3453d028c Fixed the lanes issue? 2022-01-02 11:41:39 -05:00
6842147522 Updated gitignore 2022-01-02 11:25:42 -05:00
de34b9dc59
Merge pull request #32 from rayaman/v16.0.0
Updated license
2022-01-01 23:21:58 -05:00
dff19d865f Updated license 2022-01-01 23:20:48 -05:00
544aa78d70 Updated code 2021-12-24 22:47:38 -05:00
91d0b5f7be added some spaces 2021-12-19 01:05:34 -05:00
27e03a2546 bug fixing should be done 2021-12-18 12:55:20 -05:00
7dbcd01c33
Merge pull request #31 from rayaman/V15.2.0
working on 16.0
2021-12-18 12:42:58 -05:00
cc20914391 working on 1.16 2021-12-18 12:42:14 -05:00
c2aa449a65
Merge pull request #26 from rayaman/V15.1.0
V15.1.0
2021-11-30 21:28:18 -05:00
57563688ae fixed a bug 2021-11-30 21:23:45 -05:00
317dacd0de Updated readme and documentation 2021-11-30 20:54:54 -05:00
a7ba146a64 Cleaning up code, fixing bugs (Almost ready for release) 2021-11-30 20:11:20 -05:00
efa30e30cc Updated changes, connections can be added 2021-11-27 17:55:13 -05:00
197b418fc5 Fixed typo 2021-10-10 09:12:42 -04:00
d3d53599f7 function's are now callable objects 2021-10-10 09:10:24 -04:00
bf517facd1 Adding features to threaded functions 2021-07-19 17:04:43 -04:00
9cff2735ba threaded functions can now be paused 2021-07-05 22:23:45 -04:00
ea77b934b6 Updated version number 2021-07-02 21:13:47 -04:00
74bfd571a5 Rockspecs/Readme Updated 2021-07-02 16:57:58 -04:00
06132fc1dd Fixed rockspec for threading.lua, tweaking code 2021-07-02 15:46:37 -04:00
ade5172f26 Renamed lovr to lovrManager to fit theme 2021-06-24 23:11:10 -04:00
bdc657771d Changelog/Documentation updated. Processors added 2021-06-24 23:09:15 -04:00
8c24bcbbb0 Modified changes.md 2021-06-19 21:09:32 -04:00
804a117ed0 Fixed return bug 2021-06-19 20:12:06 -04:00
37afd37f9e Adding tests wip 2021-05-27 16:47:45 -04:00
4399fb6424 updated readme 2021-05-02 22:31:29 -04:00
02a54e13ea Started to work on lovr integration 2021-05-02 22:26:35 -04:00
bb2c7d6440
Merge pull request #20 from rayaman/V15.1.0
Fixed issue with lightloop not triggering the preload event
2021-05-02 22:04:54 -04:00
d1b8ed1922 Fixed issue with lightloop not triggering the preload event 2021-05-02 22:04:25 -04:00
9992a2c091
Merge pull request #19 from rayaman/v15.0.0
V15.0.0
2021-05-01 17:33:58 -04:00
155466dc71
Merge branch 'master' into v15.0.0 2021-05-01 17:33:23 -04:00
cea6508d68 Release ready 2021-05-01 17:31:14 -04:00
726707eb8a Modifying rockspec 2021-05-01 16:35:31 -04:00
9a9d28f62f Cleaning up... 2021-05-01 16:21:34 -04:00
ed924a3d9d Version 15.0.0 2021-05-01 15:52:32 -04:00
d2ce7e070b Fixing pseudothreading 2020-05-15 01:38:24 -04:00
300827b7bd cleaning up 2020-05-08 17:44:16 -04:00
9d97eac146 Working on psuedo threading 2020-05-08 11:41:52 -04:00
61dcb9da01 Working on 15.0.0 2020-03-29 11:50:15 -04:00
66 changed files with 9414 additions and 2881 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

15
.gitignore vendored
View File

@ -1,14 +1,3 @@
test2.lua
*.mp3
*.exe
lanestestclient.lua
lanestest.lua
sample-node.lua
sample-master.lua
Ayn Rand - The Virtue of Selfishness-Mg4QJheclsQ.m4a
Atlas Shrugged by Ayn Rand Audiobook-9s2qrEau63E.webm
test.lua
test.lua
*.code-workspace
*.dat
lua5.4/*
test.lua

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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,44 +1,87 @@
# Multi Version: 14.2.0 Documentation Complete, Bloat removed!
# Multi Version: 16.0.1 - Bug fix
Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and I'll look into it!
Found an issue? Please [submit it](https://github.com/rayaman/multi/issues) and someone will look into it!
My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations and the love2d compat. If you find any bugs or have any issues, please [let me know](https://github.com/rayaman/multi/issues) and I'll look into it!.
My multitasking library for lua. It is a pure lua binding, with exceptions of the integrations.
</br>
Progress is being made in [v16.1.0](https://github.com/rayaman/multi/tree/v16.1.0)
---
</br>
INSTALLING
----------
Links to dependencies:
[lanes](https://github.com/LuaLanes/lanes)
Link to optional dependencies:
- [lanes](https://github.com/LuaLanes/lanes) `luarocks install lanes`
- [chronos](https://github.com/ldrumm/chronos) `luarocks install chronos`
- [love2d](https://love2d.org/)
When using love2d add multi:uManager() or any processor to love.update()
```lua
function love.update(dt)
multi:uManager()
end
```
To install copy the multi folder into your environment and you are good to go</br>
If you want to use the system threads, then you'll need to install lanes!
**or** use luarocks `luarocks install multi`
If you want to use the system threads, then you'll need to install lanes or love2d game engine!
Going forward I will include a Release zip for love2d.
**The Network Manager rework is currently being worked on and the old version is not included in this version.**
```
luarocks install multi
```
Discord
-------
Have a question that you need asking? Or need realtime assistance? Feel free to join the discord!</br>
https://discord.gg/U8UspuA</br>
Have a question or need realtime assistance? Feel free to join the discord!</br>
https://discord.gg/U8UspuA
Planned features/TODO
---------------------
- [x] ~~Finish Documentation~~ Finished
- [x] ~~Create test suite (In progress, mostly done)~~
- [ ] Network Parallelism rework
- [ ] Fix some bugs
Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)</br>
Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/Documentation.md)
-----
You can run tests in 2 ways:
```
lua tests/runtests.lua (Runs all tests, attempts to use lanes)
love tests (Runs all tests in love2d env)
```
```lua
local multi, thread = require("multi").init()
mutli:newThread("Example",function()
local multi, thread = require("multi"):init()
GLOBAL, THREAD = require("multi.integration.threading"):init()
multi:newSystemThread("System Thread",function()
while true do
thread.sleep(1)
print("Hello!")
THREAD.sleep(.1)
io.write(" World")
THREAD.kill()
end
end)
multi:lightloop()
--multi:mainloop()
multi:newThread("Coroutine Based Thread",function()
while true do
io.write("Hello")
thread.sleep(.1)
thread.kill()
end
end)
multi:newTLoop(function(loop)
print("!")
loop:Destroy()
os.exit()
end,.3)
multi:mainloop()
--[[
while true do
multi:uManager()

View File

@ -1,8 +1,10 @@
Current Multi Version: 14.2.0
Current Multi Version: 15.1.0
# Multi static variables
`multi.Version` — The current version of the library
`multi.TIMEOUT` — The value returned when a timed method times out
`multi.Priority_Core` — Highest level of pirority that can be given to a process
</br>`multi.Priority_Very_High`
</br>`multi.Priority_High`
@ -14,12 +16,80 @@ Current Multi Version: 14.2.0
</br>`multi.Priority_Idle` — Lowest level of pirority that can be given to a process
# Multi Runners
`multi:lightloop()` — A light version of the mainloop
`multi:lightloop()` — A light version of the mainloop doesn't run Coroutine based threads
</br>`multi:loveloop([BOOLEAN: light true])` — Run's all the love related features as well
</br>`multi:mainloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running
</br>`multi:threadloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running, but prioritizes threads over multi-objects
</br>`multi:uManager([TABLE settings])` — This runs the mainloop, but does not have its own while loop and thus needs to be within a loop of some kind.
# Global Methods
`multi:init()` — Uesd to initiate the library, should only be called once
`multi.getCurrentProcess()` — Returns currently running Process
`multi.`
# Processor Methods
These methods can be called either on the multi namespace or a process returned by `proc = multi:newProcessor()`
`proc.Stop()` — Stops the main process/child process. **Note:** If the main process is stopped all child processes are stopped as well
`proc:getTasksDetails([STRING: displaytype])` — Gets a table or string of all the running tasks
Processor Attributes
---
| Attribute | Type | Returns | Description |
---|---|---|---
Start|Method()|self| Starts the process
Stop|Method()|self| Stops the process
OnError|Connection|connection| Allows connection to the process error handler
Type|Member:`string`|"process"| Contains the type of object
Active|Member:`boolean`|variable| If false the process is not active
Name|Member:`string`|variable| The name set at process creation
process|Thread|thread| A handle to a multi thread object
[Refer to the objects for more methods](#non-actors)
Example:
```lua
package.path = "./?/init.lua;"..package.path
multi,thread = require("multi"):init()
-- Create a processor object, it works a lot like the multi object
sandbox = multi:newProcessor()
-- On our processor object create a TLoop that prints "testing..." every second
sandbox:newTLoop(function()
print("testing...")
end,1)
-- Create a thread on the processor object
sandbox:newThread("Test Thread",function()
-- Create a counter named 'a'
local a = 0
-- Start of the while loop that ends when a = 10
while true do
-- pause execution of the thread for 1 second
thread.sleep(1)
-- increment a by 1
a = a + 1
-- display the name of the current process
print("Thread Test: ".. multi.getCurrentProcess().Name)
if a == 10 then
-- Stopping the processor stops all objects created inside that process including threads. In the backend threads use a regular multiobject to handle the scheduler and all of the holding functions. These all stop when a processor is stopped. This can be really useful to sandbox processes that might need to turned on and off with ease and not having to think about it.
sandbox.Stop()
end
end
-- Catch any errors that may come up
end).OnError(function(...)
print(...)
end)
sandbox.Start() -- Start the process
multi:mainloop() -- The main loop that allows all processes to continue
```
# Multi Settings
**Note:** Most settings have been fined tuned to be at the peak of performance already, however preLoop, protect (Which drastically lowers preformance), and stopOnError should be used freely to fit your needs.
@ -150,10 +220,13 @@ returns or nil
The connect feature has some syntax sugar to it as seen below
- `link = conn(FUNCTION func, [STRING name nil], [NUMBER #conns+1])`
- `combinedconn = conn1 + conn2` — A combined connection is triggered when all connections are triggered. See example [here](#coroutine-based-threading-cbt)
Example:
```lua
local multi = require("multi")
multi,thread = require("multi"):init()
-- Lets create the events
yawn={}
OnCustomSafeEvent=multi:newConnection(true) -- lets pcall the calls in case something goes wrong default
@ -201,7 +274,7 @@ Timeouts are a collection of methods that allow you to handle timeouts. These on
```lua
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
multi = require("multi")
multi,thread = require("multi"):init()
loop = multi:newLoop(function()
-- do stuff
@ -224,7 +297,7 @@ loop:OnTimerResolved(function(self,...)
print(...)
end)
multi:lightloop()
multi:mainloop()
```
As mentioned above this is made much easier using threads
```lua
@ -255,14 +328,14 @@ print(func(0))
Example:
```lua
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
multi = require("multi")
multi,thread = require("multi"):init()
multi:scheduleJob({min = 30},function() -- Every hour at minute 30 this event will be triggered! You can mix and match as well!
print("Hi")
end)
multi:scheduleJob({min = 30,hour = 0},function() -- Every day at 12:30AM this event will be triggered
print("Hi")
end)
multi:lightloop()
multi:mainloop()
```
# Universal Actor methods
@ -287,7 +360,7 @@ All of these functions are found on actors
Example:
```lua
local multi = require("multi")
multi,thread = require("multi"):init()
count=0
-- A loop object is used to demostrate how one could use an event object.
loop=multi:newLoop(function(self,dt)
@ -298,7 +371,7 @@ event:OnEvent(function(self) -- connect to the event object
loop:Destroy() -- destroys the loop from running!
print("Stopped that loop!",count)
end) -- events like alarms need to be reset the Reset() command works here as well
multi:lightloop()
multi:mainloop()
```
# Actor: Updaters
@ -311,12 +384,12 @@ Updaters are a mix between both loops and steps. They were a way to add basic pr
Example:
```lua
local multi = require("multi")
multi,thread = require("multi"):init()
updater=multi:newUpdater(5000) -- simple, think of a loop with the skip feature of a step
updater:OnUpdate(function(self)
print("updating...")
end)
multi:lightloop()
multi:mainloop()
```
# Actor: Alarms
@ -328,13 +401,13 @@ Alarms ring after a certain amount of time, but you need to reset the alarm ever
Example:
```lua
local multi = require("multi")
multi,thread = require("multi"):init()
alarm=multi:newAlarm(3) -- in seconds can go to .001 uses the built in os.clock()
alarm:OnRing(function(a)
print("3 Seconds have passed!")
a:Reset(n) -- if n were nil it will reset back to 3, or it would reset to n seconds
end)
multi:lightloop()
multi:mainloop()
```
# Actor: Loops
@ -346,7 +419,7 @@ Loops are events that happen over and over until paused. They act like a while l
Example:
```lua
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
local a = 0
loop = multi:newLoop(function()
a = a + 1
@ -355,7 +428,7 @@ loop = multi:newLoop(function()
loop:Pause()
end
end)
multi:lightloop()
multi:mainloop()
```
# Actor: TLoops
@ -366,7 +439,7 @@ multi:lightloop()
Example:
```lua
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
local a = 0
loop = multi:newTLoop(function()
a = a + 1
@ -375,7 +448,7 @@ loop = multi:newTLoop(function()
loop:Pause()
end
end,1)
multi:lightloop()
multi:mainloop()
```
# Actor: Steps
@ -390,13 +463,13 @@ multi:lightloop()
Example:
```lua
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
multi:newStep(1,10,1,0):OnStep(function(step,pos)
print(step,pos)
end):OnEnd(fucntion(step)
step:Destroy()
end)
multi:lightloop()
multi:mainloop()
```
# Actor: TSteps
@ -411,13 +484,13 @@ multi:lightloop()
Example:
```lua
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
multi:newTStep(1,10,1,1):OnStep(function(step,pos)
print(step,pos)
end):OnEnd(fucntion(step)
step:Destroy()
end)
multi:lightloop()
multi:mainloop()
```
# Coroutine based Threading (CBT)
@ -425,7 +498,23 @@ Helpful methods are wrapped around the builtin coroutine module which make it fe
**threads.\* used within threaded enviroments**
- `thread.sleep(NUMBER n)` — Holds execution of the thread until a certain amount of time has passed
- `VARIABLE returns = thread.hold(FUNCTION func)` — Hold execution until the function returns non nil. All returns are passed to the thread once the conditions have been met. To pass nil use `multi.NIL`\*
- `VARIABLE val = THREAD.hold(FUNCTION|CONNCETION|NUMBER func, TABLE options)` — Holds the current thread until a condition is met
| Option | Description |
---|---
| interval | Time between each poll |
| cycles | Number of cycles before timing out |
| sleep | Number of seconds before timing out |
| skip | Number of cycles before testing again, does not cause a timeout! |
**Note:** cycles and sleep options cannot both be used at the same time. Interval and skip cannot be used at the same time either. Cycles take priority over sleep if both are present! HoldFor and HoldWithin can be emulated using the new features. Old functions will remain for backward compatibility.
Using cycles, sleep or interval will cause a timeout; returning nil, multi.TIMEOUT
`func` can be a number and `thread.hold` will act like `thread.sleep`. When `func` is a number the option table will be ignored!
`func` can be a connection and will hold until the condition is triggered. When using a connection the option table is ignored!
- `thread.skip(NUMBER n)` — How many cycles should be skipped until I execute again
- `thread.kill()` — Kills the thread
- `thread.yeild()` — Is the same as using thread.skip(0) or thread.sleep(0), hands off control until the next cycle
@ -438,9 +527,64 @@ Helpful methods are wrapped around the builtin coroutine module which make it fe
- `th = thread.getRunningThread()` — Returns the currently running thread
- `VARIABLE returns or nil, "TIMEOUT" = thread.holdFor(NUMBER: sec, FUNCTION: condition)` — Holds until a condidtion is met, or if there is a timeout nil,"TIMEOUT"
- `VARIABLE returns or nil, "TIMEOUT" = thread.holdWithin(NUMBER: skip, FUNCTION: func)` — Holds until a condition is met or n cycles have happened.
- `returns or handler = thread:newFunction(FUNCTION: func, [BOOLEAN: holdme false])` — func: The function you want to be threaded. holdme: If true the function waits until it has returns and then returns them. Otherwise the function returns a table
- `handler.connect(Function: func(returns))` — Connects to the event that is triggered when the returns are avaiable
- `VARIAABLE returns = handler.wait()` — Waits until returns are avaiable and then returns them
- `func = thread:newFunction(FUNCTION: func, [BOOLEAN: holdme false])` — func: The function you want to be threaded. holdme: If true the function waits until it has returns and then returns them. Otherwise the function returns a table
- `func:Pause()` — Pauses a function, function will return `nil`, `"Function is paused"`
- `func:Resume()` — Resumes a paused function
- `func:holdMe(BOOLEAN: set)` — Sets the holdme argument to `set`
- `handler = func(VARIABLE args)` — Calls the function, will return
- `handler.isTFunc` — if true then its a threaded function
- `handler.wait()` — waits for the function to finish and returns like normal
- `handler.connect(Function: func(returns))` — Connects to the event that is triggered when the returns are avaiable and returns them
- `VARIABLE returns = handler.wait()` — Waits until returns are avaiable and then
- `handler.OnStatus(connector(VARIABLE args))` — A connection to the running function's status see example below
- `handler.OnReturn(connector(VARIABLE args))` — A connection that is triggered when the running function is finished see example below
- `handler.OnError(connector(nil,error))`
Example:
```lua
package.path = "./?/init.lua;"..package.path
multi,thread = require("multi"):init()
func = thread:newFunction(function(count)
local a = 0
while true do
a = a + 1
thread.sleep(.1)
thread.pushStatus(a,count)
if a == count then break end
end
return "Done"
end)
multi:newThread("Function Status Test",function()
local ret = func(10)
local ret2 = func(15)
local ret3 = func(20)
ret.OnStatus(function(part,whole)
--[[ Print out the current status. In this case every second it will update with:
10%
20%
30%
...
100%
Function Done!
]]
print(math.ceil((part/whole)*1000)/10 .."%")
end)
ret2.OnStatus(function(part,whole)
print("Ret2: ",math.ceil((part/whole)*1000)/10 .."%")
end)
ret3.OnStatus(function(part,whole)
print("Ret3: ",math.ceil((part/whole)*1000)/10 .."%")
end)
-- Connections can now be added together, if you had multiple holds and one finished before others and wasn't consumed it would lock forever! This is now fixed
thread.hold(ret2.OnReturn + ret.OnReturn + ret3.OnReturn)
print("Function Done!")
os.exit()
end)
```
<b>\*</b>A note about multi.NIL, this should only be used within the hold and hold like methods. thread.hold(), thread.holdFor(), and thread.holdWithin() methods. This is not needed within threaded functions! The reason hold prevents nil and false is because it is testing for a condition so the first argument needs to be non nil nor false! multi.NIL should not be used anywhere else. Sometimes you may need to pass a 'nil' value or return. While you could always return true or something you could use multi.NIL to force a nil value through a hold like method.
@ -474,7 +618,7 @@ Example:
-- Jobs are not natively part of the multi library. I planned on adding them, but decided against it. Below is the code that would have been used.
-- Implementing a job manager using services
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
multi.Jobs = multi:newService(function(self,jobs)
local job = table.remove(jobs,1)
if job and job.removed==nil then
@ -532,6 +676,7 @@ jobsn[1]:removeJob() -- Select a job and remove it
multi.Jobs:removeJobs("test2") -- Remove all jobs names 'test2'
multi.Jobs.SetScheme(1) -- Jobs are internally a service, so setting scheme and priority
multi.Jobs.SetPriority(multi.Priority_Core)
multi:mainloop()
```
# CBT: newThread()
@ -556,21 +701,82 @@ Constants
Examples:
```lua
package.path="?/init.lua;?.lua;"..package.path
local multi = require("multi")
multi,thread = require("multi"):init()
multi:newThread("Example of basic usage",function()
while true do
thread.sleep(1)
print("We just made an alarm!")
end
end)
multi:lightloop()
multi:mainloop()
```
# CBT: newISOThread()
`th = multi:newThread([STRING name,] FUNCTION func, TABLE: env)` — Creates a new thread with name and function func. Sets the enviroment of the func to env. Both the thread.* and multi.* are automatically placed in the enviroment.
When within a thread, if you have any holding code you will want to use thread.* to give time to other threads while your code is running. This type of thread does not have access to outside local or globals. Only what is in the env can be seen. (This thread was made so pesudo threading could work)
Constants
---
- `th.Name` — Name of thread
- `th.Type` — Type="thread"
- `th.TID` — Thread ID
- `conn = th.OnError(FUNCTION: callback)` — Connect to an event which is triggered when an error is encountered within a thread
- `conn = th.OnDeath(FUNCTION: callback)` — Connect to an event which is triggered when the thread had either been killed or stopped running. (Not triggered when there is an error!)
- `boolean = th:isPaused()`\* — Returns true if a thread has been paused
- `self = th:Pause()`\* — Pauses a thread
- `self = th:Resume()`\* — Resumes a paused thread
- `self = th:Kill()`\* — Kills a thread
- `self = th:Destroy()`\* — Destroys a thread
<b>*</b>Using these methods on a thread directly you are making a request to a thread! The thread may not accept your request, but it most likely will. You can contorl the thread flow within the thread's function itself
```lua
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
multi,thread = require("multi"):init()
GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available
jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads
func = jq:newFunction("test",function(a,b)
THREAD.sleep(2)
return a+b
end)
for i = 1,10 do
func(i,i*3).connect(function(data)
print(data)
end)
end
local a = true
b = false
multi:newThread("Standard Thread 1",function()
while true do
thread.sleep(1)
print("Testing 1 ...",a,b,test)
end
end).OnError(function(self,msg)
print(msg)
end)
-- All upvalues are stripped! no access to the global, multi and thread are exposed however
multi:newISOThread("ISO Thread 2",function()
while true do
thread.sleep(1)
print("Testing 2 ...",a,b,test) -- a and b are nil, but test is true
end
end,{test=true,print=print})
.OnError(function(self,msg)
print(msg)
end)
multi:mainloop()
```
# System Threads (ST) - Multi-Integration Getting Started
The system threads need to be required seperatly.
```lua
-- I recommend keeping these as globals. When using lanes you can use local and things will work, but if you use love2d and locals, upvalues are not transfered over threads and this can be an issue
GLOBAL, THREAD = require("multi.integration.lanesManager"):init() -- We will talk about the global and thread interface that is returned
GLOBAL, THREAD = require("multi.integration.threading"):init() -- We will talk about the global and thread interface that is returned
GLOBAL, THREAD = require("multi.integration.loveManager"):init()
GLOBAL, THREAD = require("luvitManager") --*
```
@ -589,7 +795,7 @@ Using this integration modifies some methods that the multi library has.
- `THREAD.kill()` — Kills the thread
- `THREAD.getName()` — Returns the name of the working thread
- `THREAD.sleep(NUMBER n)` — Sleeps for an amount of time stopping the current thread
- `THREAD.hold(FUNCTION func)` — Holds the current thread until a condition is met
- `THREAD.hold(FUNCTION func, TABLE options)` — Holds the current thread until a condition is met
- `THREAD.getID()` — returns a unique ID for the current thread. This varaiable is visible to the main thread as well as by accessing it through the returned thread object. OBJ.Id
# ST - GLOBAL namespace
@ -609,8 +815,8 @@ ST - System Threads
System Threads are the feature that allows a user to interact with systen threads. It differs from regular coroutine based thread in how it can interact with variables. When using system threads the GLOBAL table is the "only way"* to send data. Spawning a System thread is really simple once all the required libraries are in place. See example below:
```lua
local multi = require("multi") -- keep this global when using lanes or implicitly define multi within the spawned thread
local GLOBAL, THREAD = require("multi.integration.lanesManager").init()
multi,thread = require("multi"):init() -- keep this global when using lanes or implicitly define multi within the spawned thread
local GLOBAL, THREAD = require("multi.integration.threading").init()
multi:newSystemThread("Example thread",function()
local multi = require("multi") -- we are in a thread so lets not refer to that upvalue!
print("We have spawned a thread!")
@ -624,7 +830,7 @@ end,"A message that we are passing") -- There are restrictions on what can be pa
tloop = multi:newTLoop(function()
print("I'm still kicking!")
end,1)
multi:lightloop()
multi:mainloop()
```
<b>*</b>This isn't entirely true, as of right now the compatiablity with the lanes library and love2d engine have their own methods to share data, but if you would like to have your code work in both enviroments then using the GLOBAL table and the data structures provided by the multi library will ensure this happens. If you do not plan on having support for both platforms then feel free to use linda's in lanes and channels in love2d.
@ -638,8 +844,8 @@ When creating objects with a name they are automatically exposed to the GLOBAL t
```lua
-- Exposing a queue
multi = require("multi")
local GLOBAL, THREAD = require("multi.integration.lanesManager").init() -- The standard setup above
multi,thread = require("multi"):init()
local GLOBAL, THREAD = require("multi.integration.threading").init() -- The standard setup above
queue = multi:newSystemThreadedQueue("myQueue"):init() -- We create and initiate the queue for the main thread
queue:push("This is a test!") -- We push some data onto the queue that other threads can consume and do stuff with
multi:newSystemThread("Example thread",function() -- Create a system thread
@ -647,7 +853,7 @@ multi:newSystemThread("Example thread",function() -- Create a system thread
local data = queue:pop() -- Get the data
print(data) -- print the data
end)
multi:lightloop()
multi:mainloop()
```
# ST - SystemThreadedQueue
@ -659,9 +865,9 @@ multi:lightloop()
Let's get into some examples:
```lua
multi = require("multi")
multi,thread = require("multi"):init()
thread_names = {"Thread_A","Thread_B","Thread_C","Thread_D"}
local GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
local GLOBAL, THREAD = require("multi.integration.threading"):init()
queue = multi:newSystemThreadedQueue("myQueue"):init()
for _,n in pairs(thread_names) do
multi:newSystemThread(n,function()
@ -683,13 +889,24 @@ end):OnEvent(function()
print("No more data within the queue!")
os.exit()
end)
multi:lightloop()
multi:mainloop()
```
You have probable noticed that the output from this is a total mess! Well I though so too, and created the system threaded console!
# ST - Using the Console
`console = THREAD.getConsole()`
This does guarantee an order to console output, it does ensure that all things are on nice neat lines
```lua
multi,thread = require("multi"):init()
local GLOBAL, THREAD = require("multi.integration.threading"):init()
console.print("Hello World!")
```
# ST - SystemThreadedJobQueue
`jq = multi:newSystemThreadedJobQueue([NUMBER: threads])` — Creates a system threaded job queue with an optional number of threads
- `boolean jq:isEmpty()` — Returns true if the jobqueue is empty false otherwise
- `jq.cores = (supplied number) or (the number of cores on your system*2)`
- `jq.OnJobCompleted(FUNCTION: func(jID,...))` — Connection that is triggered when a job has been completed. The jobID and returns of the job are supplies as arguments
- `self = jq:doToAll(FUNCTION: func)` — Send data to every thread in the job queue. Useful if you want to require a module and have it available on all threads
@ -698,13 +915,14 @@ You have probable noticed that the output from this is a total mess! Well I thou
- `handler = jq:newFunction([STRING: name], FUNCTION: func)` — returns a threaded Function that wraps around jq.registerFunction, jq.pushJob() and jq.OnJobCompleted() to provide an easy way to create and work with the jobqueue
- `handler.connect(Function: func(returns))` — Connects to the event that is triggered when the returns are avaiable
- `VARIAABLE returns = handler.wait()` — Waits until returns are avaiable and then returns them
**Note:** Created functions using this method act as normal functions on the queue side of things. So you can call the functions from other queue functions as if they were normal functions.
Example:
```lua
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
multi = require("multi")
GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
multi,thread = require("multi"):init()
GLOBAL, THREAD = require("multi.integration.threading"):init()
local jq = multi:newSystemThreadedJobQueue(4) -- job queue using 4 cores
jq:doToAll(function()
Important = 15
@ -727,7 +945,7 @@ func(5,5).connect(function(ret)
print("Connected",ret)
os.exit()
end)
multi:lightloop()
multi:mainloop()
```
# ST - SystemThreadedTable
`stt = multi:newSystemThreadedTable(STRING: name)`
@ -738,15 +956,15 @@ multi:lightloop()
Example:
```lua
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
multi = require("multi")
GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
multi,thread = require("multi"):init()
GLOBAL, THREAD = require("multi.integration.threading"):init()
local stt = multi:newSystemThreadedTable("stt")
stt["hello"] = "world"
multi:newSystemThread("test thread",function()
local stt = GLOBAL["stt"]:init()
print(stt["hello"])
end)
multi:lightloop()
multi:mainloop()
```
# Network Threads - Multi-Integration WIP Being Reworked
More of a fun project of mine then anything core to to the library it will be released and documented when it is ready. I do not have a timeframe for this

File diff suppressed because it is too large Load Diff

2582
init.lua Normal file

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

@ -0,0 +1,393 @@
--[[
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()
if not (GLOBAL and THREAD) then
GLOBAL, THREAD = multi.integration.GLOBAL, multi.integration.THREAD
else
lanes = require("lanes")
end
function multi:newSystemThreadedQueue(name)
local name = name or multi.randomString(16)
local c = {}
c.Name = name
c.linda = lanes.linda()
c.Type = multi.registerType("s_queue")
function c:push(v)
self.linda:send("Q", v)
end
function c:pop()
return ({self.linda:receive(0, "Q")})[2]
end
function c:peek()
return self.linda:get("Q")
end
function c:init()
return self
end
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
end
function multi:newSystemThreadedTable(name)
local name = name or multi.randomString(16)
local c = {}
c.link = lanes.linda()
c.Name = name
c.Type = multi.registerType("s_table")
function c:init()
return self
end
setmetatable(c,{
__index = function(t,k)
return c.link:get(k)
end,
__newindex = function(t,k,v)
c.link:set(k, v)
end
})
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.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
end
function multi:newSystemThreadedJobQueue(n)
local c = {}
c.cores = n or THREAD.getCores()*2
c.Type = multi.registerType("s_jobqueue")
c.OnJobCompleted = multi:newConnection()
local funcs = multi:newSystemThreadedTable()
local queueJob = multi:newSystemThreadedQueue()
local queueReturn = multi:newSystemThreadedQueue()
local doAll = multi:newSystemThreadedQueue()
local ID=1
local jid = 1
function c:isEmpty()
return queueJob:peek()==nil
end
function c:doToAll(func,...)
for i=1,c.cores do
doAll:push{ID,func,...}
end
ID = ID + 1
return self
end
function c:registerFunction(name,func)
funcs[name]=func
return self
end
function c:pushJob(name,...)
queueJob:push{name,jid,multi.pack(...)}
jid = jid + 1
return jid-1
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
if #rets == 0 then
return multi.NIL
else
return multi.unpack(rets)
end
end
end)
end, holup), name
end
thread:newThread("JobQueueManager",function()
while true do
local job = thread.hold(function()
return queueReturn:pop()
end)
if job then
local id = table.remove(job,1)
c.OnJobCompleted:Fire(id,multi.unpack(job))
end
end
end)
for i=1,c.cores do
multi:newSystemThread("STJQ_"..multi.randomString(8),function(queue)
local multi, thread = require("multi"):init()
local idle = os.clock()
local clock = os.clock
local ref = 0
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs})
thread:newThread("JobHandler",function()
while true do
local dat = thread.hold(function()
return queueJob:pop()
end)
idle = clock()
thread:newThread("JobQueue-Spawn",function()
local name = table.remove(dat, 1)
local jid = table.remove(dat, 1)
local args = table.remove(dat, 1)
queueReturn:push{jid, funcs[name](args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8]), queue}
end)
end
end)
thread:newThread("DoAllHandler",function()
while true do
local dat = thread.hold(function()
return doAll:peek()
end)
if dat then
if dat[1]>ref then
ref = table.remove(dat, 1)
func = table.remove(dat, 1)
idle = clock()
func(unpack(dat))
doAll:pop()
end
end
end
end)
thread:newThread("IdleHandler",function()
while true do
thread.hold(function()
return clock()-idle>3
end)
THREAD.sleep(.01)
end
end)
multi:mainloop()
end,i)
end
function c:Hold(opt)
return thread.hold(self.OnJobCompleted)
end
self:create(c)
return c
end
function multi:newSystemThreadedConnection(name)
local name = name or multi.randomString(16)
local c = {}
c.Type = multi.registerType("s_connection")
c.CONN = 0x00
c.TRIG = 0x01
c.PING = 0x02
c.PONG = 0x03
local function remove(a, b)
local ai = {}
local r = {}
for k,v in pairs(a) do ai[v]=true end
for k,v in pairs(b) do
if ai[v]==nil then table.insert(r,a[k]) end
end
return r
end
c.CID = THREAD_ID
c.subscribe = multi:newSystemThreadedQueue("SUB_STC_"..self.Name):init()
c.Name = name
c.links = {} -- All triggers sent from main connection. When a connection is triggered on another thread, they speak to the main then send stuff out.
-- 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, self.links)
end)
thread.sleep(3)
ping:Resume()
end,false)
local function fire(...)
for _, link in pairs(c.links) do
link:push {c.TRIG, multi.pack(...)}
end
end
thread:newThread("STC_SUB_MAN"..name,function()
local item
local sub_func = function() -- This will keep things held up until there is something to process
return c.subscribe:pop()
end
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(sub_func)
if item[1] == c.CONN then
multi.ForEach(c.links, function(link) -- Sync new connections
item[2]:push{c.CONN, link}
end)
c.links[#c.links+1] = item[2]
elseif item[1] == c.TRIG then
fire(multi.unpack(item[2]))
c.proxy_conn:Fire(multi.unpack(item[2]))
end
end
end)
--- ^^^ This will only exist in the init thread
function c:Fire(...)
local args = multi.pack(...)
if self.CID == THREAD_ID 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
function c:init()
local multi, thread = require("multi"):init()
self.links = {}
self.proxy_conn = multi:newConnection()
local mt = getmetatable(self.proxy_conn)
local tempMT = {}
for i,v in pairs(mt) do
tempMT[i] = v
end
tempMT.__index = self.proxy_conn
tempMT.__call = function(t,func) self.proxy_conn(func) end
setmetatable(self, tempMT)
if self.CID == THREAD_ID then return self end
thread:newThread("STC_CONN_MAN"..name,function()
local item
local link_self_ref = multi:newSystemThreadedQueue()
self.subscribe:push{self.CONN, link_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 item[2].Name ~= link_self_ref.Name then
table.insert(self.links, item[2])
end
link_self_ref:pop()
elseif item[1] == self.TRIG then
self.proxy_conn:Fire(multi.unpack(item[2]))
link_self_ref:pop()
else
-- This shouldn't be the case
end
end
end)
return self
end
if multi.isMainThread then
multi.integration.GLOBAL[name] = c
else
GLOBAL[name] = c
end
self:create(c)
return c
end
require("multi.integration.sharedExtensions")

View File

@ -0,0 +1,205 @@
--[[
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.
]]
package.path = "?/init.lua;?.lua;" .. package.path
multi, thread = require("multi"):init() -- get it all and have it on all lanes
if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassle
return {
init = function()
return multi.integration.GLOBAL, multi.integration.THREAD
end
}
end
-- Step 1 get lanes
lanes = require("lanes").configure()
multi.SystemThreads = {}
multi.isMainThread = true
_G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
function multi:canSystemThread()
return true
end
function multi:getPlatform()
return "lanes"
end
-- Step 2 set up the Linda objects
local __GlobalLinda = lanes.linda() -- handles global stuff
local __SleepingLinda = lanes.linda() -- handles sleeping stuff
local __ConsoleLinda = lanes.linda() -- handles console stuff
local __StatusLinda = lanes.linda() -- handles pushstatus for stfunctions
local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda, __SleepingLinda, __StatusLinda, __ConsoleLinda)
local count = 1
local started = false
local livingThreads = {}
function THREAD:newFunction(func, holdme)
return thread:newFunctionBase(function(...)
return multi:newSystemThread("TempSystemThread",func,...)
end, holdme, multi.registerType("s_function"))()
end
function multi:newSystemThread(name, func, ...)
local name = name or multi.randomString(16)
multi.InitSystemThreadErrorHandler()
local rand = math.random(1, 10000000)
local return_linda = lanes.linda()
c = {}
c.Name = name
c.ID = count
c.loadString = {"base","package","os","io","math","table","string","coroutine"}
livingThreads[count] = {true, name}
c.returns = return_linda
c.Type = multi.registerType("s_thread")
c.creationTime = os.clock()
c.alive = true
c.priority = THREAD.Priority_Normal
local multi_settings = multi.defaultSettings
local globe = {
THREAD_NAME = name,
THREAD_ID = count,
THREAD = THREAD,
GLOBAL = GLOBAL,
_Console = __ConsoleLinda,
_DEFER = {}
}
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(...)
multi, thread = require("multi"):init(multi_settings)
require("multi.integration.lanesManager.extensions")
require("multi.integration.sharedExtensions")
local has_error = true
returns = {pcall(func, ...)}
return_linda:set("returns", returns)
for i,v in pairs(_DEFER) do
pcall(v)
end
has_error = false
end)(...)
count = count + 1
function c:getName()
return c.Name
end
function c:kill()
self.thread:cancel()
self.alive = false
end
table.insert(multi.SystemThreads, c)
c.OnDeath = multi:newConnection()
c.OnError = multi:newConnection()
GLOBAL["__THREADS__"] = livingThreads
c.OnError(multi.error)
if self.isActor then
self:create(c)
else
multi.create(multi, c)
end
return c
end
THREAD.newSystemThread = function(...)
multi:newSystemThread(...)
end
function multi.InitSystemThreadErrorHandler()
if started == true then
return
end
started = true
thread:newThread("SystemThreadScheduler",function()
local threads = multi.SystemThreads
local _,data,status,push,temp
while true do
thread.yield()
_,data = __ConsoleLinda:receive(0, "Q")
if data then
--print(data[1])
end
for i = #threads, 1, -1 do
temp = threads[i]
status = temp.thread.status
push = __StatusLinda:get(temp.ID)
if push then
temp.statusconnector:Fire(multi.unpack(({__StatusLinda:receive(nil, temp.ID)})[2]))
end
if status == "done" or temp.returns:get("returns") then
returns = ({temp.returns:receive(0, "returns")})[2]
livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false
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
table.remove(threads, i)
elseif status == "running" then
--
elseif status == "waiting" then
--
elseif status == "error" then
-- The thread never really errors, we handle this through our linda object
elseif status == "cancelled" then
livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false
temp.OnError:Fire(temp,"thread_cancelled")
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
elseif status == "killed" then
livingThreads[temp.ID] = {false, temp.Name}
temp.alive = false
temp.OnError:Fire(temp,"thread_killed")
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
end
end
end
end).OnError(multi.error)
end
multi.print("Integrated Lanes Threading!")
multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.lanesManager.extensions")
return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -28,7 +28,8 @@ local function getOS()
return "unix"
end
end
local function INIT(__GlobalLinda,__SleepingLinda)
local function INIT(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console)
local THREAD = {}
THREAD.Priority_Core = 3
THREAD.Priority_High = 2
@ -37,64 +38,71 @@ local function INIT(__GlobalLinda,__SleepingLinda)
THREAD.Priority_Below_Normal = -1
THREAD.Priority_Low = -2
THREAD.Priority_Idle = -3
function THREAD.set(name, val)
__GlobalLinda:set(name, val)
end
function THREAD.get(name)
__GlobalLinda:get(name)
end
function THREAD.waitFor(name)
local function wait()
math.randomseed(os.time())
__SleepingLinda:receive(.001, "__non_existing_variable")
end
repeat
wait()
until __GlobalLinda:get(name)
return __GlobalLinda:get(name)
end
if getOS() == "windows" then
THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else
THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
function THREAD.waitFor(name)
local multi, thread = require("multi"):init()
return multi.hold(function()
math.randomseed(os.time())
__SleepingLinda:receive(.001, "__non_existing_variable")
return __GlobalLinda:get(name)
end)
end
function THREAD.getCores()
return THREAD.__CORES
end
function THREAD.getConsole()
local c = {}
c.queue = _Console
c.queue = __Console
function c.print(...)
c.queue:send("Q", {...})
c.queue:push("Q", table.concat(multi.pack(...), "\t"))
end
function c.error(err)
c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}
error(err)
c.queue:push("Q", "Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err)
multi.error(err)
end
return c
end
function THREAD.getThreads()
return GLOBAL.__THREADS__
end
if os.getOS() == "windows" then
THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else
THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
end
function THREAD.kill() -- trigger the lane destruction
error("Thread was killed!")
error("Thread was killed!\1")
end
function THREAD.getName()
return THREAD_NAME
function THREAD.sync()
-- Maybe do something...
end
function THREAD.getID()
return THREAD_ID
function THREAD.pushStatus(...)
local args = multi.pack(...)
__StatusLinda:send(nil,THREAD_ID, args)
end
_G.THREAD_ID = 0
function THREAD.sleep(n)
math.randomseed(os.time())
__SleepingLinda:receive(n, "__non_existing_variable")
end
function THREAD.hold(n)
local function wait()
math.randomseed(os.time())
@ -104,6 +112,7 @@ local function INIT(__GlobalLinda,__SleepingLinda)
wait()
until n()
end
local GLOBAL = {}
setmetatable(GLOBAL, {
__index = function(t, k)
@ -113,8 +122,30 @@ local function INIT(__GlobalLinda,__SleepingLinda)
__GlobalLinda:set(k, v)
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
end
return {init = function(g,s)
return INIT(g,s)
return {init = function(g,s,st,c,onexit)
return INIT(g,s,st,c,onexit)
end}

View File

@ -0,0 +1,244 @@
if not ISTHREAD then
multi, thread = require("multi").init()
GLOBAL = multi.integration.GLOBAL
THREAD = multi.integration.THREAD
end
function multi:newSystemThreadedQueue(name)
local name = name or multi.randomString(16)
local c = {}
c.Name = name
c.Type = multi.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()
self.chan = love.thread.getChannel(self.Name)
return self
end
function c:Hold(opt)
local multi, thread = require("multi"):init()
if opt.peek then
return thread.hold(function()
return self:peek()
end)
else
return thread.hold(function()
return self:pop()
end)
end
end
GLOBAL[name] = c
self:create(c)
return c
end
function multi:newSystemThreadedTable(name)
local name = name or multi.randomString(16)
local c = {}
c.Name = name
c.Type = multi.registerType("s_table")
c.tab = THREAD.createTable(name)
function c:init()
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
})
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
end
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 = THREAD.createTable("__JobQueue_"..jqc.."_table")
c.queue = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queue")
c.queueReturn = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueReturn")
c.queueAll = multi:newSystemThreadedQueue("__JobQueue_"..jqc.."_queueAll")
c.id = 0
c.OnJobCompleted = multi:newConnection()
local allfunc = 0
function c:doToAll(func)
for i = 1, self.cores do
self.queueAll:push({allfunc, func})
end
allfunc = allfunc + 1
end
function c:registerFunction(name, func)
if self.funcs[name] then
multi.error("A function by the name "..name.." has already been registered!")
end
self.funcs[name] = func
end
function c:pushJob(name,...)
self.id = self.id + 1
self.queue:push{name,self.id,...}
return self.id
end
function c:isEmpty()
return queueJob:peek()==nil
end
local nFunc = 0
function c:newFunction(name,func,holup) -- This registers with the queue
if type(name)=="function" then
holup = func
func = name
name = "JQ_Function_"..nFunc
end
nFunc = nFunc + 1
c:registerFunction(name,func)
return thread:newFunction(function(...)
local id = c:pushJob(name,...)
local link
local rets
link = c.OnJobCompleted(function(jid,...)
if id==jid then
rets = multi.pack(...)
end
end)
return thread.hold(function()
if rets then
return multi.unpack(rets) or multi.NIL
end
end)
end,holup),name
end
thread:newThread("jobManager",function()
while true do
thread.yield()
local dat = c.queueReturn:pop()
if dat then
c.OnJobCompleted:Fire(multi.unpack(dat))
end
end
end)
for i=1,c.cores do
multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc)
local multi, thread = require("multi"):init()
require("love.timer")
love.timer.sleep(1)
local clock = os.clock
local funcs = THREAD.createTable("__JobQueue_"..jqc.."_table")
local queue = THREAD.waitFor("__JobQueue_"..jqc.."_queue")
local queueReturn = THREAD.waitFor("__JobQueue_"..jqc.."_queueReturn")
local lastProc = clock()
local queueAll = THREAD.waitFor("__JobQueue_"..jqc.."_queueAll")
local registry = {}
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs})
thread:newThread("startUp",function()
while true do
thread.yield()
local all = queueAll:peek()
if all and not registry[all[1]] then
lastProc = os.clock()
queueAll:pop()[2]()
end
end
end)
thread:newThread("runner",function()
thread.sleep(.1)
while true do
thread.yield()
local all = queueAll:peek()
if all and not registry[all[1]] then
lastProc = os.clock()
queueAll:pop()[2]()
end
local dat = thread.hold(queue)
if dat then
multi:newThread("Test",function()
lastProc = os.clock()
local name = table.remove(dat,1)
local id = table.remove(dat,1)
local tab = {funcs[name](multi.unpack(dat))}
table.insert(tab,1,id)
--local test = queueReturn.push
queueReturn:push(tab)
end)
end
end
end)
thread:newThread("Idler",function()
while true do
thread.yield()
if clock()-lastProc> 2 then
THREAD.sleep(.05)
else
THREAD.sleep(.001)
end
end
end)
multi:mainloop()
end,jqc)
end
function c:Hold(opt)
return thread.hold(self.OnJobCompleted)
end
jqc = jqc + 1
self:create(c)
return c
end

View File

@ -0,0 +1,137 @@
if ISTHREAD then
error("You cannot require the loveManager from within a thread!")
end
local ThreadFileData = [[
ISTHREAD = true
args = {...}
THREAD_ID = args[1]
THREAD_NAME = args[2]
GLOBAL, THREAD, DEFER = require("multi.integration.loveManager.threads"):init()
__FUNC = THREAD.unpackValue(args[3])
ARGS = THREAD.unpackValue(args[4])
settings = args[5]
if ARGS == nil then ARGS = {} end
math.randomseed(THREAD_ID)
math.random()
math.random()
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
]]
_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 = {}
c.Type = multi.STHREAD
c.Name = name
c.ID = tid
c.thread = love.thread.newThread(ThreadFileData)
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.OnError = multi:newConnection()
c.status_channel = love.thread.getChannel("__status_channel__" .. c.ID)
function c:getName() return c.name end
table.insert(threads, c)
c.OnError(multi.error)
if self.isActor then
self:create(c)
else
multi.create(multi, c)
end
return c
end
local started = false
local console_channel = love.thread.getChannel("__console_channel__")
function THREAD:newFunction(func, holdme)
return thread:newFunctionBase(function(...)
return multi:newSystemThread("SystemThreaded Function Handler", func, ...)
end, holdme, multi.SFUNCTION)()
end
function love.threaderror(thread, errorstr)
multi.error("Thread error! " .. errorstr)
end
function multi.InitSystemThreadErrorHandler()
if started == true then return end
started = true
thread:newThread("Love System Thread Handler", function()
while true do
thread.yield()
for i = #threads, 1, -1 do
local th = threads[i]
if th.status_channel:peek() ~= nil then
th.statusconnector:Fire(multi.unpack(th.status_channel:pop()))
end
local th_err = th.thread:getError()
if th_err == "Thread Killed!\1" then
th.OnDeath:Fire("Thread Killed!")
table.remove(threads, i)
elseif th_err then
th.OnError:Fire(th, th_err)
table.remove(threads, i)
elseif th.stab.returns then
th.OnDeath:Fire(multi.unpack(th.stab.returns))
th.stab.returns = nil
table.remove(threads, i)
end
end
end
end)
end
THREAD.newSystemThread = function(...)
multi:newSystemThread(...)
end
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.loveManager.extensions")
require("multi.integration.sharedExtensions")
multi.print("Integrated Love Threading!")
return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -0,0 +1,228 @@
--[[
MIT License
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
require("love.timer")
require("love.system")
require("love.data")
require("love.thread")
local multi, thread = require("multi"):init()
-- Checks if the given value is a LOVE2D object (i.e. has metatable with __index field) and if that __index field contains functions typical of LOVE2D objects
function isLoveObject(value)
-- Check if the value has metatable
if type(value) == "userdata" and getmetatable(value) then
-- Check if the metatable has the __index field
local index = getmetatable(value).__index
if type(index) == "table" then
-- Check if the metatable's __index table contains functions typical of LOVE2D objects
if index.draw or index.update or index.getWidth or index.getHeight or index.getString or index.getPointer then
return true
end
end
end
return false
end
-- 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
function tableToFunctionString(t)
if type(t) == "nil" then return "\1\2:nil:" end
if type(t) == "function" then return "\1\2:func:"..string.dump(t) end
if type(t) ~= "table" then return t end
local newtable = {}
for k, v in pairs(t) do
if type(v) == "function" then
newtable[k] = "\1\2:func:"..string.dump(v)
elseif type(v) == "table" then
newtable[k] = tableToFunctionString(v)
elseif isLoveObject(v) then
newtable[k] = v
elseif type(v) == "userdata" then
newtable[k] = tostring(v)
else
newtable[k] = v
end
end
return newtable
end
-- Converts strings with the value "\1\2:func:<function_string>" back to functions
function functionStringToTable(t)
if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end
if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end
if type(t) ~= "table" then return t end
for k, v in pairs(t) do
if type(v) == "string" then
if v:sub(1, 8) == "\1\2:func:" then
t[k] = loadstring(v:sub(9, -1))
else
t[k] = v
end
elseif type(v) == "table" then
t[k] = functionStringToTable(v)
else
t[k] = v
end
end
if t.init then
t:init()
end
return t
end
local function packValue(t)
return tableToFunctionString(t)
end
local function unpackValue(t)
return functionStringToTable(t)
end
local function createTable(n)
if not n then
n = "STAB"..multi.randomString(8)
end
local __proxy = {}
local function set(name, val)
local chan = love.thread.getChannel(n .. name)
if chan:getCount() == 1 then chan:pop() end
__proxy[name] = true
chan:push(packValue(val))
end
local function get(name)
return unpackValue(love.thread.getChannel(n .. name):peek())
-- if type(data) == "table" and data.init then
-- return data:init()
-- else
-- return data
-- end
end
return setmetatable({},
{
__index = function(t, k)
return get(k)
end,
__newindex = function(t, k, v)
set(k,v)
end
}
)
end
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 = {}
c.queue = console_channel
function c.print(...)
c.queue:push(table.concat(multi.pack(...), "\t"))
end
function c.error(err)
c.queue:push("Error in <"..THREAD_NAME..":" .. THREAD_ID .. ">: ".. err)
multi.error(err)
end
return c
end
function THREAD.getThreads()
--
end
function THREAD.kill() -- trigger the lane destruction
error("Thread was killed!\1")
end
function THREAD.pushStatus(...)
status_channel:push(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
return {
init = function()
return INIT()
end
}

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -30,7 +30,7 @@ function multi:newSystemThreadedQueue(name)
local fRef = {"func",nil}
function c:init()
local q = {}
q.chan = love.thread.getChannel(self.Name)
q.chan = lovr.thread.getChannel(self.Name)
function q:push(dat)
if type(dat) == "function" then
fRef[2] = THREAD.dump(dat)
@ -76,9 +76,9 @@ function multi:newSystemThreadedJobQueue(n)
c.cores = n or THREAD.getCores()
c.registerQueue = {}
c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table")
c.queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue")
c.queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn")
c.queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll")
c.queue = lovr.thread.getChannel("__JobQueue_"..jqc.."_queue")
c.queueReturn = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueReturn")
c.queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll")
c.id = 0
c.OnJobCompleted = multi:newConnection()
local allfunc = 0
@ -99,6 +99,9 @@ function multi:newSystemThreadedJobQueue(n)
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
@ -115,42 +118,43 @@ function multi:newSystemThreadedJobQueue(n)
local rets
link = c.OnJobCompleted(function(jid,...)
if id==jid then
rets = {...}
link:Remove()
rets = multi.pack(...)
link:Destroy()
end
end)
return thread.hold(function()
if rets then
return unpack(rets) or multi.NIL
return multi.unpack(rets) or multi.NIL
end
end)
end,holup),name
end
multi:newThread("jobManager",function()
thread:newThread("jobManager",function()
while true do
thread.yield()
local dat = c.queueReturn:pop()
if dat then
c.OnJobCompleted:Fire(unpack(dat))
c.OnJobCompleted:Fire(multi.unpack(dat))
end
end
end)
for i=1,c.cores do
multi:newSystemThread("JobQueue_"..jqc.."_worker_"..i,function(jqc)
local multi, thread = require("multi"):init()
require("love.timer")
require("lovr.timer")
local function atomic(channel)
return channel:pop()
end
local clock = os.clock
local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table")
local queue = love.thread.getChannel("__JobQueue_"..jqc.."_queue")
local queueReturn = love.thread.getChannel("__JobQueue_"..jqc.."_queueReturn")
local queue = lovr.thread.getChannel("__JobQueue_"..jqc.."_queue")
local queueReturn = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueReturn")
local lastProc = clock()
local queueAll = love.thread.getChannel("__JobQueue_"..jqc.."_queueAll")
local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll")
local registry = {}
_G["__QR"] = queueReturn
setmetatable(_G,{__index = funcs})
multi:newThread("startUp",function()
thread:newThread("startUp",function()
while true do
thread.yield()
local all = queueAll:peek()
@ -160,7 +164,7 @@ function multi:newSystemThreadedJobQueue(n)
end
end
end)
multi:newThread("runner",function()
thread:newThread("runner",function()
thread.sleep(.1)
while true do
thread.yield()
@ -174,7 +178,7 @@ function multi:newSystemThreadedJobQueue(n)
lastProc = os.clock()
local name = table.remove(dat,1)
local id = table.remove(dat,1)
local tab = {funcs[name](unpack(dat))}
local tab = {funcs[name](multi.unpack(dat))}
table.insert(tab,1,id)
queueReturn:push(tab)
end
@ -182,7 +186,7 @@ function multi:newSystemThreadedJobQueue(n)
end):OnError(function(...)
error(...)
end)
multi:newThread("Idler",function()
thread:newThread("Idler",function()
while true do
thread.yield()
if clock()-lastProc> 2 then

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -21,12 +21,13 @@ 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.
]]
-- TODO make compatible with lovr
if ISTHREAD then
error("You cannot require the loveManager from within a thread!")
error("You cannot require the lovrManager from within a thread!")
end
local ThreadFileData = [[
ISTHREAD = true
THREAD = require("multi.integration.loveManager.threads") -- order is important!
THREAD = require("multi.integration.lovrManager.threads") -- order is important!
sThread = THREAD
__IMPORTS = {...}
__FUNC__=table.remove(__IMPORTS,1)
@ -35,15 +36,17 @@ __THREADNAME__=table.remove(__IMPORTS,1)
stab = THREAD.createStaticTable(__THREADNAME__)
GLOBAL = THREAD.getGlobal()
multi, thread = require("multi").init()
stab["returns"] = {THREAD.loadDump(__FUNC__)(unpack(__IMPORTS))}
stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))}
]]
local multi, thread = require("multi.compat.love2d"):init()
local multi, thread = require("multi.compat.lovr2d"):init()
local THREAD = {}
__THREADID__ = 0
__THREADNAME__ = "MainThread"
_G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
multi.integration={}
multi.integration.love2d={}
local THREAD = require("multi.integration.loveManager.threads")
multi.integration.lovr2d={}
local THREAD = require("multi.integration.lovrManager.threads")
local GLOBAL = THREAD.getGlobal()
local THREAD_ID = 1
local OBJECT_ID = 0
@ -57,7 +60,7 @@ function THREAD:newFunction(func,holup)
if t.stab["returns"] then
local dat = t.stab.returns
t.stab.returns = nil
return unpack(dat)
return multi.unpack(dat)
end
end)
end,holup)()
@ -67,20 +70,29 @@ function multi:newSystemThread(name,func,...)
local c = {}
c.name = name
c.ID=THREAD_ID
c.thread=love.thread.newThread(ThreadFileData)
c.thread=lovr.thread.newThread(ThreadFileData)
c.thread:start(THREAD.dump(func),c.ID,c.name,...)
c.stab = THREAD.createStaticTable(name)
GLOBAL["__THREAD_"..c.ID] = {ID=c.ID,Name=c.name,Thread=c.thread}
GLOBAL["__THREAD_COUNT"] = THREAD_ID
THREAD_ID=THREAD_ID+1
if self.isActor then
self:create(c)
else
multi.create(multi, c)
end
return c
end
function love.threaderror(thread, errorstr)
print("Thread error!\n"..errorstr)
THREAD.newSystemThread = multi.newSystemThread
function lovr.threaderror(thread, errorstr)
multi.print("Thread error!\n"..errorstr)
end
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.loveManager.extensions")
require("multi.integration.lovrManager.extensions")
multi.print("Integrated lovr Threading!")
return {init=function()
return GLOBAL,THREAD
end}

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -21,9 +21,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
require("love.timer")
require("love.system")
require("love.data")
-- TODO make compatible with lovr
require("lovr.timer")
require("lovr.system")
require("lovr.data")
local socket = require("socket")
local multi, thread = require("multi").init()
local threads = {}
@ -31,7 +32,7 @@ function threads.loadDump(d)
return loadstring(d:getString())
end
function threads.dump(func)
return love.data.newByteData(string.dump(func))
return lovr.data.newByteData(string.dump(func))
end
local fRef = {"func",nil}
local function manage(channel, value)
@ -55,11 +56,11 @@ end
local GNAME = "__GLOBAL_"
local proxy = {}
function threads.set(name,val)
if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end
if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end
proxy[name]:performAtomic(manage, val)
end
function threads.get(name)
if not proxy[name] then proxy[name] = love.thread.getChannel(GNAME..name) end
if not proxy[name] then proxy[name] = lovr.thread.getChannel(GNAME..name) end
local dat = proxy[name]:peek()
if type(dat)=="table" and dat[1]=="func" then
return THREAD.loadDump(dat[2])
@ -74,7 +75,7 @@ function threads.waitFor(name)
end)
end
while threads.get(name)==nil do
love.timer.sleep(.001)
lovr.timer.sleep(.001)
end
local dat = threads.get(name)
if type(dat) == "table" and dat.init then
@ -89,7 +90,7 @@ function threads.package(name,val)
val.init=init
end
function threads.getCores()
return love.system.getProcessorCount()
return lovr.system.getProcessorCount()
end
function threads.kill()
error("Thread Killed!")
@ -111,7 +112,7 @@ function threads.getID()
return __THREADID__
end
function threads.sleep(n)
love.timer.sleep(n)
lovr.timer.sleep(n)
end
function threads.getGlobal()
return setmetatable({},
@ -128,11 +129,11 @@ end
function threads.createTable(n)
local _proxy = {}
local function set(name,val)
if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end
if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end
_proxy[name]:performAtomic(manage, val)
end
local function get(name)
if not _proxy[name] then _proxy[name] = love.thread.getChannel(n..name) end
if not _proxy[name] then _proxy[name] = lovr.thread.getChannel(n..name) end
local dat = _proxy[name]:peek()
if type(dat)=="table" and dat[1]=="func" then
return THREAD.loadDump(dat[2])
@ -153,9 +154,9 @@ function threads.createTable(n)
end
function threads.getConsole()
local c = {}
c.queue = love.thread.getChannel("__CONSOLE__")
c.queue = lovr.thread.getChannel("__CONSOLE__")
function c.print(...)
c.queue:push{...}
c.queue:push(multi.pack(...))
end
function c.error(err)
c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__}
@ -166,14 +167,14 @@ end
if not ISTHREAD then
local clock = os.clock
local lastproc = clock()
local queue = love.thread.getChannel("__CONSOLE__")
multi:newThread("consoleManager",function()
local queue = lovr.thread.getChannel("__CONSOLE__")
thread:newThread("consoleManager",function()
while true do
thread.yield()
dat = queue:pop()
if dat then
lastproc = clock()
print(unpack(dat))
print(multi.unpack(dat))
end
if clock()-lastproc>2 then
thread.sleep(.1)
@ -185,14 +186,14 @@ function threads.createStaticTable(n)
local __proxy = {}
local function set(name,val)
if __proxy[name] then return end
local chan = love.thread.getChannel(n..name)
local chan = lovr.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()
local dat = lovr.thread.getChannel(n..name):peek()
if type(dat)=="table" and dat[1]=="func" then
__proxy[name] = THREAD.loadDump(dat[2])
return __proxy[name]

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -35,7 +35,7 @@ local function _INIT(luvitThread, timer)
end
-- Step 1 get setup threads on luvit... Sigh how do i even...
local multi, thread = require("multi").init()
isMainThread = true
multi.isMainThread = true
function multi:canSystemThread()
return true
end
@ -107,7 +107,7 @@ local function _INIT(luvitThread, timer)
local c = {}
local __self = c
c.name = name
c.Type = "sthread"
c.Type = multi.STHREAD
c.thread = {}
c.func = string.dump(func)
function c:kill()
@ -116,6 +116,7 @@ local function _INIT(luvitThread, timer)
luvitThread.start(entry, package.path, name, c.func, ...)
return c
end
THREAD.newSystemThread = multi.newSystemThread
multi.print("Integrated Luvit!")
multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -23,7 +23,7 @@ SOFTWARE.
]]
local multi, thread = require("multi"):init()
local net = require("net")
local bin = require("bin")
--local bin = require("bin")
local char = string.char
local byte = string.byte
bin.setBitsInterface(infinabits)

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -72,7 +72,7 @@ function master:newNetworkThread(nodeName,func,...)
local ret
local nID = netID
local conn = multi:newConnection()
multi:newThread(function()
thread:newthread(function()
dat:addBlock{
args = args,
func = func,
@ -143,7 +143,7 @@ function multi:newMasterNode(cd)
else
c:getNodesFromBroadcast()
end
multi:newThread("CMDQueueProcessor",function()
thread:newthread("CMDQueueProcessor",function()
while true do
thread.skip(128)
local data = table.remove(c._queue,1)

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,7 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Copyright (c) 2022 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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

@ -0,0 +1,123 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
package.path = "?/init.lua;?.lua;" .. package.path
local multi, thread = require("multi"):init()
local pseudoProcessor = multi:newProcessor()
if multi.integration then
return {
init = function()
return multi.integration.GLOBAL, multi.integration.THREAD
end
}
end
multi.isMainThread = true
local activator = require("multi.integration.pseudoManager.threads")
local GLOBAL, THREAD = activator.init(thread)
_G.THREAD_NAME = "MAIN_THREAD"
_G.THREAD_ID = 0
function multi:canSystemThread() -- We are emulating system threading
return true
end
function multi:getPlatform()
return "pesudo"
end
local function split(str)
local tab = {}
for word in string.gmatch(str, '([^,]+)') do
table.insert(tab,word)
end
return tab
end
local tab = [[_VERSION,io,os,require,load,debug,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,xpcall,math,coroutine,string,table]]
tab = split(tab)
local id = 0
function multi:newSystemThread(name, func, ...)
local env
env = {
GLOBAL = GLOBAL,
THREAD = THREAD,
THREAD_NAME = tostring(name),
__THREADNAME__ = tostring(name),
THREAD_ID = id,
thread = thread,
multi = multi,
}
for i, v in pairs(_G) do
if not(env[i]) and not(i == "_G") and not(i == "local_global") then
env[i] = v
else
multi.warn("skipping:",i)
end
end
if GLOBAL["__env"] then
for i,v in pairs(GLOBAL["__env"]) do
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
return th
end
THREAD.newSystemThread = multi.newSystemThread
-- System threads as implemented here cannot share memory, but use a message passing system.
-- An isolated thread allows us to mimic that behavior so if access data from the "main" thread happens things will not work. This behavior is in line with how the system threading works
function THREAD:newFunction(func,holdme)
return thread:newFunctionBase(function(...)
return multi:newSystemThread("TempSystemThread",func,...)
end, holdme, multi.registerType("s_function", "pseudoFunctions"))()
end
multi.print("Integrated Pesudo Threading!")
multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.pseudoManager.extensions")
require("multi.integration.sharedExtensions")
return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -0,0 +1,122 @@
--[[
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 function getOS()
if package.config:sub(1, 1) == "\\" then
return "windows"
else
return "unix"
end
end
local function INIT(thread)
local THREAD = {}
local GLOBAL = {}
THREAD.Priority_Core = 3
THREAD.Priority_High = 2
THREAD.Priority_Above_Normal = 1
THREAD.Priority_Normal = 0
THREAD.Priority_Below_Normal = -1
THREAD.Priority_Low = -2
THREAD.Priority_Idle = -3
function THREAD.set(name, val)
GLOBAL[name] = val
end
function THREAD.get(name)
return GLOBAL[name]
end
function THREAD.waitFor(name)
return thread.hold(function() return GLOBAL[name] end)
end
if getOS() == "windows" then
THREAD.__CORES = tonumber(os.getenv("NUMBER_OF_PROCESSORS"))
else
THREAD.__CORES = tonumber(io.popen("nproc --all"):read("*n"))
end
function THREAD.getCores()
return THREAD.__CORES
end
function THREAD.getConsole()
local c = {}
function c.print(...)
print(...)
end
function c.error(err)
error("ERROR in <"..GLOBAL["$__THREADNAME__"]..">: "..err)
end
return c
end
function THREAD.getThreads()
return {}--GLOBAL.__THREADS__
end
THREAD.pushStatus = thread.pushStatus
function THREAD.kill()
error("Thread was killed!")
end
THREAD.sleep = thread.sleep
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
end
return {init = function(thread, global)
return INIT(thread, global)
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

13
integration/threading.lua Normal file
View File

@ -0,0 +1,13 @@
-- We need to detect what enviroment we are running our code in.
return {
init = function()
if love then
return require("multi.integration.loveManager"):init()
else
if pcall(require,"lanes") then
return require("multi.integration.lanesManager"):init()
end
return require("multi.integration.pesudoManager"):init()
end
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

@ -1,95 +0,0 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local multi, thread = require("multi").init()
os.sleep = love.timer.sleep
multi.drawF = {}
function multi:onDraw(func, i)
i = i or 1
table.insert(self.drawF, i, func)
end
multi.OnKeyPressed = multi:newConnection()
multi.OnKeyReleased = multi:newConnection()
multi.OnMousePressed = multi:newConnection()
multi.OnMouseReleased = multi:newConnection()
multi.OnMouseWheelMoved = multi:newConnection()
multi.OnMouseMoved = multi:newConnection()
multi.OnDraw = multi:newConnection()
multi.OnTextInput = multi:newConnection()
multi.OnUpdate = multi:newConnection()
multi.OnQuit = multi:newConnection()
multi.OnPreLoad(function()
local function Hook(func, conn)
if love[func] ~= nil then
love[func] = Library.convert(love[func])
love[func]:inject(function(...)
conn:Fire(...)
return {...}
end,1)
elseif love[func] == nil then
love[func] = function(...)
conn:Fire(...)
end
end
end
Hook("quit", multi.OnQuit)
Hook("keypressed", multi.OnKeyPressed)
Hook("keyreleased", multi.OnKeyReleased)
Hook("mousepressed", multi.OnMousePressed)
Hook("mousereleased", multi.OnMouseReleased)
Hook("wheelmoved", multi.OnMouseWheelMoved)
Hook("mousemoved", multi.OnMouseMoved)
Hook("draw", multi.OnDraw)
Hook("textinput", multi.OnTextInput)
Hook("update", multi.OnUpdate)
multi.OnDraw(function()
for i = 1, #multi.drawF do
love.graphics.setColor(255, 255, 255, 255)
multi.drawF[i]()
end
end)
end)
function multi:loveloop(light)
local link
link = multi:newThread(function()
local mainloop = love.run()
while true do
thread.yield()
pcall(mainloop)
end
end).OnError(function(...)
print(...)
end)
if light==false then
multi:mainloop()
else
multi:lightloop()
end
end
multi.OnQuit(function()
multi.Stop()
love.event.quit()
end)
return multi

File diff suppressed because it is too large Load Diff

View File

@ -1,168 +0,0 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local multi, thread = require("multi"):init()
local GLOBAL, THREAD = multi.integration.GLOBAL,multi.integration.THREAD
function multi:newSystemThreadedQueue(name)
local c = {}
c.linda = lanes.linda()
function c:push(v)
self.linda:send("Q", v)
end
function c:pop()
return ({self.linda:receive(0, "Q")})[2]
end
function c:peek()
return self.linda:get("Q")
end
function c:init()
return self
end
GLOBAL[name or "_"] = c
return c
end
function multi:newSystemThreadedTable(name)
local c = {}
c.link = lanes.linda()
setmetatable(c,{
__index = function(t,k)
return c.link:get(k)
end,
__newindex = function(t,k,v)
c.link:set(k,v)
end
})
function c:init()
return self
end
GLOBAL[name or "_"] = c
return c
end
function multi:newSystemThreadedJobQueue(n)
local c = {}
c.cores = n or THREAD.getCores()*2
c.OnJobCompleted = multi:newConnection()
local funcs = multi:newSystemThreadedTable()
local queueJob = multi:newSystemThreadedQueue()
local queueReturn = multi:newSystemThreadedQueue()
local doAll = multi:newSystemThreadedQueue()
local ID=1
local jid = 1
function c:doToAll(func)
for i=1,c.cores do
doAll:push{ID,func}
end
ID = ID + 1
return self
end
function c:registerFunction(name,func)
funcs[name]=func
return self
end
function c:pushJob(name,...)
queueJob:push{name,jid,{...}}
jid = jid + 1
return jid-1
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 = {...}
link:Remove()
end
end)
return thread.hold(function()
if rets then
return unpack(rets) or multi.NIL
end
end)
end,holup),name
end
multi:newThread("JobQueueManager",function()
while true do
local job = thread.hold(function()
return queueReturn:pop()
end)
local id = table.remove(job,1)
c.OnJobCompleted:Fire(id,unpack(job))
end
end)
for i=1,c.cores do
multi:newSystemThread("SystemThreadedJobQueue",function(queue)
local multi,thread = require("multi"):init()
local idle = os.clock()
local clock = os.clock
local ref = 0
setmetatable(_G,{__index = funcs})
multi:newThread("JobHandler",function()
while true do
local dat = thread.hold(function()
return queueJob:pop()
end)
idle = clock()
local name = table.remove(dat,1)
local jid = table.remove(dat,1)
local args = table.remove(dat,1)
queueReturn:push{jid, funcs[name](unpack(args)),queue}
end
end)
multi:newThread("DoAllHandler",function()
while true do
local dat = thread.hold(function()
return doAll:peek()
end)
if dat then
if dat[1]>ref then
idle = clock()
ref = dat[1]
dat[2]()
doAll:pop()
end
end
end
end)
multi:newThread("IdleHandler",function()
while true do
thread.hold(function()
return clock()-idle>3
end)
THREAD.sleep(.01)
end
end)
multi:mainloop()
end,i).priority = thread.Priority_Core
end
return c
end

View File

@ -1,157 +0,0 @@
--[[
MIT License
Copyright (c) 2020 Ryan Ward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
package.path = "?/init.lua;?.lua;" .. package.path
multi, thread = require("multi").init() -- get it all and have it on all lanes
if multi.integration then -- This allows us to call the lanes manager from supporting modules without a hassel
return {
init = function()
return multi.integration.GLOBAL, multi.integration.THREAD
end
}
end
-- Step 1 get lanes
lanes = require("lanes").configure()
multi.SystemThreads = {}
multi.isMainThread = true
function multi:canSystemThread()
return true
end
function multi:getPlatform()
return "lanes"
end
-- Step 2 set up the Linda objects
local __GlobalLinda = lanes.linda() -- handles global stuff
local __SleepingLinda = lanes.linda() -- handles sleeping stuff
local __ConsoleLinda = lanes.linda() -- handles console stuff
multi:newLoop(function()
local _,data = __ConsoleLinda:receive(0, "Q")
if data then
print(unpack(data))
end
end)
local GLOBAL,THREAD = require("multi.integration.lanesManager.threads").init(__GlobalLinda,__SleepingLinda)
local threads = {}
local count = 1
local started = false
local livingThreads = {}
function THREAD:newFunction(func,holup)
return function(...)
local t = multi:newSystemThread("SystemThreadedFunction",function(...)
return func(...)
end,...)
return thread:newFunction(function()
thread.hold(function() return t.thread end)
return thread.hold(function()
return t.thread:join(.001)
end)
end,holup)()
end
end
function multi:newSystemThread(name, func, ...)
multi.InitSystemThreadErrorHandler()
rand = math.random(1, 10000000)
local c = {}
local __self = c
c.name = name
c.Name = name
c.Id = count
c.loadString = {"base","package","os","io","math","table","string","coroutine"}
livingThreads[count] = {true, name}
c.Type = "sthread"
c.creationTime = os.clock()
c.alive = true
c.priority = THREAD.Priority_Normal
local args = {...}
multi:newThread(function()
c.thread = lanes.gen(table.concat(c.loadString,","),
{
globals={ -- Set up some globals
THREAD_NAME=name,
THREAD_ID=count,
THREAD = THREAD,
GLOBAL = GLOBAL,
_Console = __ConsoleLinda
},
priority=c.priority
},func)(unpack(args))
thread.kill()
end)
count = count + 1
function c:kill()
self.thread:cancel()
multi.print("Thread: '" .. self.name .. "' has been stopped!")
self.alive = false
end
table.insert(multi.SystemThreads, c)
c.OnError = multi:newConnection()
GLOBAL["__THREADS__"] = livingThreads
return c
end
multi.OnSystemThreadDied = multi:newConnection()
function multi.InitSystemThreadErrorHandler()
if started == true then
return
end
started = true
multi:newThread(
"ThreadErrorHandler",
function()
local threads = multi.SystemThreads
while true do
thread.sleep(.5) -- switching states often takes a huge hit on performance. half a second to tell me there is an error is good enough.
for i = #threads, 1, -1 do
local v, err, t = threads[i].thread:join(.001)
if err then
if err:find("Thread was killed!") then
print(err)
livingThreads[threads[i].Id] = {false, threads[i].Name}
threads[i].alive = false
multi.OnSystemThreadDied:Fire(threads[i].Id)
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
elseif err:find("stack traceback") then
print(err)
threads[i].OnError:Fire(threads[i], err, "Error in systemThread: '" .. threads[i].name .. "' <" .. err .. ">")
threads[i].alive = false
livingThreads[threads[i].Id] = {false, threads[i].Name}
multi.OnSystemThreadDied:Fire(threads[i].Id)
GLOBAL["__THREADS__"] = livingThreads
table.remove(threads, i)
end
end
end
end
end
)
end
multi.print("Integrated Lanes!")
multi.integration = {} -- for module creators
multi.integration.GLOBAL = GLOBAL
multi.integration.THREAD = THREAD
require("multi.integration.lanesManager.extensions")
return {
init = function()
return GLOBAL, THREAD
end
}

View File

@ -1,2 +0,0 @@
-- Planned system threaded objects
-- multi:newSystemThreadedConnection(name, protect)

View File

@ -0,0 +1,38 @@
package = "multi"
version = "15.0-0"
source = {
url = "git://github.com/rayaman/multi.git",
tag = "v15.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 how to use.
]],
homepage = "https://github.com/rayaman/multi",
license = "MIT"
}
dependencies = {
"lua >= 5.1",
"lanes",
}
build = {
type = "builtin",
modules = {
["multi"] = "multi/init.lua",
["multi.compat.love2d"] = "multi/compat/love2d.lua",
["multi.integration.threading"] = "multi/integration/threading.lua",
["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua",
["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua",
["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua",
["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua",
["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua",
["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua",
["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua",
["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua",
["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua",
["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua",
--["multi.integration.networkManager"] = "multi/integration/networkManager.lua",
}
}

View File

@ -0,0 +1,42 @@
package = "multi"
version = "15.1-0"
source = {
url = "git://github.com/rayaman/multi.git",
tag = "V15.1.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",
"lanes",
}
build = {
type = "builtin",
modules = {
["multi"] = "multi/init.lua",
["multi.compat.love2d"] = "multi/compat/love2d.lua",
["multi.compat.lovr"] = "multi/compat/lovr.lua",
["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua",
["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua",
["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua",
["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua",
["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua",
["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua",
--["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua",
--["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua",
--["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua",
["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua",
["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua",
["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua",
["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua",
["multi.integration.threading"] = "multi/integration/threading.lua",
--["multi.integration.networkManager"] = "multi/integration/networkManager.lua",
}
}

View File

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

View File

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

View File

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

View File

@ -1,17 +0,0 @@
package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path
local multi,thread = require("multi"):init()
local GLOBAL, THREAD = require("multi.integration.lanesManager"):init()
multi:newSystemThread("test",function(msg)
print("In thread:", THREAD.getID(), "Msg:", msg)
while true do
-- Hold forever :D
end
end,"Passing a message")
multi:newThread("localthread",function()
print("In local thread :D")
while true do
thread.sleep(1)
print("...")
end
end)
multi:mainloop()

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

193
tests/runtests.lua Normal file
View File

@ -0,0 +1,193 @@
package.path = "../?/init.lua;../?.lua;./init.lua;./?.lua;"..package.path
local multi, thread = require("multi"):init{print=true,warn=true,error=true}--{priority=true}
local good = false
local proc = multi:newProcessor("Test")
proc.Start()
proc:newAlarm(3):OnRing(function()
good = true
end)
runTest = thread:newFunction(function()
local alarms,tsteps,steps,loops,tloops,updaters,events=false,0,0,0,0,0,false
multi.print("Testing Basic Features. If this fails most other features will probably not work!")
proc:newAlarm(2):OnRing(function(a)
alarms = true
a:Destroy()
end)
proc:newTStep(1,10,1,.1):OnStep(function(t)
tsteps = tsteps + 1
end):OnEnd(function(step)
step:Destroy()
end)
proc:newStep(1,10):OnStep(function(s)
steps = steps + 1
end):OnEnd(function(step)
step:Destroy()
end)
local loop = proc:newLoop(function(l)
loops = loops + 1
end)
proc:newTLoop(function(t)
tloops = tloops + 1
end,.1)
local updater = proc:newUpdater(1):OnUpdate(function()
updaters = updaters + 1
end)
local event = proc:newEvent(function()
return alarms
end)
event.OnEvent(function(evnt)
evnt:Destroy()
events = true
multi.success("Alarms: Ok")
multi.success("Events: Ok")
if tsteps == 10 then multi.success("TSteps: Ok") else multi.error("TSteps: Bad!") end
if steps == 10 then multi.success("Steps: Ok") else multi.error("Steps: Bad!") end
if loops > 100 then multi.success("Loops: Ok") else multi.error("Loops: Bad!") end
if tloops > 10 then multi.success("TLoops: Ok") else multi.error("TLoops: Bad!") end
if updaters > 100 then multi.success("Updaters: Ok") else multi.error("Updaters: Bad!") end
end)
thread.hold(event.OnEvent)
multi.print("Starting Connection and Thread tests!")
func = thread:newFunction(function(count)
multi.print("Starting Status test: ",count)
local a = 0
while true do
a = a + 1
thread.sleep(.1)
thread.pushStatus(a,count)
if a == count then break end
end
return "Done", true, math.random(1,10000)
end)
local ret = func(10)
local ret2 = func(15)
local ret3 = func(20)
local s1,s2,s3 = 0,0,0
ret.OnError(function(...)
multi.error("Func 1:",...)
end)
ret2.OnError(function(...)
multi.error("Func 2:",...)
end)
ret3.OnError(function(...)
multi.error("Func 3:",...)
end)
ret.OnStatus(function(part,whole)
s1 = math.ceil((part/whole)*1000)/10
end)
ret2.OnStatus(function(part,whole)
s2 = math.ceil((part/whole)*1000)/10
end)
ret3.OnStatus(function(part,whole)
s3 = math.ceil((part/whole)*1000)/10
end)
ret.OnReturn(function(...)
multi.success("Done 1",...)
end)
ret2.OnReturn(function(...)
multi.success("Done 2",...)
end)
ret3.OnReturn(function(...)
multi.success("Done 3",...)
end)
local err, timeout = thread.hold(ret.OnReturn * ret2.OnReturn * ret3.OnReturn)
if s1 == 100 and s2 == 100 and s3 == 100 then
multi.success("Threads: All tests Ok")
else
if s1>0 and s2>0 and s3 > 0 then
multi.success("Thread OnStatus: Ok")
else
multi.error("Threads OnStatus or thread.hold(conn) Error!")
end
if timeout then
multi.error("Connection Error!")
else
multi.success("Connection Test 1: Ok")
end
multi.error("Connection holding Error!")
end
conn1 = proc:newConnection()
conn2 = proc:newConnection()
conn3 = proc:newConnection()
local c1,c2,c3,c4 = false,false,false,false
local a = conn1(function()
c1 = true
end)
local b = conn2(function()
c2 = true
end)
local c = conn3(function()
c3 = true
end)
local d = conn3(function()
c4 = true
end)
conn1:Fire()
conn2:Fire()
conn3:Fire()
if c1 and c2 and c3 and c4 then
multi.success("Connection Test 2: Ok")
else
multi.error("Connection Test 2: Error")
end
c3 = false
c4 = false
conn3:Unconnect(d)
conn3:Fire()
if c3 and not(c4) then
multi.success("Connection Test 3: Ok")
else
multi.error("Connection Test 3: Error removing connection")
end
if not love then
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)
local handle = runTest()
handle.OnError(function(...)
multi.error("Something went wrong with the test!")
print(...)
end)
if not love then
multi:mainloop()
else
local hold = thread:newFunction(function()
thread.hold(handle.OnError + handle.OnReturn)
end, true)
hold()
multi.print("Starting Threading tests!")
end

298
tests/test.lua Normal file
View File

@ -0,0 +1,298 @@
package.path = "../?/init.lua;../?.lua;"..package.path
multi, thread = require("multi"):init{print=true,warn=true,debugging=true}
-- for i,v in pairs(thread) do
-- print(i,v)
-- end
-- require("multi.integration.priorityManager")
-- multi.debugging.OnObjectCreated(function(obj, process)
-- multi.print("Created:", obj.Type, "in", process.Type, process:getFullName())
-- end)
-- multi.debugging.OnObjectDestroyed(function(obj, process)
-- multi.print("Destroyed:", obj.Type, "in", process.Type, process:getFullName())
-- end)
-- test = multi:newProcessor("Test")
-- test:setPriorityScheme(multi.priorityScheme.TimeBased)
-- test:newUpdater(10000000):OnUpdate(function()
-- print("Print is slowish")
-- end)
-- print("Running...")
-- local conn1, conn2 = multi:newConnection(), multi:newConnection()
-- conn3 = conn1 + conn2
-- conn1(function()
-- print("Hi 1")
-- end)
-- conn2(function()
-- print("Hi 2")
-- end)
-- conn3(function()
-- print("Hi 3")
-- end)
-- function test(a,b,c)
-- print("I run before all and control if execution should continue!")
-- return a>b
-- end
-- conn4 = test .. conn1
-- conn5 = conn2 .. function() print("I run after it all!") end
-- conn4:Fire(3,2,3)
-- -- This second one won't trigger the Hi's
-- conn4:Fire(1,2,3)
-- conn5(function()
-- print("Test 1")
-- end)
-- conn5(function()
-- print("Test 2")
-- end)
-- conn5(function()
-- print("Test 3")
-- end)
-- conn5:Fire()
-- multi.print("Testing thread:newProcessor()")
-- proc = thread:newProcessor("Test")
-- proc:newLoop(function()
-- multi.print("Running...")
-- thread.sleep(1)
-- end)
-- proc:newThread(function()
-- while true do
-- multi.warn("Everything is a thread in this proc!")
-- thread.sleep(1)
-- end
-- end)
-- 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()
-- print("Awaiting status")
-- thread.hold(conn1 + (conn2 * conn3))
-- print("Conn or Conn2 and Conn3")
-- end)
-- multi:newAlarm(1):OnRing(function()
-- print("Conn")
-- conn1:Fire()
-- end)
-- multi:newAlarm(2):OnRing(function()
-- print("Conn2")
-- conn2:Fire()
-- end)
-- multi:newAlarm(3):OnRing(function()
-- print("Conn3")
-- conn3:Fire()
-- os.exit()
-- end)
-- local conn = multi:newSystemThreadedConnection("conn"):init()
-- multi:newSystemThread("Thread_Test_1", function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:mainloop()
-- end)
-- multi:newSystemThread("Thread_Test_2", function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:newAlarm(2):OnRing(function()
-- console.print("Fire 2!!!")
-- conn:Fire(4,5,6)
-- THREAD.kill()
-- end)
-- multi:mainloop()
-- end)
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print("Mainloop conn got triggered!",a,b,c)
-- end)
-- alarm = multi:newAlarm(1)
-- alarm:OnRing(function()
-- console.print("Fire 1!!!")
-- conn:Fire(1,2,3)
-- end)
-- alarm = multi:newAlarm(3):OnRing(function()
-- multi:newSystemThread("Thread_Test_3",function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local console = THREAD.getConsole()
-- conn(function(a,b,c)
-- console.print(THREAD:getName().." was triggered!",a,b,c)
-- end)
-- multi:newAlarm(4):OnRing(function()
-- console.print("Fire 3!!!")
-- conn:Fire(7,8,9)
-- end)
-- multi:mainloop()
-- end)
-- end)
-- multi:newSystemThread("Thread_Test_4",function()
-- local multi, thread = require("multi"):init()
-- local conn = GLOBAL["conn"]:init()
-- local conn2 = multi:newConnection()
-- local console = THREAD.getConsole()
-- multi:newAlarm(2):OnRing(function()
-- conn2:Fire()
-- end)
-- multi:newThread(function()
-- console.print("Conn Test!")
-- thread.hold(conn + conn2)
-- console.print("It held!")
-- end)
-- multi:mainloop()
-- end)
-- multi:mainloop()
--[[
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