From 0a92890290c36e64a04ac5f8703868255539c8c7 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 15 Mar 2026 01:13:00 -0700 Subject: [PATCH] working on task management --- .github/workflows/love.yml | 48 +- .github/workflows/nix_ci.yml | 82 +- .gitignore | 4 +- LICENSE | 42 +- NetworkManager.md | 50 +- README.md | 196 +- docs/Documentation.md | 1942 +++--- docs/changes.md | 7374 ++++++++++---------- init.lua | 5711 +++++++-------- integration/debugManager/init.lua | 212 +- integration/effilManager/init.lua | 90 +- integration/lanesManager/extensions.lua | 784 +-- integration/lanesManager/init.lua | 410 +- integration/lanesManager/threads.lua | 300 +- integration/loveManager/extensions.lua | 486 +- integration/loveManager/init.lua | 274 +- integration/loveManager/threads.lua | 454 +- integration/lovrManager/extensions.lua | 406 +- integration/lovrManager/init.lua | 194 +- integration/lovrManager/threads.lua | 442 +- integration/luvitManager.lua | 276 +- integration/networkManager/channel.lua | 66 +- integration/networkManager/childNode.lua | 90 +- integration/networkManager/clientSide.lua | 68 +- integration/networkManager/cmds.lua | 82 +- integration/networkManager/extensions.lua | 44 +- integration/networkManager/init.lua | 112 +- integration/networkManager/masterNode.lua | 324 +- integration/networkManager/node.lua | 252 +- integration/networkManager/nodeManager.lua | 94 +- integration/networkManager/serverSide.lua | 92 +- integration/networkManager/threads.lua | 44 +- integration/networkManager/utils.lua | 52 +- integration/priorityManager/init.lua | 424 +- integration/pseudoManager/extensions.lua | 440 +- integration/pseudoManager/init.lua | 246 +- integration/pseudoManager/threads.lua | 242 +- integration/sharedExtensions/init.lua | 672 +- integration/threading.lua | 24 +- lovethreads/conf.lua | 40 +- lovethreads/main.lua | 148 +- oldversions/BasicCommands.lua | 1022 +-- oldversions/EventManager(0.0.1).lua | 106 +- oldversions/EventManager(0.9.0).lua | 110 +- oldversions/EventManager(1.0.0).lua | 110 +- oldversions/EventManager(1.1.0).lua | 196 +- oldversions/EventManager(1.2.0).lua | 406 +- oldversions/EventManager(1.3.0).lua | 1456 ++-- oldversions/EventManager(5.7.3).lua | 1456 ++-- oldversions/MultiManager(0.3.0).lua | 730 +- oldversions/MultiManager(0.4.0-1).lua | 1012 +-- oldversions/MultiManager(0.4.0-5).lua | 1192 ++-- oldversions/MultiManager(0.5.1-6).lua | 1534 ++-- oldversions/MultiManager(0.6.1-6).lua | 1858 ++--- oldversions/MultiManager(0.6.2).lua | 2112 +++--- oldversions/MultiManager(0.6.3).lua | 2224 +++--- oldversions/MultiManager(1.0.0).lua | 2750 ++++---- oldversions/MultiManager(1.0.1).lua | 2782 ++++---- oldversions/MultiManager(1.2.0).lua | 2868 ++++---- oldversions/MultiManager(1.3.0).lua | 2920 ++++---- oldversions/MultiManager(1.4.1).lua | 3036 ++++---- rockspecs/multi-1.10-0.rockspec | 64 +- rockspecs/multi-1.11-0.rockspec | 62 +- rockspecs/multi-1.8-2.rockspec | 58 +- rockspecs/multi-1.8-3.rockspec | 58 +- rockspecs/multi-1.8-4.rockspec | 58 +- rockspecs/multi-1.8-5.rockspec | 58 +- rockspecs/multi-1.8-6.rockspec | 58 +- rockspecs/multi-1.9-1.rockspec | 60 +- rockspecs/multi-1.9-2.rockspec | 64 +- rockspecs/multi-1.9-3.rockspec | 64 +- rockspecs/multi-12.0-0.rockspec | 62 +- rockspecs/multi-12.2-0.rockspec | 62 +- rockspecs/multi-12.2-1.rockspec | 60 +- rockspecs/multi-13.0-0.rockspec | 60 +- rockspecs/multi-13.1-0.rockspec | 60 +- rockspecs/multi-14.0-0.rockspec | 68 +- rockspecs/multi-14.1-0.rockspec | 68 +- rockspecs/multi-14.2-0.rockspec | 66 +- rockspecs/multi-15.0-0.rockspec | 74 +- rockspecs/multi-15.1-0.rockspec | 82 +- rockspecs/multi-15.2-0.rockspec | 76 +- rockspecs/multi-15.2-1.rockspec | 76 +- rockspecs/multi-15.3-0.rockspec | 76 +- rockspecs/multi-15.3-1.rockspec | 76 +- rockspecs/multi-16.0-0.rockspec | 82 +- rockspecs/multi-16.0-1.rockspec | 82 +- rockspecs/multi-16.1-0.rockspec | 82 +- rockspecs/multi-16.2-0.rockspec | 82 +- tests/units/connections.lua | 140 +- tests/vscode-debuggee.lua | 2204 +++--- 91 files changed, 28728 insertions(+), 28727 deletions(-) diff --git a/.github/workflows/love.yml b/.github/workflows/love.yml index d38888d..6555576 100644 --- a/.github/workflows/love.yml +++ b/.github/workflows/love.yml @@ -1,25 +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: | +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 \ No newline at end of file diff --git a/.github/workflows/nix_ci.yml b/.github/workflows/nix_ci.yml index fd6b9f0..dea3806 100644 --- a/.github/workflows/nix_ci.yml +++ b/.github/workflows/nix_ci.yml @@ -1,41 +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 +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 diff --git a/.gitignore b/.gitignore index 0a96143..3e63f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -*.code-workspace -lua5.4/* +*.code-workspace +lua5.4/* test.lua \ No newline at end of file diff --git a/LICENSE b/LICENSE index f0e3a38..3d74e46 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -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, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +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, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NetworkManager.md b/NetworkManager.md index 3b40295..306df1a 100644 --- a/NetworkManager.md +++ b/NetworkManager.md @@ -1,26 +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, ...) +# NTHREAD Namespace +- [ ] NTHREAD.set(name, val) +- [ ] NTHREAD.get(name, val) +- [ ] NTHREAD.waitFor(name) +- [ ] NTHREAD.getCores()* +- [ ] NTHREAD.getConsole() +- [ ] NTHREAD.getThreads() +- [ ] NTHREAD.kill() +- [ ] NTHREAD.getName() +- [ ] NTHREAD.getID() +- [ ] NTHREAD.pushStatus(...) +- [ ] NTHREAD.sleep(n) +- [ ] NTHREAD.hold(n) +- [ ] NTHREAD.setENV(env) +- [ ] NTHREAD.getENV() + +# Extensions +- [ ] multi:newNetworkThreadedQueue(name) +- [ ] multi:newNetworkThreadedTable(name) +- [ ] multi:newNetworkThreadedJobQueue(n) +- [ ] multi:newNetworkThreadedConnection(name) + +# Core +- [ ] NTHREAD:newFunction(func, holdme) +- [ ] NTHREAD:newNetworkThread(name, func, ...) - [ ] mulit:newNetworkThread(name, func, ...) \ No newline at end of file diff --git a/README.md b/README.md index 2190b38..3919995 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,98 @@ -# Multi Version: 16.3.0 - -**Key Changes** -- Fixed connection multiplying - -Refer to the [Change Log](https://github.com/rayaman/multi/blob/master/docs/changes.md) for more infromation - -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. - -
- -Progress is being made in [v16.3.0](https://github.com/rayaman/multi/tree/v16.3.0) ---- - -
- -INSTALLING ----------- -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
-If you want to use the system threads, then you'll need to install lanes or love2d game engine! - -``` -luarocks install multi -``` - -Discord -------- -Have a question or need realtime assistance? Feel free to join the discord!
-https://discord.gg/U8UspuA - -Planned features/TODO ---------------------- -- [x] ~~Create test suite (In progress, mostly done)~~ -- [ ] Network Parallelism rework - -Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/docs/Documentation.md) ------ - -You can run tests in 2 ways: -``` -lua tests/runtests.lua (Runs all tests, attempts to use lanes) -love tests (Runs all tests in love2d env) -``` - -```lua -local multi, thread = require("multi"):init() -GLOBAL, THREAD = require("multi.integration.threading"):init() - -multi:newSystemThread("System Thread",function() - while true do - THREAD.sleep(.1) - io.write(" World") - THREAD.kill() - end -end) - -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() -end -]] -``` - -Known Bugs/Issues ------------------ -Check the [Issues tab](https://github.com/rayaman/multi/issues) for issues +# Multi Version: 16.3.0 - +**Key Changes** +- Fixed connection multiplying + +Refer to the [Change Log](https://github.com/rayaman/multi/blob/master/docs/changes.md) for more infromation + +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. + +
+ +Progress is being made in [v16.3.0](https://github.com/rayaman/multi/tree/v16.3.0) +--- + +
+ +INSTALLING +---------- +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
+If you want to use the system threads, then you'll need to install lanes or love2d game engine! + +``` +luarocks install multi +``` + +Discord +------- +Have a question or need realtime assistance? Feel free to join the discord!
+https://discord.gg/U8UspuA + +Planned features/TODO +--------------------- +- [x] ~~Create test suite (In progress, mostly done)~~ +- [ ] Network Parallelism rework + +Usage: [Check out the documentation for more info](https://github.com/rayaman/multi/blob/master/docs/Documentation.md) +----- + +You can run tests in 2 ways: +``` +lua tests/runtests.lua (Runs all tests, attempts to use lanes) +love tests (Runs all tests in love2d env) +``` + +```lua +local multi, thread = require("multi"):init() +GLOBAL, THREAD = require("multi.integration.threading"):init() + +multi:newSystemThread("System Thread",function() + while true do + THREAD.sleep(.1) + io.write(" World") + THREAD.kill() + end +end) + +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() +end +]] +``` + +Known Bugs/Issues +----------------- +Check the [Issues tab](https://github.com/rayaman/multi/issues) for issues diff --git a/docs/Documentation.md b/docs/Documentation.md index 4fa437b..7f4a9f1 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,971 +1,971 @@ -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 -
`multi.Priority_Very_High` -
`multi.Priority_High` -
`multi.Priority_Above_Normal` -
`multi.Priority_Normal` — The default level of pirority that is given to a process -
`multi.Priority_Below_Normal` -
`multi.Priority_Low` -
`multi.Priority_Very_Low` -
`multi.Priority_Idle` — Lowest level of pirority that can be given to a process - -# Multi Runners -`multi:lightloop()` — A light version of the mainloop doesn't run Coroutine based threads -
`multi:loveloop([BOOLEAN: light true])` — Run's all the love related features as well -
`multi:mainloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running -
`multi:threadloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running, but prioritizes threads over multi-objects -
`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. - -| Setting | Type: default | Purpose | -| --------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| preLoop | function: nil | This is a function that is called after all the important components of the library are loaded. This is called once only. The first and only argument passed is a reference to itself. | -| protect | boolean: false | This runs code within a protected call. To catch when errors happen see built in connections | -| stopOnError | boolean: false | This setting is used with protect. If an object crashes due to some error should it be paused? | -| priority | number: 0 | This sets the priority scheme. Look at the P-Charts below for examples. | -| auto_priority | boolean: false | **Note: This overrides any value set for priority!** If auto priority is enabled then priority scheme 3 is used and processes are considered for "recheck" after a certain amount of time. If a process isn't taking too long to complete anymore then it will be reset to core, if it starts to take a lot of time all of a sudden it will be set to idle. | -| auto_stretch | number: 1 | For use with auto_priority. Modifies the internal reperesentation of idle time by multiplying multi.Priority_Idle by the value given | -| auto_delay | number: 3 | For use with auto_priority. This changes the time in seconds that process are "rechecked" | -| auto_lowerbound | number: multi.Priority_Idle | For use with auto_priority. The lowerbound is what is considered to be idle time. A higher value combined with auto_stretch allows one to fine tune how pirority is managed. | - -# P-Chart: Priority 1 - -P1 follows a forumla that resembles this: ~n=I*PRank
Where **n** is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. - -| Priority: n | PRank | Formula | -| --------------------- | ----- | ------------ | -| Core: 3322269 | 7 | n = ~**I***7 | -| High: 2847660 | 6 | n = ~**I***6 | -| Above_Normal: 2373050 | 5 | n = ~**I***5 | -| Normal: 1898440 | 4 | n = ~**I***4 | -| Below_Normal: 1423830 | 3 | n = ~**I***3 | -| Low: 949220 | 2 | n = ~**I***2 | -| **I**dle: 474610 | 1 | n = ~**I***1 | - -**General Rule:** ~n=**I***PRank - -# P-Chart: Priority 2 - -P2 follows a formula that resembles this: ~n=n*4 where n starts as the initial idle time, see chart below. The goal of this one was to make core process’ higher while keeping idle process’ low. - -| Priority: n | -|-| -| Core: 6700821| -| High: 1675205| -| Above_Normal: 418801| -| Normal: 104700| -| Below_Normal: 26175| -| Low: 6543| -| **I**dle: 1635| - -**General Rule:** `~n=n*4` Where the inital n = **I** - -# P-Chart: Priority 3 -P3 Ignores using a basic formula and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! - -There are 2 settings for this: Core and Idle. If a process takes too long then it is set to idle. Otherwise it will stay core. - -Example of settings: -```lua -settings = { - preLoop = function(m) - print("All settings have been loaded!") - end, - protect = false, - stopOnError = false, - priority = 0, - auto_priority = false, - auto_stretch = 1, - auto_delay = 3, - auto_lowerbound = multi.Priority_Idle -} - --- Below are how the runners work - -multi:lightloop() -- lighter version of mainloop. Everything except priority management for non service objects will function like normal! - --- or - -multi:mainloop(settings) -- normal runner - --- or - -multi:threadloop(settings) -- Prioritizes threads over multi-objs - --- or - -while true do - multi:uManager(settings) -- allows you to run the multi main loop within another loop -end -``` - -# Non-Actors -`timer = multi:newTimer()` -- `conn = multi:newConnection([BOOLEAN protect true])` -- `func = multi:newFunction(FUNCTION func)` - -# Actors -- `event = multi:newEvent(FUNCTION task)` -- `updater = multi:newUpdater([NUMBER skip 1])` -- `alarm = multi:newAlarm([NUMBER 0])` -- `loop = multi:newLoop(FUNCTION func)` -- `tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1])` -- `step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` -- `tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1])` - -**Note:** A lot of methods will return itself as a return. This allows for chaining of methods to work. - -# Non-Actor: Timers -`timer = multi:newTimer()` — Creates a timer object that can keep track of time - -- **self** = timer:Start() — Starts the timer -- time_elapsed = timer:Get() — Returns the time elapsed since timer:Start() was called -- boolean = timer:isPaused() — Returns if the timer is paused or not -- **self** = timer:Pause() — Pauses the timer, it skips time that would be counted during the time that it is paused -- **self** = timer:Resume() — Resumes a paused timer. **See note below** -- **self** = timer:tofile(**STRING** path) — Saves the object to a file at location path - -**Note:** If a timer was paused after 1 second then resumed a second later and Get() was called a second later, timer would have 2 seconds counted though 3 really have passed. - -# Non-Actor: Connections -`conn = multi:newConnection([BOOLEAN: protect true],FUNCTION: callback, BOOLEAN: kill false)` — -Creates a connection object and defaults to a protective state. All calls will run within pcall() callback if it exists will be triggered each time the connection is fired. kill when set to true makes the connection object work like a queue. Where all the events that are fired is removed from the queue. -- `self = conn:HoldUT([NUMBER n 0])` — Will hold futhur execution of the thread until the connection was triggered. If n is supplied the connection must be triggered n times before it will allow ececution to continue. -- `conntable_old = conn:Bind(TABLE conntable)` — sets the table to hold the connections. A quick way to destroy all connections is by binding it to a new table. -- `conntable = conn:Remove()` — Removes all connections. Returns the conntable -- `link = conn:connect(FUNCTION func, [STRING name nil], [NUMBER num #conns+1])` — Connects to the object using function func which will recieve the arguments passed by Fire(...). You can name a connection, which allows you to use conn:getConnection(name). Names must be unique! num is simple the position in the order in which connections are triggered. The return Link is the link to the connected event that was made. You can remove this event or even trigger it specifically if need be. -- `link:Fire(...)` — Fires the created event -- `bool = link:Destroy()` — returns true if success. -- `subConn = conn:getConnection(STRING name, BOOLEAN ingore)` — returns the sub connection which matches name. -returns or nil - - subConn:Fire() — "returns" if non-nil is a table containing return values from the triggered connections. -- `self = conn:tofile(STRING path)` — Saves the object to a file at location path - -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 -multi,thread = require("multi"):init() --- Let’s create the events -yawn={} -OnCustomSafeEvent=multi:newConnection(true) -- lets pcall the calls in case something goes wrong default -OnCustomEvent=multi:newConnection(false) -- let’s not pcall the calls and let errors happen. -OnCustomEvent:Bind(yawn) -- create the connection lookup data in yawn - --- Let’s connect to them, a recent update adds a nice syntax to connect to these -cd1=OnCustomSafeEvent:Connect(function(arg1,arg2,...) - print("CSE1",arg1,arg2,...) -end,"bob") -- let’s give this connection a name -cd2=OnCustomSafeEvent:Connect(function(arg1,arg2,...) - print("CSE2",arg1,arg2,...) -end,"joe") -- let’s give this connection a name -cd3=OnCustomSafeEvent:Connect(function(arg1,arg2,...) - print("CSE3",arg1,arg2,...) -end) -- let’s not give this connection a name - --- Using syntax sugar -OnCustomEvent(function(arg1,arg2,...) - print(arg1,arg2,...) -end) - --- Now within some loop/other object you trigger the connection like -OnCustomEvent:Fire(1,2,"Hello!!!") -- fire all connections - --- You may have noticed that some events have names! See the following example! -OnCustomSafeEvent:getConnection("bob"):Fire(1,100,"Bye!") -- fire only bob! -OnCustomSafeEvent:getConnection("joe"):Fire(1,100,"Hello!") -- fire only joe!! -OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all!!! - --- Connections have more to them than that though! --- As seen above cd1-cd3 these are hooks to the connection object. This allows you to remove a connection --- For Example: -cd1:Remove() -- remove this connection from the master connection object -print("------") -OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!! --- To remove all connections use: -OnCustomSafeEvent:Remove() -print("------") -OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!! -``` - -# Semi-Actors: timeouts -Timeouts are a collection of methods that allow you to handle timeouts. These only work on multi-objs, and much of the functionality can easly be done now using threads! - -```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -multi,thread = require("multi"):init() - -loop = multi:newLoop(function() - -- do stuff -end) - -loop:SetTime(3) -multi:newAlarm(2):OnRing(function() - -- some condition that leads to resolving the timer - loop:ResolveTimer(true,"We good") - multi:newAlarm(2):OnRing(function() - loop:SetTime(2) - end) -end) - -loop:OnTimedOut(function() - print("Timeout") -end) - -loop:OnTimerResolved(function(self,...) - print(...) -end) - -multi:mainloop() -``` -As mentioned above this is made much easier using threads -```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -multi, thread = require("multi"):init() -func = thread:newFunction(function(a) - return thread.holdFor(3,function() - return a==5 and "This is returned" -- Condition being tested! - end) -end,true) -print(func(5)) -print(func(0)) --- You actually do not need the light/mainloop or any runner for threaded functions to work --- multi:lightloop() -``` - -# Semi-Actors: scheduleJob -`multi:scheduleJob(TABLE: time, FUNCTION: callback)` -- `TABLE: time` - - `NUMBER: time.min` — Minute(0-59) Repeats every hour - - `NUMBER: time.hour` — Hour(0-23) Repeats every day - - `NUMBER: time.day` — Day of month(1-31) repeats every month - - `NUMBER: time.wday` — Weekday(0-6) repeats every week - - `NUMBER: time.month` — Month(1-12) repeats every year -- `FUNCTION: callback` - - Called when the time table is matched - -Example: -```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -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:mainloop() -``` - -# Universal Actor methods -All of these functions are found on actors -- `self = multiObj:Pause()` — Pauses the actor from running -- `self = multiObj:Resume()` — Resumes the actor that was paused -- `nil = multiObj:Destroy()` — Removes the object from the mainloop -- `bool = multiObj:isPaused()` — Returns true if the object is paused, false otherwise -- `string = multiObj:getType()` — Returns the type of the object -- `self = multiObj:SetTime(n)` — Sets a timer, and creates a special "timemaster" actor, which will timeout unless ResolveTimer is called -- `self = multiObj:ResolveTimer(...)` — Stops the timer that was put onto the multiObj from timing out -- `self = multiObj:OnTimedOut(func)` — If ResolveTimer was not called in time this event will be triggered. The function connected to it get a refrence of the original object that the timer was created on as the first argument. -- `self = multiObj:OnTimerResolved(func)` — This event is triggered when the timer gets resolved. Same argument as above is passed, but the variable arguments that are accepted in resolvetimer are also passed as well. -- `self = multiObj:Reset(n)` — In the cases where it isn't obvious what it does, it acts as Resume() -- `self = multiObj:SetName(STRING name)` - -# Actor: Events -`event = multi:newEvent(FUNCTION task)` — The object that started it all. These are simply actors that wait for a condition to take place, then auto triggers an event. The event when triggered once isn't triggered again unless you Reset() it. - -- `self = event:SetTask(FUNCTION func)` — This function is not needed if you supplied task at construction time -- `self = event:OnEvent(FUNCTION func)` — Connects to the OnEvent event passes argument self to the connectee - -Example: -```lua -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) - count=count+1 -end) -event=multi:newEvent(function() return count==100 end) -- set the event -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:mainloop() -``` - -# Actor: Updaters -`updater = multi:newUpdater([NUMBER skip 1])` — set the amount of steps that are skipped. - -Updaters are a mix between both loops and steps. They were a way to add basic priority management to loops (until a better way was added). Now they aren't as useful, but if you do not want the performance hit of turning on priority then they are useful to auro skip some loops. Note: The performance hit due to priority management is not as bas as it used to be. - -- `self = updater:SetSkip(NUMBER n)` — sets the amount of steps that are skipped -- `self = OnUpdate(FUNCTION func)` — connects to the main trigger of the updater which is called every nth step - -Example: -```lua -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:mainloop() -``` - -# Actor: Alarms -`alarm = multi:newAlarm([NUMBER 0])` — creates an alarm which waits n seconds -Alarms ring after a certain amount of time, but you need to reset the alarm every time it rings! Use a TLoop if you do not want to have to reset. - -- `self = alarm:Reset([NUMBER sec current_time_set])` — Allows one to reset an alarm, optional argument to change the time until the next ring. -- `self = alarm:OnRing(FUNCTION func` — Allows one to connect to the alarm event which is triggerd after a certain amount of time has passed. - -Example: -```lua -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:mainloop() -``` - -# Actor: Loops -`loop = multi:newLoop(FUNCTION func)` — func the main connection that you can connect to. Is optional, but you can also use OnLoop(func) to connect as well. -Loops are events that happen over and over until paused. They act like a while loop. - -- `self = OnLoop(FUNCTION func)` — func the main connection that you can connect to. Alllows multiple connections to one loop if need be. - -Example: -```lua -package.path="?/init.lua;?.lua;"..package.path -multi,thread = require("multi"):init() -local a = 0 -loop = multi:newLoop(function() - a = a + 1 - if a == 1000 then - print("a = 1000") - loop:Pause() - end -end) -multi:mainloop() -``` - -# Actor: TLoops -`tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1])` — TLoops are pretty much the same as loops. The only difference is that they take set which is how long it waits, in seconds, before triggering function func. - -- `self = OnLoop(FUNCTION func)` — func the main connection that you can connect to. Alllows multiple connections to one TLoop if need be. - -Example: -```lua -package.path="?/init.lua;?.lua;"..package.path -multi,thread = require("multi"):init() -local a = 0 -loop = multi:newTLoop(function() - a = a + 1 - if a == 10 then - print("a = 10") - loop:Pause() - end -end,1) -multi:mainloop() -``` - -# Actor: Steps -`step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` — Steps were originally introduced to bs used as for loops that can run parallel with other code. When using steps think of it like this: `for i=start,reset,count do` When the skip argument is given, each time the step object is given cpu cycles it will be skipped by n cycles. So if skip is 1 every other cpu cycle will be alloted to the step object. - -- `self = step:OnStart(FUNCTION func(self))` — This connects a function to an event that is triggered everytime a step starts. -- `self = step:OnStep(FUNCTION func(self,i))` — This connects a function to an event that is triggered every step or cycle that is alloted to the step object -- `self = step:OnEnd(FUNCTION func(self))` — This connects a function to an event that is triggered when a step reaches its goal -- `self = step:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` — Update can be used to change the goals of the step. -- `self = step:Reset()` — Resets the step - -Example: -```lua -package.path="?/init.lua;?.lua;"..package.path -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:mainloop() -``` - -# Actor: TSteps -`tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1])` — TSteps work just like steps, the only difference is that instead of skip, we have set which is how long in seconds it should wait before triggering the OnStep() event. - -- `self = tstep:OnStart(FUNCTION func(self))` — This connects a function to an event that is triggered everytime a step starts. -- `self = tstep:OnStep(FUNCTION func(self,i))` — This connects a function to an event that is triggered every step or cycle that is alloted to the step object -- `self = tstep:OnEnd(FUNCTION func(self))` — This connects a function to an event that is triggered when a step reaches its goal -- `self = tstep:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER set 1])` — Update can be used to change the goals of the step. You should call step:Reset() after using Update to restart the step. -- `self = tstep:Reset([NUMBER n set])` — Allows you to reset a tstep that has ended, but also can change the time between each trigger. - -Example: -```lua -package.path="?/init.lua;?.lua;"..package.path -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:mainloop() -``` - -# Coroutine based Threading (CBT) -Helpful methods are wrapped around the builtin coroutine module which make it feel like real threading. - -**threads.\* used within threaded enviroments** -- `thread.sleep(NUMBER n)` — Holds execution of the thread until a certain amount of time has passed -- `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 -- `BOOLEAM bool = thread.isThread()` — Returns true if the current running code is inside of a coroutine based thread -- `NUMBER conres = thread.getCores()` — Returns the number of cores that the current system has. (used for system threads) -- `thread.set(STRING name, VARIABLE val)` — A global interface where threads can talk with eachother. sets a variable with name and its value -- `thread.get(STRING name)` — Gets the data stored in name -- `VARIABLE val = thread.waitFor(STRING name)` — Holds executon of a thread until variable name exists -- `thread.request(THREAD th,STRING cmd, VARIABLE args)` — Sends a request to the selected thread telling it to do a certain command -- `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. -- `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) -``` - -\*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. - -# CBT: newService(FUNCTION: func) -`serv = newService(FUNCTION: func(self,TABLE: data))` — func is called each time the service is updated think of it like a loop multi-obj. self is the service object and data is a private table that only the service can see. -- `serv.OnError(FUNCTION: func)` — connection that fired if there is an error -- `serv.OnStopped(FUNCTION: func(serv))` — connection that is fired when a service is stopped -- `serv.OnStarted(FUNCTION: func(serv))` — connection that is fired when a service is started -- `serv.Start()` — Starts the service -- `serv.Stop()` — Stops the service and destroys the data table -- `serv.Pause()` — Pauses the service -- `serv.Resume()` — Resumes the service -- `serv.GetUpTime()` — Returns the amount of time the service has been running -- `serv.SetPriority(PRIORITY: pri)` — Sets the priority of the service - - `multi.Priority_Core` - - `multi.Priority_Very_High` - - `multi.Priority_High` - - `multi.Priority_Above_Normal` - - `multi.Priority_Normal` **Default** - - `multi.Priority_Below_Normal` - - `multi.Priority_Low` - - `multi.Priority_Very_Low` - - `multi.Priority_Idle` -- `serv.SetScheme(NUMBER: n)` — Sets the scheme of the priority management - - `1` **Default** — uses a time based style of yielding. thread.sleep() - - `2` — uses a cycle based style of yielding. thread.skip() -- `CONVERTS(serv) = serv.Destroy()` — Stops the service then Destroys the service triggering all events! The service becomes a destroyed object - -Example: -```lua --- 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 -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 - job.func() - end -end) -multi.Jobs.OnStarted(function(self,jobs) - function self:newJob(func,name) - table.insert(jobs,{ - func = func, - name = name, - removeJob = function(self) self.removed = true end - }) - end - function self:getJobs(name) - local tab = {} - if not name then return jobs end - for i=1,#jobs do - if name == jobs[i].name then - table.insert(tab,jobs[i]) - end - end - return tab - end - function self:removeJobs(name) - for i=1,#jobs do - if name ~= nil and name == jobs[i].name then - jobs[i]:removeJob() - elseif name == nil then - jobs[i]:removeJob() - end - end - end -end) -multi.Jobs.SetPriority(multi.Priority_Normal) -multi.Jobs.Start() - --- Testing job stuff -function pushJobs() - multi.Jobs:newJob(function() - print("job called") - end) -- No name job - multi.Jobs:newJob(function() - print("job called2") - end,"test") - multi.Jobs:newJob(function() - print("job called3") - end,"test2") -end -pushJobs() -pushJobs() -local jobs = multi.Jobs:getJobs() -- gets all jobs -local jobsn = multi.Jobs:getJobs("test") -- gets all jobs names 'test' -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() -`th = multi:newThread([STRING name,] FUNCTION func)` — Creates a new thread with name and function. - -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. -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 - -*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 - -Examples: -```lua -package.path="?/init.lua;?.lua;"..package.path -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: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 - -*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.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") --* -``` -Using this integration modifies some methods that the multi library has. -- `multi:canSystemThread()` — Returns true if system threading is possible. -- `multi:getPlatform()` — Returns (for now) either "lanes", "love2d" and "luvit" -- `multi.isMainThread = true` — This is only modified on the main thread. So code that moves from one thread to another knows where it's at. - -*GLOBAL and THREAD do not do anything when using the luvit integration - -# ST - THREAD namespace -- `THREAD.set(STRING name, VALUE val)` — Sets a value in GLOBAL -- `THREAD.get(STRING name)` — Gets a value in GLOBAL -- `THREAD.waitFor(STRING name)` — Waits for a value in GLOBAL to exist -- `THREAD.getCores()` — Returns the number of actual system threads/cores -- `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, 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 -Treat global like a table. -```lua -GLOBAL["name"] = "Ryan" -print(GLOBAL["name"]) -``` -Removes the need to use THREAD.set() and THREAD.get() - -ST - System Threads -------------------- -- `systemThread = multi:newSystemThread(STRING thread_name, FUNCTION spawned_function,ARGUMENTS ...)` — Spawns a thread with a certain name. -- `systemThread:kill()` — kills a thread; can only be called in the main thread! -- `systemThread.OnError(FUNCTION(systemthread,errMsg,errorMsgWithThreadName))` - -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 -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!") - -- we could do work but theres no need to we can save that for other examples - print("Lets have a non ending loop!") - while true do - -- If this was not in a thread execution would halt for the entire process - end -end,"A message that we are passing") -- There are restrictions on what can be passed! - -tloop = multi:newTLoop(function() - print("I'm still kicking!") -end,1) -multi:mainloop() -``` - -*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. - -**Note:** luvit currently has very basic support, it only allows the spawning of system threads, but no way to send data back and forth as of yet. I do not know if this is doable or not, but I will keep looking into it. - -# ST - System Threaded Objects -Great we are able to spawn threads, but unless your working with a process that works on passed data and then uses a socket or writes to the disk I can't do to much with out being able to pass data between threads. This section we will look at how we can share objects between threads. In order to keep the compatibility between both love2d and lanes I had to format the system threaded objects in a strange way, but they are consistant and should work on both enviroments. - -When creating objects with a name they are automatically exposed to the GLOBAL table. Which means you can retrieve them from a spawned thread. For example we have a queue object, which will be discussed in more detail next. - -```lua --- Exposing a queue -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 - queue = THREAD.waitFor("myQueue"):init() -- Get the queue. It is good pratice to use the waitFor command when getting objects. If it doesn't exist yet we wait for it, preventing future errors. It is possible for the data to not ve present when a thread is looking for it! Especally when using the love2d module, my fault needs some rewriting data passing on the GLOBAL is quite slow, but the queue internally uses channels so after it is exposed you should have good speeds! - local data = queue:pop() -- Get the data - print(data) -- print the data -end) -multi:mainloop() -``` - -# ST - SystemThreadedQueue -- `queue(nonInit) = multi:newSystemThreadedQueue(STRING name)` — You must enter a name! -- `queue = queue:init()` — initiates the queue, without doing this it will not work -- `void = queue:push(DATA data)` — Pushes data into a queue that all threads that have been shared have access to -- `data = queue:pop()` — pops data from the queue removing it from all threads -- `data = queue:peek()` — looks at data that is on the queue, but dont remove it from the queue - -Let's get into some examples: -```lua -multi,thread = require("multi"):init() -thread_names = {"Thread_A","Thread_B","Thread_C","Thread_D"} -local GLOBAL, THREAD = require("multi.integration.threading"):init() -queue = multi:newSystemThreadedQueue("myQueue"):init() -for _,n in pairs(thread_names) do - multi:newSystemThread(n,function() - queue = THREAD.waitFor("myQueue"):init() - local name = THREAD.getName() - local data = queue:pop() - while data do - print(name.." "..data) - data = queue:pop() - end - end) -end -for i=1,100 do - queue:push(math.random(1,1000)) -end -multi:newEvent(function() -- Felt like using the event object, I hardly use them for anything non internal - return not queue:peek() -end):OnEvent(function() - print("No more data within the queue!") - os.exit() -end) -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 -- `self = jq:registerFunction(STRING: name, FUNCTION: func)` — Registers a function on the job queue. Name is the name of function func -- `jID = jq:pushJob(STRING: name,[...])` — Pushes a job onto the jobqueue -- `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,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 -end) -jq:registerFunction("test",function(a,b) - --print(a,b,a+b) - return true -end) -jq.OnJobCompleted(function(jid,arg) - print(jid,arg) -end) -local jid = jq:pushJob("test",10,5) -print("Job pushed! ID = ".. jid) -local func = jq:newFunction("test2",function(a,b) - print(a,b,a*b) - return -end) -print("Waited",func(10,5).wait()) -func(5,5).connect(function(ret) - print("Connected",ret) - os.exit() -end) -multi:mainloop() -``` -# ST - SystemThreadedTable -`stt = multi:newSystemThreadedTable(STRING: name)` -- `stt:init()` — Used to init object over threads -- `stt[var] = val` -- `val = stt[var]` - -Example: -```lua -package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path -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: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 - +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 +
`multi.Priority_Very_High` +
`multi.Priority_High` +
`multi.Priority_Above_Normal` +
`multi.Priority_Normal` — The default level of pirority that is given to a process +
`multi.Priority_Below_Normal` +
`multi.Priority_Low` +
`multi.Priority_Very_Low` +
`multi.Priority_Idle` — Lowest level of pirority that can be given to a process + +# Multi Runners +`multi:lightloop()` — A light version of the mainloop doesn't run Coroutine based threads +
`multi:loveloop([BOOLEAN: light true])` — Run's all the love related features as well +
`multi:mainloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running +
`multi:threadloop([TABLE settings])` — This runs the mainloop by having its own internal while loop running, but prioritizes threads over multi-objects +
`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. + +| Setting | Type: default | Purpose | +| --------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| preLoop | function: nil | This is a function that is called after all the important components of the library are loaded. This is called once only. The first and only argument passed is a reference to itself. | +| protect | boolean: false | This runs code within a protected call. To catch when errors happen see built in connections | +| stopOnError | boolean: false | This setting is used with protect. If an object crashes due to some error should it be paused? | +| priority | number: 0 | This sets the priority scheme. Look at the P-Charts below for examples. | +| auto_priority | boolean: false | **Note: This overrides any value set for priority!** If auto priority is enabled then priority scheme 3 is used and processes are considered for "recheck" after a certain amount of time. If a process isn't taking too long to complete anymore then it will be reset to core, if it starts to take a lot of time all of a sudden it will be set to idle. | +| auto_stretch | number: 1 | For use with auto_priority. Modifies the internal reperesentation of idle time by multiplying multi.Priority_Idle by the value given | +| auto_delay | number: 3 | For use with auto_priority. This changes the time in seconds that process are "rechecked" | +| auto_lowerbound | number: multi.Priority_Idle | For use with auto_priority. The lowerbound is what is considered to be idle time. A higher value combined with auto_stretch allows one to fine tune how pirority is managed. | + +# P-Chart: Priority 1 + +P1 follows a forumla that resembles this: ~n=I*PRank
Where **n** is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. + +| Priority: n | PRank | Formula | +| --------------------- | ----- | ------------ | +| Core: 3322269 | 7 | n = ~**I***7 | +| High: 2847660 | 6 | n = ~**I***6 | +| Above_Normal: 2373050 | 5 | n = ~**I***5 | +| Normal: 1898440 | 4 | n = ~**I***4 | +| Below_Normal: 1423830 | 3 | n = ~**I***3 | +| Low: 949220 | 2 | n = ~**I***2 | +| **I**dle: 474610 | 1 | n = ~**I***1 | + +**General Rule:** ~n=**I***PRank + +# P-Chart: Priority 2 + +P2 follows a formula that resembles this: ~n=n*4 where n starts as the initial idle time, see chart below. The goal of this one was to make core process’ higher while keeping idle process’ low. + +| Priority: n | +|-| +| Core: 6700821| +| High: 1675205| +| Above_Normal: 418801| +| Normal: 104700| +| Below_Normal: 26175| +| Low: 6543| +| **I**dle: 1635| + +**General Rule:** `~n=n*4` Where the inital n = **I** + +# P-Chart: Priority 3 +P3 Ignores using a basic formula and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! + +There are 2 settings for this: Core and Idle. If a process takes too long then it is set to idle. Otherwise it will stay core. + +Example of settings: +```lua +settings = { + preLoop = function(m) + print("All settings have been loaded!") + end, + protect = false, + stopOnError = false, + priority = 0, + auto_priority = false, + auto_stretch = 1, + auto_delay = 3, + auto_lowerbound = multi.Priority_Idle +} + +-- Below are how the runners work + +multi:lightloop() -- lighter version of mainloop. Everything except priority management for non service objects will function like normal! + +-- or + +multi:mainloop(settings) -- normal runner + +-- or + +multi:threadloop(settings) -- Prioritizes threads over multi-objs + +-- or + +while true do + multi:uManager(settings) -- allows you to run the multi main loop within another loop +end +``` + +# Non-Actors +`timer = multi:newTimer()` +- `conn = multi:newConnection([BOOLEAN protect true])` +- `func = multi:newFunction(FUNCTION func)` + +# Actors +- `event = multi:newEvent(FUNCTION task)` +- `updater = multi:newUpdater([NUMBER skip 1])` +- `alarm = multi:newAlarm([NUMBER 0])` +- `loop = multi:newLoop(FUNCTION func)` +- `tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1])` +- `step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` +- `tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1])` + +**Note:** A lot of methods will return itself as a return. This allows for chaining of methods to work. + +# Non-Actor: Timers +`timer = multi:newTimer()` — Creates a timer object that can keep track of time + +- **self** = timer:Start() — Starts the timer +- time_elapsed = timer:Get() — Returns the time elapsed since timer:Start() was called +- boolean = timer:isPaused() — Returns if the timer is paused or not +- **self** = timer:Pause() — Pauses the timer, it skips time that would be counted during the time that it is paused +- **self** = timer:Resume() — Resumes a paused timer. **See note below** +- **self** = timer:tofile(**STRING** path) — Saves the object to a file at location path + +**Note:** If a timer was paused after 1 second then resumed a second later and Get() was called a second later, timer would have 2 seconds counted though 3 really have passed. + +# Non-Actor: Connections +`conn = multi:newConnection([BOOLEAN: protect true],FUNCTION: callback, BOOLEAN: kill false)` — +Creates a connection object and defaults to a protective state. All calls will run within pcall() callback if it exists will be triggered each time the connection is fired. kill when set to true makes the connection object work like a queue. Where all the events that are fired is removed from the queue. +- `self = conn:HoldUT([NUMBER n 0])` — Will hold futhur execution of the thread until the connection was triggered. If n is supplied the connection must be triggered n times before it will allow ececution to continue. +- `conntable_old = conn:Bind(TABLE conntable)` — sets the table to hold the connections. A quick way to destroy all connections is by binding it to a new table. +- `conntable = conn:Remove()` — Removes all connections. Returns the conntable +- `link = conn:connect(FUNCTION func, [STRING name nil], [NUMBER num #conns+1])` — Connects to the object using function func which will recieve the arguments passed by Fire(...). You can name a connection, which allows you to use conn:getConnection(name). Names must be unique! num is simple the position in the order in which connections are triggered. The return Link is the link to the connected event that was made. You can remove this event or even trigger it specifically if need be. +- `link:Fire(...)` — Fires the created event +- `bool = link:Destroy()` — returns true if success. +- `subConn = conn:getConnection(STRING name, BOOLEAN ingore)` — returns the sub connection which matches name. +returns or nil + - subConn:Fire() — "returns" if non-nil is a table containing return values from the triggered connections. +- `self = conn:tofile(STRING path)` — Saves the object to a file at location path + +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 +multi,thread = require("multi"):init() +-- Let’s create the events +yawn={} +OnCustomSafeEvent=multi:newConnection(true) -- lets pcall the calls in case something goes wrong default +OnCustomEvent=multi:newConnection(false) -- let’s not pcall the calls and let errors happen. +OnCustomEvent:Bind(yawn) -- create the connection lookup data in yawn + +-- Let’s connect to them, a recent update adds a nice syntax to connect to these +cd1=OnCustomSafeEvent:Connect(function(arg1,arg2,...) + print("CSE1",arg1,arg2,...) +end,"bob") -- let’s give this connection a name +cd2=OnCustomSafeEvent:Connect(function(arg1,arg2,...) + print("CSE2",arg1,arg2,...) +end,"joe") -- let’s give this connection a name +cd3=OnCustomSafeEvent:Connect(function(arg1,arg2,...) + print("CSE3",arg1,arg2,...) +end) -- let’s not give this connection a name + +-- Using syntax sugar +OnCustomEvent(function(arg1,arg2,...) + print(arg1,arg2,...) +end) + +-- Now within some loop/other object you trigger the connection like +OnCustomEvent:Fire(1,2,"Hello!!!") -- fire all connections + +-- You may have noticed that some events have names! See the following example! +OnCustomSafeEvent:getConnection("bob"):Fire(1,100,"Bye!") -- fire only bob! +OnCustomSafeEvent:getConnection("joe"):Fire(1,100,"Hello!") -- fire only joe!! +OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all!!! + +-- Connections have more to them than that though! +-- As seen above cd1-cd3 these are hooks to the connection object. This allows you to remove a connection +-- For Example: +cd1:Remove() -- remove this connection from the master connection object +print("------") +OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!! +-- To remove all connections use: +OnCustomSafeEvent:Remove() +print("------") +OnCustomSafeEvent:Fire(1,100,"Hi Ya Folks!!!") -- fire them all again!!! +``` + +# Semi-Actors: timeouts +Timeouts are a collection of methods that allow you to handle timeouts. These only work on multi-objs, and much of the functionality can easly be done now using threads! + +```lua +package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path +multi,thread = require("multi"):init() + +loop = multi:newLoop(function() + -- do stuff +end) + +loop:SetTime(3) +multi:newAlarm(2):OnRing(function() + -- some condition that leads to resolving the timer + loop:ResolveTimer(true,"We good") + multi:newAlarm(2):OnRing(function() + loop:SetTime(2) + end) +end) + +loop:OnTimedOut(function() + print("Timeout") +end) + +loop:OnTimerResolved(function(self,...) + print(...) +end) + +multi:mainloop() +``` +As mentioned above this is made much easier using threads +```lua +package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path +multi, thread = require("multi"):init() +func = thread:newFunction(function(a) + return thread.holdFor(3,function() + return a==5 and "This is returned" -- Condition being tested! + end) +end,true) +print(func(5)) +print(func(0)) +-- You actually do not need the light/mainloop or any runner for threaded functions to work +-- multi:lightloop() +``` + +# Semi-Actors: scheduleJob +`multi:scheduleJob(TABLE: time, FUNCTION: callback)` +- `TABLE: time` + - `NUMBER: time.min` — Minute(0-59) Repeats every hour + - `NUMBER: time.hour` — Hour(0-23) Repeats every day + - `NUMBER: time.day` — Day of month(1-31) repeats every month + - `NUMBER: time.wday` — Weekday(0-6) repeats every week + - `NUMBER: time.month` — Month(1-12) repeats every year +- `FUNCTION: callback` + - Called when the time table is matched + +Example: +```lua +package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path +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:mainloop() +``` + +# Universal Actor methods +All of these functions are found on actors +- `self = multiObj:Pause()` — Pauses the actor from running +- `self = multiObj:Resume()` — Resumes the actor that was paused +- `nil = multiObj:Destroy()` — Removes the object from the mainloop +- `bool = multiObj:isPaused()` — Returns true if the object is paused, false otherwise +- `string = multiObj:getType()` — Returns the type of the object +- `self = multiObj:SetTime(n)` — Sets a timer, and creates a special "timemaster" actor, which will timeout unless ResolveTimer is called +- `self = multiObj:ResolveTimer(...)` — Stops the timer that was put onto the multiObj from timing out +- `self = multiObj:OnTimedOut(func)` — If ResolveTimer was not called in time this event will be triggered. The function connected to it get a refrence of the original object that the timer was created on as the first argument. +- `self = multiObj:OnTimerResolved(func)` — This event is triggered when the timer gets resolved. Same argument as above is passed, but the variable arguments that are accepted in resolvetimer are also passed as well. +- `self = multiObj:Reset(n)` — In the cases where it isn't obvious what it does, it acts as Resume() +- `self = multiObj:SetName(STRING name)` + +# Actor: Events +`event = multi:newEvent(FUNCTION task)` — The object that started it all. These are simply actors that wait for a condition to take place, then auto triggers an event. The event when triggered once isn't triggered again unless you Reset() it. + +- `self = event:SetTask(FUNCTION func)` — This function is not needed if you supplied task at construction time +- `self = event:OnEvent(FUNCTION func)` — Connects to the OnEvent event passes argument self to the connectee + +Example: +```lua +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) + count=count+1 +end) +event=multi:newEvent(function() return count==100 end) -- set the event +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:mainloop() +``` + +# Actor: Updaters +`updater = multi:newUpdater([NUMBER skip 1])` — set the amount of steps that are skipped. + +Updaters are a mix between both loops and steps. They were a way to add basic priority management to loops (until a better way was added). Now they aren't as useful, but if you do not want the performance hit of turning on priority then they are useful to auro skip some loops. Note: The performance hit due to priority management is not as bas as it used to be. + +- `self = updater:SetSkip(NUMBER n)` — sets the amount of steps that are skipped +- `self = OnUpdate(FUNCTION func)` — connects to the main trigger of the updater which is called every nth step + +Example: +```lua +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:mainloop() +``` + +# Actor: Alarms +`alarm = multi:newAlarm([NUMBER 0])` — creates an alarm which waits n seconds +Alarms ring after a certain amount of time, but you need to reset the alarm every time it rings! Use a TLoop if you do not want to have to reset. + +- `self = alarm:Reset([NUMBER sec current_time_set])` — Allows one to reset an alarm, optional argument to change the time until the next ring. +- `self = alarm:OnRing(FUNCTION func` — Allows one to connect to the alarm event which is triggerd after a certain amount of time has passed. + +Example: +```lua +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:mainloop() +``` + +# Actor: Loops +`loop = multi:newLoop(FUNCTION func)` — func the main connection that you can connect to. Is optional, but you can also use OnLoop(func) to connect as well. +Loops are events that happen over and over until paused. They act like a while loop. + +- `self = OnLoop(FUNCTION func)` — func the main connection that you can connect to. Alllows multiple connections to one loop if need be. + +Example: +```lua +package.path="?/init.lua;?.lua;"..package.path +multi,thread = require("multi"):init() +local a = 0 +loop = multi:newLoop(function() + a = a + 1 + if a == 1000 then + print("a = 1000") + loop:Pause() + end +end) +multi:mainloop() +``` + +# Actor: TLoops +`tloop = multi:newTLoop(FUNCTION func ,NUMBER: [set 1])` — TLoops are pretty much the same as loops. The only difference is that they take set which is how long it waits, in seconds, before triggering function func. + +- `self = OnLoop(FUNCTION func)` — func the main connection that you can connect to. Alllows multiple connections to one TLoop if need be. + +Example: +```lua +package.path="?/init.lua;?.lua;"..package.path +multi,thread = require("multi"):init() +local a = 0 +loop = multi:newTLoop(function() + a = a + 1 + if a == 10 then + print("a = 10") + loop:Pause() + end +end,1) +multi:mainloop() +``` + +# Actor: Steps +`step = multi:newStep(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` — Steps were originally introduced to bs used as for loops that can run parallel with other code. When using steps think of it like this: `for i=start,reset,count do` When the skip argument is given, each time the step object is given cpu cycles it will be skipped by n cycles. So if skip is 1 every other cpu cycle will be alloted to the step object. + +- `self = step:OnStart(FUNCTION func(self))` — This connects a function to an event that is triggered everytime a step starts. +- `self = step:OnStep(FUNCTION func(self,i))` — This connects a function to an event that is triggered every step or cycle that is alloted to the step object +- `self = step:OnEnd(FUNCTION func(self))` — This connects a function to an event that is triggered when a step reaches its goal +- `self = step:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER skip 0])` — Update can be used to change the goals of the step. +- `self = step:Reset()` — Resets the step + +Example: +```lua +package.path="?/init.lua;?.lua;"..package.path +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:mainloop() +``` + +# Actor: TSteps +`tstep = multi:newStep(NUMBER start, NUMBER reset, [NUMBER count 1], [NUMBER set 1])` — TSteps work just like steps, the only difference is that instead of skip, we have set which is how long in seconds it should wait before triggering the OnStep() event. + +- `self = tstep:OnStart(FUNCTION func(self))` — This connects a function to an event that is triggered everytime a step starts. +- `self = tstep:OnStep(FUNCTION func(self,i))` — This connects a function to an event that is triggered every step or cycle that is alloted to the step object +- `self = tstep:OnEnd(FUNCTION func(self))` — This connects a function to an event that is triggered when a step reaches its goal +- `self = tstep:Update(NUMBER start,*NUMBER reset, [NUMBER count 1], [NUMBER set 1])` — Update can be used to change the goals of the step. You should call step:Reset() after using Update to restart the step. +- `self = tstep:Reset([NUMBER n set])` — Allows you to reset a tstep that has ended, but also can change the time between each trigger. + +Example: +```lua +package.path="?/init.lua;?.lua;"..package.path +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:mainloop() +``` + +# Coroutine based Threading (CBT) +Helpful methods are wrapped around the builtin coroutine module which make it feel like real threading. + +**threads.\* used within threaded enviroments** +- `thread.sleep(NUMBER n)` — Holds execution of the thread until a certain amount of time has passed +- `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 +- `BOOLEAM bool = thread.isThread()` — Returns true if the current running code is inside of a coroutine based thread +- `NUMBER conres = thread.getCores()` — Returns the number of cores that the current system has. (used for system threads) +- `thread.set(STRING name, VARIABLE val)` — A global interface where threads can talk with eachother. sets a variable with name and its value +- `thread.get(STRING name)` — Gets the data stored in name +- `VARIABLE val = thread.waitFor(STRING name)` — Holds executon of a thread until variable name exists +- `thread.request(THREAD th,STRING cmd, VARIABLE args)` — Sends a request to the selected thread telling it to do a certain command +- `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. +- `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) +``` + +\*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. + +# CBT: newService(FUNCTION: func) +`serv = newService(FUNCTION: func(self,TABLE: data))` — func is called each time the service is updated think of it like a loop multi-obj. self is the service object and data is a private table that only the service can see. +- `serv.OnError(FUNCTION: func)` — connection that fired if there is an error +- `serv.OnStopped(FUNCTION: func(serv))` — connection that is fired when a service is stopped +- `serv.OnStarted(FUNCTION: func(serv))` — connection that is fired when a service is started +- `serv.Start()` — Starts the service +- `serv.Stop()` — Stops the service and destroys the data table +- `serv.Pause()` — Pauses the service +- `serv.Resume()` — Resumes the service +- `serv.GetUpTime()` — Returns the amount of time the service has been running +- `serv.SetPriority(PRIORITY: pri)` — Sets the priority of the service + - `multi.Priority_Core` + - `multi.Priority_Very_High` + - `multi.Priority_High` + - `multi.Priority_Above_Normal` + - `multi.Priority_Normal` **Default** + - `multi.Priority_Below_Normal` + - `multi.Priority_Low` + - `multi.Priority_Very_Low` + - `multi.Priority_Idle` +- `serv.SetScheme(NUMBER: n)` — Sets the scheme of the priority management + - `1` **Default** — uses a time based style of yielding. thread.sleep() + - `2` — uses a cycle based style of yielding. thread.skip() +- `CONVERTS(serv) = serv.Destroy()` — Stops the service then Destroys the service triggering all events! The service becomes a destroyed object + +Example: +```lua +-- 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 +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 + job.func() + end +end) +multi.Jobs.OnStarted(function(self,jobs) + function self:newJob(func,name) + table.insert(jobs,{ + func = func, + name = name, + removeJob = function(self) self.removed = true end + }) + end + function self:getJobs(name) + local tab = {} + if not name then return jobs end + for i=1,#jobs do + if name == jobs[i].name then + table.insert(tab,jobs[i]) + end + end + return tab + end + function self:removeJobs(name) + for i=1,#jobs do + if name ~= nil and name == jobs[i].name then + jobs[i]:removeJob() + elseif name == nil then + jobs[i]:removeJob() + end + end + end +end) +multi.Jobs.SetPriority(multi.Priority_Normal) +multi.Jobs.Start() + +-- Testing job stuff +function pushJobs() + multi.Jobs:newJob(function() + print("job called") + end) -- No name job + multi.Jobs:newJob(function() + print("job called2") + end,"test") + multi.Jobs:newJob(function() + print("job called3") + end,"test2") +end +pushJobs() +pushJobs() +local jobs = multi.Jobs:getJobs() -- gets all jobs +local jobsn = multi.Jobs:getJobs("test") -- gets all jobs names 'test' +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() +`th = multi:newThread([STRING name,] FUNCTION func)` — Creates a new thread with name and function. + +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. +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 + +*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 + +Examples: +```lua +package.path="?/init.lua;?.lua;"..package.path +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: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 + +*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.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") --* +``` +Using this integration modifies some methods that the multi library has. +- `multi:canSystemThread()` — Returns true if system threading is possible. +- `multi:getPlatform()` — Returns (for now) either "lanes", "love2d" and "luvit" +- `multi.isMainThread = true` — This is only modified on the main thread. So code that moves from one thread to another knows where it's at. + +*GLOBAL and THREAD do not do anything when using the luvit integration + +# ST - THREAD namespace +- `THREAD.set(STRING name, VALUE val)` — Sets a value in GLOBAL +- `THREAD.get(STRING name)` — Gets a value in GLOBAL +- `THREAD.waitFor(STRING name)` — Waits for a value in GLOBAL to exist +- `THREAD.getCores()` — Returns the number of actual system threads/cores +- `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, 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 +Treat global like a table. +```lua +GLOBAL["name"] = "Ryan" +print(GLOBAL["name"]) +``` +Removes the need to use THREAD.set() and THREAD.get() + +ST - System Threads +------------------- +- `systemThread = multi:newSystemThread(STRING thread_name, FUNCTION spawned_function,ARGUMENTS ...)` — Spawns a thread with a certain name. +- `systemThread:kill()` — kills a thread; can only be called in the main thread! +- `systemThread.OnError(FUNCTION(systemthread,errMsg,errorMsgWithThreadName))` + +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 +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!") + -- we could do work but theres no need to we can save that for other examples + print("Lets have a non ending loop!") + while true do + -- If this was not in a thread execution would halt for the entire process + end +end,"A message that we are passing") -- There are restrictions on what can be passed! + +tloop = multi:newTLoop(function() + print("I'm still kicking!") +end,1) +multi:mainloop() +``` + +*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. + +**Note:** luvit currently has very basic support, it only allows the spawning of system threads, but no way to send data back and forth as of yet. I do not know if this is doable or not, but I will keep looking into it. + +# ST - System Threaded Objects +Great we are able to spawn threads, but unless your working with a process that works on passed data and then uses a socket or writes to the disk I can't do to much with out being able to pass data between threads. This section we will look at how we can share objects between threads. In order to keep the compatibility between both love2d and lanes I had to format the system threaded objects in a strange way, but they are consistant and should work on both enviroments. + +When creating objects with a name they are automatically exposed to the GLOBAL table. Which means you can retrieve them from a spawned thread. For example we have a queue object, which will be discussed in more detail next. + +```lua +-- Exposing a queue +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 + queue = THREAD.waitFor("myQueue"):init() -- Get the queue. It is good pratice to use the waitFor command when getting objects. If it doesn't exist yet we wait for it, preventing future errors. It is possible for the data to not ve present when a thread is looking for it! Especally when using the love2d module, my fault needs some rewriting data passing on the GLOBAL is quite slow, but the queue internally uses channels so after it is exposed you should have good speeds! + local data = queue:pop() -- Get the data + print(data) -- print the data +end) +multi:mainloop() +``` + +# ST - SystemThreadedQueue +- `queue(nonInit) = multi:newSystemThreadedQueue(STRING name)` — You must enter a name! +- `queue = queue:init()` — initiates the queue, without doing this it will not work +- `void = queue:push(DATA data)` — Pushes data into a queue that all threads that have been shared have access to +- `data = queue:pop()` — pops data from the queue removing it from all threads +- `data = queue:peek()` — looks at data that is on the queue, but dont remove it from the queue + +Let's get into some examples: +```lua +multi,thread = require("multi"):init() +thread_names = {"Thread_A","Thread_B","Thread_C","Thread_D"} +local GLOBAL, THREAD = require("multi.integration.threading"):init() +queue = multi:newSystemThreadedQueue("myQueue"):init() +for _,n in pairs(thread_names) do + multi:newSystemThread(n,function() + queue = THREAD.waitFor("myQueue"):init() + local name = THREAD.getName() + local data = queue:pop() + while data do + print(name.." "..data) + data = queue:pop() + end + end) +end +for i=1,100 do + queue:push(math.random(1,1000)) +end +multi:newEvent(function() -- Felt like using the event object, I hardly use them for anything non internal + return not queue:peek() +end):OnEvent(function() + print("No more data within the queue!") + os.exit() +end) +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 +- `self = jq:registerFunction(STRING: name, FUNCTION: func)` — Registers a function on the job queue. Name is the name of function func +- `jID = jq:pushJob(STRING: name,[...])` — Pushes a job onto the jobqueue +- `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,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 +end) +jq:registerFunction("test",function(a,b) + --print(a,b,a+b) + return true +end) +jq.OnJobCompleted(function(jid,arg) + print(jid,arg) +end) +local jid = jq:pushJob("test",10,5) +print("Job pushed! ID = ".. jid) +local func = jq:newFunction("test2",function(a,b) + print(a,b,a*b) + return +end) +print("Waited",func(10,5).wait()) +func(5,5).connect(function(ret) + print("Connected",ret) + os.exit() +end) +multi:mainloop() +``` +# ST - SystemThreadedTable +`stt = multi:newSystemThreadedTable(STRING: name)` +- `stt:init()` — Used to init object over threads +- `stt[var] = val` +- `val = stt[var]` + +Example: +```lua +package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path +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: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 + diff --git a/docs/changes.md b/docs/changes.md index ac10f97..0fd92a6 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,3688 +1,3688 @@ -# Changelog -Table of contents ---- -[Update 16.3.0 - Bug Fixes](#update-1620---bug-fixes)
-[Update 16.2.0 - TimeStamps and UUIDs](#update-1620---timestamps-and-uuids)
-[Update 16.1.0 - The Flow State](#update-1610---the-flow-state)
-[Update 16.0.1 - Bug fix](#update-1601---bug-fix)
-[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)
-[Update 15.3.1 - Bug fix](#update-1531---bug-fix)
-[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
-[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
-[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
-[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
-[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
-[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
-[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
-[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
-[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
-[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
-[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
-[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
-[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
-[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
-[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
-[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
-[Update: 1.11.0](#update-1110)
-[Update: 1.10.0](#update-1100)
-[Update: 1.9.2](#update-192)
-[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
-[Update: 1.9.0](#update-190)
-[Update: 1.8.7](#update-187)
-[Update: 1.8.6](#update-186)
-[Update: 1.8.5](#update-185)
-[Update: 1.8.4](#update-184)
-[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
-[Update: 1.8.2](#update-182)
-[Update: 1.8.1](#update-181)
-[Update: 1.7.6](#update-176)
-[Update: 1.7.5](#update-175)
-[Update: 1.7.4](#update-174)
-[Update: 1.7.3](#update-173)
-[Update: 1.7.2](#update-172)
-[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
-[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
-[Update: 1.6.0](#update-160)
-[Update: 1.5.0](#update-150)
-[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
-[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
-[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
-[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
-[Update: 1.1.0](#update-110)
-[Update: 1.0.0](#update-100)
-[Update: 0.6.3](#update-063)
-[Update: 0.6.2](#update-062)
-[Update: 0.6.1-6](#update-061-6)
-[Update: 0.5.1-6](#update-051-6)
-[Update: 0.4.1](#update-041)
-[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
-[Update: EventManager 2.0.0](#update-eventmanager-200)
-[Update: EventManager 1.2.0](#update-eventmanager-120)
-[Update: EventManager 1.1.0](#update-eventmanager-110)
-[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
-[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) - -# Update 16.3.0 - Bug Fixes - -Added ---- - -Changed ---- -- `thread.kill(msg)` now accepts a message. If omitted will use default `thread killed!` - -Fixed ---- -- [Issue](https://github.com/rayaman/multi/issues/77) with concat of connections where conn .. func did not work properly -- [Issue](https://github.com/rayaman/multi/issues/67) with connection multiplying - -# Update 16.2.0 - TimeStamps and UUIDs and bugfixes -Added ---- -- threadedfunctions, multiobjs and threads contain new method: `GetCreationTimestamp()` With will return a string formated date time using ISO 8601 - -Changed ---- -- Internal uuid functions to not use bitwise operators - -Fixed ---- -- [Bitwise operations break lua 5.1/luajit](https://github.com/rayaman/multi/issues/73) - -# Update 16.1.0 - The Flow State -Added ---- -- `multi.UUID()` generates a uuid7, if chronos is installed that will be used as the initial time seed -- `More control over processors` - - `multi:newProcessor(name, opts, priority)` -- Now accepts a opts table. Old param still works - - | Option | Description | Default | - | --- | --- | --- | - | Attach | If true a hook will be attached to the parent process and will run when Start is called or Start is set | false | - | MaxObjects | Maximum number of objects that a processor can spawn | -1 (disabled) | - | MaxThreads | Maximum number of threads that a processor can spawn | -1 (disabled) | - | Start | If true the processor will start instantlly | false | - | Priority | If true the processor will use the priority handler | false | - | TaskDelay | Sets the task delay in seconds between each tasks execution | 0 | - | TashHandler | If false will disable the task feature of a processor | true | - - `proc:newThread()` returns nil, "errorstring" if it was unable to create a thread - - `proc:new[Object]` now returns {unscheduled}, "errorstring", used to just return the {scheduledObject} and no errorstring - - `proc:getMaxThreads()` -- returns the max thread limit - - `proc:setMaxThreads(n)` -- sets the max thread limit - - `proc:getMaxObjects()` -- gets the max object limit - - `proc:setMaxObjects(n)` -- sets the max object limit - - `proc.Status` -- A table that contains all status errors that occured in a process - Example: - ```lua - local multi, thread = require("multi"):init() - - proc = multi:newProcessor("thread test",{ - Start = true, - MaxThreads = 1, - MaxObjects = 1, - Attach = true, - TaskDelay = .1, - Priority = true, - TaskHandler = false - }) - - print(proc:newThread("testing",function() - while true do - print("STATUS:\n---") - for i,v in ipairs(proc.Status) do - print(i,v) - end - thread.sleep(1) - end - end)) - proc:newThread("testing 2",function() - while true do - print("testing 2...") - thread.sleep(1) - end - end) - proc:newThread("testing 3",function() - while true do - print("testing 3...") - thread.sleep(1) - end - end) - - proc:newLoop(function() - -- - end) - - print(proc:newLoop(function() - -- - end)) - - multi:mainloop() - ``` -- `multi.hasType(typ)` returns true if a type has been registered -- `multi.isMultiObj(obj)` returns true if the object is a multi object -- `multi.forwardConnection(src, dest)` forwards events from one connection to another connection. Doesn't modify anything and both connections are triggered when src is Fired, but not when dest is fired. -- `multi.isTimeout(res)` returns true if the response it gets is a timeout type or a string equal to `multi.TIMEOUT`'s value -- `multi:newTimeout(seconds)` returns a connection that will trigger after a certain amount of time. See example below: -```lua -local multi, thread = require("multi"):init() - -data = multi:newConnection() - --- This alarm takes too long... We will timeout -multi:newAlarm(4):OnRing(function() - data:Fire({Type="request"},"data is tasty") -end) - -multi:newThread(function() - res, data = thread.hold(data + multi:newTimeout(3)) -- combined connections allow this to work - if multi.isTimeout(res) then - print("We timed out!") - else - print("We got the data:", data) - end - os.exit() -end) - -multi:mainloop() -``` -- `connection % function` can now modify the arguments of a connection. See above example modified below -```lua -local multi, thread = require("multi"):init() - -local data = multi:newAlarm(1).OnRing % function() return {Type="request"}, "data is tasty" end - -multi:newThread(function() - res, data = thread.hold(data + multi:newTimeout(3)) - if multi.isTimeout(res) then - print("We timed out!") - else - print("We got the data:", data) - end - os.exit() -end) - -multi:mainloop() -``` - -If the alarm takes longer the the timeout: `We timed out!` If the alarm is shorter: `We got the data: data is tasty` - -Changed ---- -- `multi:newBase(tp,ins,callback)` now accepts a tp param and a callback function. Callback is expected to return true/false, errorstring. If callback is false it will cancel scheduling of an object. If type is set the type of the object will be set. - - **Note:** This is all internal functionality - -# Update 16.0.1 - Bug fix -Fixed ---- -- thread.pushStatus() wasn't properly working when forwarding events from THREAD.pushStatus OnStatus connection. This bug also caused stack overflow errors with the following code -```lua -func = thread:newFunction(function() - for i=1,10 do - thread.sleep(1) - thread.pushStatus(i) - end -end) - -func2 = thread:newFunction(function() - local ref = func() - ref.OnStatus(function(num) - -- do stuff with this data - - thread.pushStatus(num*2) -- Technically this is not ran within a thread. This is ran outside of a thread inside the thread handler. - end) -end) - -local handler = func2() -handler.OnStatus(function(num) - print(num) -end) - -multi:mainloop() -``` - -# Update 16.0.0 - Getting the priorities straight - -## Added New Integration: **priorityManager** - -Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! -- Allows the creation of custom priorityManagers - -Added ---- -- thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a threaded function, when the function returns or errors. -- multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. `delay` can be a function as well and will be processed as if thread.hold was called. -- processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function -- thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. -- shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. - ```lua - package.path = "?/init.lua;?.lua;"..package.path - - multi, thread = require("multi"):init({print=true}) - THREAD, GLOBAL = require("multi.integration.lanesManager"):init() - - stp = multi:newSystemThreadedProcessor(8) - - local shared = stp:newSharedTable("shared") - - shared["test"] = "We work!" - - for i=1,5 do - -- There is a bit of overhead when creating threads on a process. Takes some time, mainly because we are creating a proxy. - stp:newThread(function() - local multi, thread = require("multi"):init() - local shared = _G["shared"] - print(THREAD_NAME, shared.test, shared.test2) - multi:newAlarm(.5):OnRing(function() -- Play around with the time. System threads do not create instantly. They take quite a bit of time to get spawned. - print(THREAD_NAME, shared.test, shared.test2) - end) - end) - end - - shared["test2"] = "We work!!!" - - multi:mainloop() - ``` - - Output: - ``` - INFO: Integrated Lanes Threading! - STJQ_cPXT8GOx We work! nil - STJQ_hmzdYDVr We work! nil - STJQ_3lwMhnfX We work! nil - STJQ_hmzdYDVr We work! nil - STJQ_cPXT8GOx We work! nil - STJQ_cPXT8GOx We work! We work!!! - STJQ_hmzdYDVr We work! We work!!! - STJQ_3lwMhnfX We work! We work!!! - STJQ_hmzdYDVr We work! We work!!! - STJQ_cPXT8GOx We work! We work!!! - ``` - -- multi:chop(obj) -- We cannot directly interact with a local object on lanes, so we chop the object and set some globals on the thread side. Should use like: `mulit:newProxy(multi:chop(multi:newThread(function() ... end)))` -- multi:newProxy(ChoppedObject) -- Creates a proxy object that allows you to interact with an object on a thread - - **Note:** Objects with __index=table do not work with the proxy object! The object must have that function in it's own table for proxy to pick it up and have it work properly. Connections on a proxy allow you to subscribe to an event on the thread side of things. The function that is being connected to happens on the thread! -- multi:newSystemThreadedProcessor(name) -- Works like newProcessor(name) each object created returns a proxy object that you can use to interact with the objects on the system thread - ```lua - package.path = "?/init.lua;?.lua;"..package.path - - multi, thread = require("multi"):init({print=true}) - THREAD, GLOBAL = require("multi.integration.lanesManager"):init() - - stp = multi:newSystemThreadedProcessor("Test STP") - - alarm = stp:newAlarm(3) - - alarm._OnRing:Connect(function(alarm) - print("Hmm...", THREAD_NAME) - end) - ``` - Output: - ``` - Hmm... SystemThreadedJobQueue_A5tp - ``` - Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. - - Proxies can also be shared between threads, just remember to use proxy:getTransferable() before transferring and proxy:init() on the other end. (We need to avoid copying over coroutines) - - The work done with proxies negates the usage of multi:newSystemThreadedConnection(), the only difference is you lose the metatables from connections. - - You cannot connect directly to a proxy connection on the non proxy thread, you can however use proxy_conn:Hold() or thread.hold(proxy_conn) to emulate this, see below. - - ```lua - package.path = "?/init.lua;?.lua;"..package.path - - multi, thread = require("multi"):init({print=true, warn=true, error=true}) - THREAD, GLOBAL = require("multi.integration.lanesManager"):init() - - stp = multi:newSystemThreadedProcessor(8) - - tloop = stp:newTLoop(nil, 1) - - multi:newSystemThread("Testing proxy copy",function(tloop) - local function tprint (tbl, indent) - if not indent then indent = 0 end - for k, v in pairs(tbl) do - formatting = string.rep(" ", indent) .. k .. ": " - if type(v) == "table" then - print(formatting) - tprint(v, indent+1) - else - print(formatting .. tostring(v)) - end - end - end - local multi, thread = require("multi"):init() - tloop = tloop:init() - print("tloop type:",tloop.Type) - print("Testing proxies on other threads") - thread:newThread(function() - while true do - thread.hold(tloop.OnLoop) - print(THREAD_NAME,"Loopy") - end - end) - tloop.OnLoop(function(a) - print(THREAD_NAME, "Got loop...") - end) - multi:mainloop() - end, tloop:getTransferable()).OnError(multi.error) - - print("tloop", tloop.Type) - - thread:newThread(function() - print("Holding...") - thread.hold(tloop.OnLoop) - print("Held on proxied no proxy connection 1") - end).OnError(print) - - thread:newThread(function() - tloop.OnLoop:Hold() - print("held on proxied no proxy connection 2") - end) - - tloop.OnLoop(function() - print("OnLoop",THREAD_NAME) - end) - - thread:newThread(function() - while true do - tloop.OnLoop:Hold() - print("OnLoop",THREAD_NAME) - end - end).OnError(multi.error) - - multi:mainloop() - ``` - Output: - ``` - INFO: Integrated Lanes Threading! 1 - tloop proxy - Holding... - tloop type: proxy - Testing proxies on other threads - OnLoop STJQ_W9SZGB6Y - STJQ_W9SZGB6Y Got loop... - OnLoop MAIN_THREAD - Testing proxy copy Loopy - Held on proxied no proxy connection 1 - held on proxied no proxy connection 2 - OnLoop STJQ_W9SZGB6Y - STJQ_W9SZGB6Y Got loop... - Testing proxy copy Loopy - OnLoop MAIN_THREAD - OnLoop STJQ_W9SZGB6Y - STJQ_W9SZGB6Y Got loop... - - ... (Will repeat every second) - - Testing proxy copy Loopy - OnLoop MAIN_THREAD - OnLoop STJQ_W9SZGB6Y - STJQ_W9SZGB6Y Got loop... - - ... - ``` - - The proxy version can only subscribe to events on the proxy thread, which means that connection metamethods will not work with the proxy version (`_OnRing` on the non proxy thread side), but the (`OnRing`) version will work. Cleverly handling the proxy thread and the non proxy thread will allow powerful connection logic. Also this is not a full system threaded connection. **Proxies should only be used between 2 threads!** To keep things fast I'm using simple queues to transfer data. There is no guarantee that things will work! - - Currently supporting: - - proxyLoop = STP:newLoop(...) - - proxyTLoop = STP:newTLoop(...) - - proxyUpdater = STP:newUpdater(...) - - proxyEvent = STP:newEvent(...) - - proxyAlarm = STP:newAlarm(...) - - proxyStep = STP:newStep(...) - - proxyTStep = STP:newTStep(...) - - proxyThread = STP:newThread(...) - - proxyService = STP:newService(...) - - threadedFunction = STP:newFunction(...) - - Unique: - - STP:newSharedTable(name) - -
- - **STP** functions (The ones above) cannot be called within coroutine based thread when using lanes. This causes thread.hold to break. Objects(proxies) returned by these functions are ok to use in coroutine based threads! - ```lua - package.path = "?/init.lua;?.lua;"..package.path - - multi, thread = require("multi"):init({print=true}) - THREAD, GLOBAL = require("multi.integration.lanesManager"):init() - - stp = multi:newSystemThreadedProcessor() - - alarm = stp:newAlarm(3) - - alarm.OnRing:Connect(function(alarm) - print("Hmm...", THREAD_NAME) - end) - - thread:newThread(function() - print("Holding...") - local a = thread.hold(alarm.OnRing) -- it works :D - print("We work!") - end) - - multi:mainloop() - ``` - -- multi.OnObjectDestroyed(func(obj, process)) now supplies obj, process just like OnObjectCreated -- thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. -- multi.Processors:getHandler() -- returns the thread handler for a process -- multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! -- multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. -- multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. -- multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object -- multi.success(...) -- Sends a success. Green `SUCCESS` mainly used for tests -- multi.warn(...) -- Sends a warning. Yellow `WARNING` -- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR` - - **Note:** If you want to have multi.print, multi.warn and multi.error to work you need to enable them in settings - ```lua - multi, thread = require("multi"):init { - print=true, - warn=true, - error=true -- Errors will throw regardless. Setting to true will - -- cause the library to force hard crash itself! - } - ``` -- THREAD.exposeEnv(name) -- Merges set env into the global namespace of the system thread it was called in. -- THREAD.setENV(table [, name]) -- Set a simple table that will be merged into the global namespace. If a name is supplied the global namespace will not be merged. Call THREAD.exposeEnv(name) to expose that namespace within a thread. - - **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only. - ```lua - THREAD.setENV({ - shared_function = function() - print("I am shared!") - end - }) - ``` - When this function is used it writes to a special variable that is read at thread spawn time. If this function is then ran later it can be used to set a different env and be applied to future spawned threads. -- THREAD.getENV() can be used to manage advanced uses of the setENV() functionality -- Connection objects now support the % function. This supports a function % connection object. What it does is allow you to **mod**ify the incoming arguments of a connection event. - ```lua - local conn1 = multi:newConnection() - local conn2 = function(a,b,c) return a*2, b*2, c*2 end % conn1 - conn2(function(a,b,c) - print("Conn2",a,b,c) - end) - conn1(function(a,b,c) - print("Conn1",a,b,c) - end) - conn1:Fire(1,2,3) - conn2:Fire(1,2,3) - ``` - Output: - ``` - Conn2 2 4 6 - Conn1 1 2 3 - Conn2 1 2 3 - ``` - **Note:** Conn1 does not get modified, however firing conn1 will also fire conn2 and have it's arguments modified. Also firing conn2 directly **does not** modify conn2's arguments! - See it's implementation below: - ```lua - __mod = function(obj1, obj2) - local cn = multi:newConnection() - if type(obj1) == "function" and type(obj2) == "table" then - obj2(function(...) - cn:Fire(obj1(...)) - end) - else - error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") - end - return cn - end - ``` -- The len operator `#` will return the number of connections in the object! - ``` - local conn = multi:newConnection() - conn(function() print("Test 1") end) - conn(function() print("Test 2") end) - conn(function() print("Test 3") end) - conn(function() print("Test 4") end) - print(#conn) - ``` - Output: - ``` - 4 - ``` -- Connection objects can be negated -conn returns self so conn = -conn, reverses the order of connection events - ```lua - local conn = multi:newConnection() - conn(function() print("Test 1") end) - conn(function() print("Test 2") end) - conn(function() print("Test 3") end) - conn(function() print("Test 4") end) - - print("Fire 1") - conn:Fire() - conn = -conn - print("Fire 2") - conn:Fire() - ``` - Output: - ``` - Fire 1 - Test 1 - Test 2 - Test 3 - Test 4 - Fire 2 - Test 4 - Test 3 - Test 2 - Test 1 - ``` -- Connection objects can be divided, function / connection - This is a mix between the behavior between mod and concat, where the original connection can forward it's events to the new one as well as do a check like concat can. View it's implementation below: - ```lua - __div = function(obj1, obj2) -- / - local cn = self:newConnection() - local ref - if type(obj1) == "function" and type(obj2) == "table" then - obj2(function(...) - local args = {obj1(...)} - if args[1] then - cn:Fire(multi.unpack(args)) - end - end) - else - multi.error("Invalid divide! ", type(obj1), type(obj2)," Expected function/connection(table)") - end - return cn - end - ``` -- Connection objects can now be concatenated with functions, not each other. For example: - ```lua - multi, thread = require("multi"):init{print=true,findopt=true} - - local conn1, conn2 = multi:newConnection(), multi:newConnection() - conn3 = conn1 + conn2 - - conn1(function() - print("Hi 1") - end) - - conn2(function() - print("Hi 2") - end) - - conn3(function() - print("Hi 3") - end) - - function test(a,b,c) - print("I run before all and control if execution should continue!") - return a>b - end - - conn4 = test .. conn1 - - conn5 = conn2 .. function() print("I run after it all!") end - - conn4:Fire(3,2,3) - - -- This second one won't trigger the Hi's - conn4:Fire(1,2,3) - - conn5(function() - print("Test 1") - end) - - conn5(function() - print("Test 2") - end) - - conn5(function() - print("Test 3") - end) - - conn5:Fire() - ``` - - Output: - ``` - I run before all and control if things go! - Hi 3 - Hi 1 - Test 1 - Test 2 - Test 3 - I run after it all! - ``` - - **Note:** Concat of connections does modify internal events on both connections depending on the direction func .. conn or conn .. func See implemention below: - ```lua - __concat = function(obj1, obj2) - local cn = multi:newConnection() - local ref - if type(obj1) == "function" and type(obj2) == "table" then - cn(function(...) - if obj1(...) then - obj2:Fire(...) - end - end) - cn.__connectionAdded = function(conn, func) - cn:Unconnect(conn) - obj2:Connect(func) - end - elseif type(obj1) == "table" and type(obj2) == "function" then - ref = cn(function(...) - obj1:Fire(...) - obj2(...) - end) - cn.__connectionAdded = function() - cn.rawadd = true - cn:Unconnect(ref) - ref = cn(function(...) - if obj2(...) then - obj1:Fire(...) - end - end) - end - else - error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") - end - return cn - end - ``` - -Changed ---- -- multi:newTask(task) is not tied to the processor it is created on. -- `multi:getTasks()` renamed to `multi:getRunners()`, should help with confusion between multi:newTask() -- changed how multi adds unpack to the global namespace. Instead we capture that value into multi.unpack. -- multi:newUpdater(skip, func) -- Now accepts func as the second argument. So you don't need to call OnUpdate(func) after creation. -- multi errors now internally call `multi.error` instead of `multi.print` -- Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise. -- Connection:Connect(func, name) Now you can supply a name and name the connection. -- Connection:getConnection(name) This will return the connection function which you can do what you will with it. -- Fast connections are the only connections. Legacy connections have been removed completely. Not much should change on the users end. Perhaps some minor changes. -- conn:Lock(conn) When supplied with a connection reference (What is returned by Connect(func)) it will only lock that connection Reference and not the entire connection. Calling without any arguments will lock the entire connection. -- connUnlock(conn) When supplied with a connection reference it restores that reference and it can be fired again. When no arguments are supplied it unlocks the entire connection. - - **Note:** Lock and Unlock when supplied with arguments and not supplied with arguments operate on different objects. If you unlock an entire connection. Individual connection refs will not unlock. The same applies with locking. The entire connection and references are treated differently. - -- multi.OnObjectCreated is only called when an object is created in a particular process. Proc.OnObjectCreated is needed to detect when an object is created within a process. -- multi.print shows "INFO" before it's message. Blue `INFO` -- Connections internals changed, not too much changed on the surface. -- newConnection(protect, func, kill) - - `protect` disables fastmode, but protects the connection - - `func` uses `..` and appends func to the connection so it calls it after all connections run. There is some internal overhead added when using this, but it isn't much. - - `kill` removes the connection when fired - - **Note:** When using protect/kill connections are triggered in reverse order - -Removed ---- -- multi.CONNECTOR_LINK -- No longer used -- multi:newConnector() -- No longer used -- THREAD.getName() use THREAD_NAME instead -- THREAD.getID() use THREAD_ID instead -- conn:SetHelper(func) -- With the removal of old Connect this function is no longer needed -- connection events can no longer can be chained with connect. Connect only takes a function that you want to connect - -Fixed ---- -- Issue with luajit w/5.2 compat breaking with coroutine.running(), fixed the script to properly handle so thread.isThread() returns as expected! -- Issue with coroutine based threads where they weren't all being scheduled due to a bad for loop. Replaced with a while to ensure all threads are consumed properly. If a thread created a thread that created a thread that may or may not be on the same process, things got messed up due to the original function not being built with these abstractions in mind. -- Issue with thread:newFunction() where a threaded function will keep a record of their returns and pass them to future calls of the function. -- Issue with multi:newTask(func) not properly handling tasks to be removed. Now uses a thread internally to manage things. -- multi.isMainThread was not properly handled in each integration. This has been resolved. -- Issue with pseudo threading env's being messed up. Required removal of getName and getID! -- connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! -- multi:reallocate(processor, index) has been fixed to work with the current changes of the library. -- Issue with lanes not handling errors properly. This is now resolved -- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now! - ```lua - func = thread:newFunction(function() - for i=1,10 do - thread.sleep(1) - thread.pushStatus(i) - end - end) - - func2 = thread:newFunction(function() - local ref = func() - ref.OnStatus(function(num) - -- do stuff with this data - - thread.pushStatus(num*2) -- Technically this is not ran within a thread. This is ran outside of a thread inside the thread handler. - end) - end) - - local handler = func2() - handler.OnStatus(function(num) - print(num) - end) - ``` - -ToDo ---- -- Network Manager, I know I said it will be in this release, but I'm still planning it out. - -# Update 15.3.1 - Bug fix -Fixed ---- -- Issue where multiplying connections triggered events improperly -```lua -local multi, thread = require("multi"):init() -conn1 = multi:newConnection() -conn2 = multi:newConnection(); -- To remove function ambiguity - -(conn1 * conn2)(function() print("Triggered!") end) - -conn1:Fire() -conn2:Fire() - --- Looks like this is triggering a response. It shouldn't. We need to account for this -conn1:Fire() -conn1:Fire() --- Triggering conn1 twice counted as a valid way to trigger the virtual connection (conn1 * conn2) - --- Now in 15.3.1, this works properly and the above doesn't do anything. Internally connections are locked until the conditions are met. -conn2:Fire() -``` - -# Update 15.3.0 - A world of Connections - -Full Update Showcase -```lua -multi, thread = require("multi"):init{print=true} -GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - -local conn = multi:newSystemThreadedConnection("conn"):init() - -multi:newSystemThread("Thread_Test_1",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - conn(function() - print(THREAD:getName().." was triggered!") - end) - multi:mainloop() -end) - -multi:newSystemThread("Thread_Test_2",function() - local multi, thread = require("multi"):init() - local conn = GLOBAL["conn"]:init() - conn(function(a,b,c) - print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(2):OnRing(function() - print("Fire 2!!!") - conn:Fire(4,5,6) - THREAD.kill() - end) - - multi:mainloop() -end) - -conn(function(a,b,c) - print("Mainloop conn got triggered!",a,b,c) -end) - -alarm = multi:newAlarm(1) -alarm:OnRing(function() - 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() - conn(function(a,b,c) - print(THREAD:getName().." was triggered!",a,b,c) - end) - multi:newAlarm(2):OnRing(function() - 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() - multi:newAlarm(2):OnRing(function() - conn2:Fire() - end) - multi:newThread(function() - print("Conn Test!") - thread.hold(conn + conn2) - print("It held!") - end) - multi:mainloop() -end) - -multi:mainloop() -``` - -Added ---- -- `multi:newConnection():Unconnect(conn_link)` Fastmode previously didn't have the ability to be unconnected to. This method works with both fastmode and non fastmode. `fastMode` will be made the default in v16.0.0 (This is a breaking change for those using the Destroy method, use this time to migrate to using `Unconnect()`) -- `thread.chain(...)` allows you to chain `thread.hold(FUNCTIONs)` together - ```lua - while true do - thread.chain(hold_function_1, hold_function_2) - end - ``` - If the first function returns true, it moves on to the next one. if expanded it follows: - ```lua - while true do - thread.hold(hold_function_1) - thread.hold(hold_function_2) - end - ``` -- Experimental option to multi settings: `findopt`. When set to `true` it will print out a message when certain pattern are used with this library. For example if an anonymous function is used in thread.hold() within a loop. The library will trigger a message alerting you that this isn't the most performant way to use thread.hold(). -- `multi:newSystemThreadedConnection()` - - Allows one to trigger connection events across threads. Works like how any connection would work. Supports all of the features, can even be `added` with non SystemThreadedConnections as demonstrated in the full showcase. -- `multi:newConnection():SetHelper(func)` - - Sets the helper function that the connection object uses when creating connection links. - -- `multi.ForEach(table, callback_function)` - - Loops through the table and calls callback_function with each element of the array. - -- If a name is not supplied when creating threads and threaded objects; a name is randomly generated. Unless sending through an established channel/queue you might not be able to easily init the object. - -Changed ---- -- Internally all `OnError` events are now connected to with multi.print, you must pass `print=true` to the init settings when initializing the multi object. `require("multi"):init{print=true}` -- All actors now use fastmode on connections -- Performance enhancement with processes that are pumped. Instead of automatically running, by suppressing the creation of an internal loop object that would manage the process, we bypass that freeing up memory and adding a bit more speed. -- `Connection:fastMode() or Connection:SetHelper()` now returns a reference to itself -- `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. -- `Connections when added(+) together now act like 'or', to get the 'and' feature multiply(*) them together.` - - **Note:** This is a potentially breaking change for using connections. - - ```lua - multi, thread = require("multi"):init{print=true} - -- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() - - local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() - - thread:newThread(function() - print("Awaiting status") - thread.hold(conn1 + (conn2 * conn3)) - print("Conn or Conn2 and Conn3") - end) - - multi:newAlarm(1):OnRing(function() - print("Conn") - conn1:Fire() - end) - - multi:newAlarm(2):OnRing(function() - print("Conn2") - conn2:Fire() - end) - - multi:newAlarm(3):OnRing(function() - print("Conn3") - conn3:Fire() - end) - ``` - -Removed ---- -- Connection objects methods removed: - - holdUT(), HoldUT() -- With the way `thread.hold(conn)` interacts with connections this method was no longer needed. To emulate this use `multi.hold(conn)`. `multi.hold()` is able to emulate what `thread.hold()` outside of a thread, albeit with some drawbacks. - -Fixed ---- -- SystemThreaded Objects variables weren't consistent. -- Issue with connections being multiplied only being able to have a combined fire once - -ToDo ---- - -- Work on network parallelism (I am really excited to start working on this. Not because it will have much use, but because it seems like a cool addition/project to work on. I just need time to actually do work on stuff) - -# Update 15.2.1 - Bug fix -Fixed issue #41 ---- - -# Update 15.2.0 - Upgrade Complete - -Full Update Showcase - -```lua -package.path = "./?/init.lua;"..package.path -multi, thread = require("multi"):init{print=true} -GLOBAL, THREAD = require("multi.integration.threading"):init() - --- Using a system thread, but both system and local threads support this! --- Don't worry if you don't have lanes or love2d. PesudoThreading will kick in to emulate the threading features if you do not have access to system threading. -func = THREAD:newFunction(function(count) - print("Starting Status test: ",count) - local a = 0 - while true do - a = a + 1 - THREAD.sleep(.1) - -- Push the status from the currently running threaded function to the main thread - THREAD.pushStatus(a,count) - if a == count then break end - end - return "Done" -end) - -thread:newThread("test",function() - local ret = func(10) - ret.OnStatus(function(part,whole) - print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - print("TEST",func(5).wait()) - -- The results from the OnReturn connection is passed by thread.hold - print("Status:",thread.hold(ret.OnReturn)) - print("Function Done!") -end).OnError(function(...) - print("Error:",...) -end) - -local ret = func(10) -local ret2 = func(15) -local ret3 = func(20) -local s1,s2,s3 = 0,0,0 -ret.OnError(function(...) - print("Error:",...) -end) -ret2.OnError(function(...) - print("Error:",...) -end) -ret3.OnError(function(...) - print("Error:",...) -end) -ret.OnStatus(function(part,whole) - s1 = math.ceil((part/whole)*1000)/10 - print(s1) -end) -ret2.OnStatus(function(part,whole) - s2 = math.ceil((part/whole)*1000)/10 - print(s2) -end) -ret3.OnStatus(function(part,whole) - s3 = math.ceil((part/whole)*1000)/10 - print(s3) -end) - -loop = multi:newTLoop() - -function loop:testing() - print("testing haha") -end - -loop:Set(1) -t = loop:OnLoop(function() - print("Looping...") -end):testing() - -local proc = multi:newProcessor("Test") -local proc2 = multi:newProcessor("Test2") -local proc3 = proc2:newProcessor("Test3") -proc.Start() -proc2.Start() -proc3.Start() -proc:newThread("TestThread_1",function() - while true do - thread.sleep(1) - end -end) -proc:newThread("TestThread_2",function() - while true do - thread.sleep(1) - end -end) -proc2:newThread("TestThread_3",function() - while true do - thread.sleep(1) - end -end) - -thread:newThread(function() - thread.sleep(1) - local tasks = multi:getStats() - - for i,v in pairs(tasks) do - print("Process: " ..i.. "\n\tTasks:") - for ii,vv in pairs(v.tasks) do - print("\t\t"..vv:getName()) - end - print("\tThreads:") - for ii,vv in pairs(v.threads) do - print("\t\t"..vv:getName()) - end - end - - thread.sleep(10) -- Wait 10 seconds then kill the process! - os.exit() -end) - -multi:mainloop() -``` - -Added: ---- -- `multi:getStats()` - - Returns a structured table where you can access data on processors there tasks and threads: - ```lua - -- Upon calling multi:getStats() the table below is returned - get_Stats_Table { - proc_1 -- table - proc_2 -- table - ... - proc_n -- table - } - proc_Table { - tasks = {alarms,steps,loops,etc} -- All multi objects - threads = {thread_1,thread_2,thread_3,etc} -- Thread objects - } - -- Refer to the objects documentation to see how you can interact with them - ``` - - Reference the Full update showcase for the method in action -- `multi:newProcessor(name, nothread)` - - If no thread is true auto sets the processor as Active, so proc.run() will start without the need for proc.Start() - -- `multi:getProcessors()` - - Returns a list of all processors - -- `multi:getName()` - - Returns the name of a processor - -- `multi:getFullName()` - - Returns the fullname/entire process tree of a process - -- Processors can be attached to processors - -- `multi:getTasks()` - - Returns a list of all non thread based objects (loops, alarms, steps, etc) - -- `multi:getThreads()` - - Returns a list of all threads on a process - -- `multi:newProcessor(name, nothread).run()` - - New function run to the processor object to - -- `multi:newProcessor(name, nothread):newFunction(func, holdme)` - - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor - -- `multi:newTLoop()` member functions - - `TLoop:Set(set)` - Sets the time to wait for the TLoop - -- `multi:newStep()` member functions - - `Step:Count(count)` - Sets the amount a step should count by - -- `multi:newTStep()` member functions - - `TStep:Set(set)` - Sets the time to wait for the TStep - - -Changed: ---- -- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: - ```lua - multi, thread = require("multi"):init() - - func = thread:newFunction(function(count) - local a = 0 - while true do - a = a + 1 - thread.sleep(.1) - thread.pushStatus(a,count) - if a == count then break end - end - return "Done", 1, 2, 3 - end) - - thread:newThread("test",function() - local ret = func(10) - ret.OnStatus(function(part,whole) - print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - print("Status:",thread.hold(ret.OnReturn)) - print("Function Done!") - os.exit() - end).OnError(function(...) - print("Error:",...) - end) - - multi:mainloop() - ``` - Output: - ``` - Ret1: 10% - Ret1: 20% - Ret1: 30% - Ret1: 40% - Ret1: 50% - Ret1: 60% - Ret1: 70% - Ret1: 80% - Ret1: 90% - Ret1: 100% - Status: Done 1 2 3 nil nil nil nil nil nil nil nil nil nil nil nil - Function Done! - ``` - -- Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. -- Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. - -- multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) - | Setting | Description | - ---|--- - print | When set to true parts of the library will print out updates otherwise no internal printing will be done - priority | When set to true, the library will prioritize different objects based on their priority -- `multi:newProcessor(name,nothread)` The new argument allows you to tell the system you won't be using the Start() and Stop() functions, rather you will handle the process yourself. Using the proc.run() function. This function needs to be called to pump the events. - - Processors now also use lManager instead of uManager. -- `multi.hold(n,opt)` now supports an option table like thread.hold does. -- Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update - - ```lua - multi,thread = require("multi"):init() - - loop = multi:newTLoop() - - function loop:testing() - print("testing haha") - end - - loop:Set(1) - t = loop:OnLoop(function() - print("Looping...") - end):testing() - - multi:mainloop() - - --[[Returns as expected: - - testing haha - Looping... - Looping... - Looping... - ... - Looping... - Looping... - Looping... - ]] - ``` - - While chaining on the OnSomeEventMethod() wasn't really a used feature, I still wanted to keep it just incase someone was relying on this working. And it does have it uses - -- All Multi Objects now use Connection objects - - `multiobj:OnSomeEvent(func)` or `multiobj.OnSomeEvent(func)` - -- Connection Objects no longer Fire with syntax sugar when attached to an object: - - `multiobj:OnSomeEvent(...)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! - -- multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) - -Removed: ---- -- `multi:getTasksDetails()` Remade completely and now called `multi:getStats()` -- `multi:getError()` Removed when setting protect was removed -- `multi:FreeMainEvent()` The new changes with connections make's this function unnecessary -- `multi:OnMainConnect(func)` See above -- `multi:connectFinal(func)` See above -- `multi:lightloop()` Cleaned up the mainloop/uManager method, actually faster than lightloop (Which should have been called liteloop) -- `multi:threadloop()` See above for reasons -- `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. -- `multi:GetParentProcess()` use `multi.getCurrentProcess()` instead -- priority scheme 2, 3 and auto-priority have been removed! Only priority scheme 1 actually performed in a reasonable fashion so that one remained. -- `multi:newFunction(func)` - - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did -- `multi.holdFor()` Now that multi.hold takes the option table that thread.hold has this feature can be emulated using that. - -- Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed - -Fixed: ---- - -- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seemed to be related to my filesystem, since remounting the drive caused the issue to stop. (Windows) - -- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not being up to date with threaded functions - -- Issue where gettasksdetails() would try to process a destroyed object causing it to crash - -- Issue with multi.hold() not pumping the mainloop and only the scheduler - -ToDo: ---- - -- Work on network parallelism - - -# Update 15.1.0 - Hold the thread! - -Full Update Showcase - -```lua -local 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("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") - end) - ret2.OnStatus(function(part,whole) - print("Ret2: ",math.ceil((part/whole)*1000)/10 .."%") - end) - ret3.OnStatus(function(part,whole) - print("Ret3: ",math.ceil((part/whole)*1000)/10 .."%") - end) - -- 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) - -test = thread:newFunction(function() - return 1,2,nil,3,4,5,6,7,8,9 -end,true) -print(test()) -multi:newThread("testing",function() - print("#Test = ",test()) - print(thread.hold(function() - print("Hello!") - return false - end,{ - interval = 2, - cycles = 3 - })) -- End result, 3 attempts within 6 seconds. If still false then timeout - print("held") -end).OnError(function(...) - print(...) -end) - -sandbox = multi:newProcessor() -sandbox:newTLoop(function() - print("testing...") -end,1) - -test2 = multi:newTLoop(function() - print("testing2...") -end,1) - -sandbox:newThread("Test Thread",function() - local a = 0 - while true do - thread.sleep(1) - a = a + 1 - print("Thread Test: ".. multi.getCurrentProcess().Name) - if a == 10 then - sandbox.Stop() - end - end -end).OnError(function(...) - print(...) -end) -multi:newThread("Test Thread",function() - while true do - thread.sleep(1) - print("Thread Test: ".. multi.getCurrentProcess().Name) - end -end).OnError(function(...) - print(...) -end) - -sandbox.Start() - -multi:mainloop() -``` - -Added: ---- - -- multi:newSystemThreadedJobQueue(n) - - `queue:isEmpty()` - - Returns true if the queue is empty, false if there are items in the queue. - -**Note:** a queue might be empty, but the job may still be running and not finished yet! Also if a registered function is called directly instead of pushed, it will not reflect inside the queue until the next cycle! - -Example: -```lua -local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available - -jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads -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("Empty:",jq:isEmpty()) - end -end).OnError(function(self,msg) - print(msg) -end) -multi:mainloop() -``` - -## multi.TIMEOUT - -`multi.TIMEOUT` is equal to "TIMEOUT", it is reccomended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. - -## new connections on threaded functions - -- `func.OnStatus(...)` - - Allows you to connect to the status of a function see [thread.pushStatus()](#status-added-to-threaded-functions) - -- `func.OnReturn(...)` - - Allows you to connect to the functions return event and capture its returns see [Example](#status-added-to-threaded-functions) for an example of it in use. - -## multi:newProcessor(name) - -```lua -local 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 -``` - -**Note:** Processor objects have been added and removed many times in the past, but will remain with this update. - -| 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 - -**Note:** All tasks/threads created on a process are linked to that process. If a process is stopped all tasks/threads will be halted until the process is started back up. - -## Connection can now be added together - -Very useful when using thread.hold for multiple connections to trigger. - -Iif you had multiple holds and one finished before others and wasn't consumed it would lock forever! This is now fixed - -`print(conn + conn2 + conn3 + connN)` - -Can be chained as long as you want! See example below - -## Status added to threaded functions -- `thread.pushStatus(...)` - - Allows a developer to push a status from a function. - -- `tFunc.OnStatus(func(...))` - - A connection that can be used on a function to view the status of the threaded function - -Example: - -```lua -local 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) -``` - -Changed: ---- - -- `f = thread:newFunction(func,holdme)` - - Nothing changed that will affect how the object functions by default. The returned function is now a table that is callable and 3 new methods have been added: - - Method | Description - ---|--- - Pause() | Pauses the function, Will cause the function to return `nil, Function is paused` - Resume() | Resumes the function - holdMe(set) | Sets the holdme argument that existed at function creation - - ```lua - local multi, thread = require("multi"):init() - - test = thread:newFunction(function(a,b) - thread.sleep(1) - return a,b - end, true) - - print(test(1,2)) - - test:Pause() - - print(test(1,2)) - - test:Resume() - - print(test(1,2)) - - --[[ -- If you left holdme nil/false - - print(test(1,2).connect(function(...) - print(...) - end)) - - test:Pause() - - print(test(1,2).connect(function(...) - print(...) - end)) - - test:Resume() - - print(test(1,2).connect(function(...) - print(...) - end)) - - ]] - - multi:mainloop() - ``` - - **Output:** - - ``` - 1 2 - nil Function is paused - 1 2 - ``` - - **If holdme is nil/false:** - - ``` - nil Function is paused - - - 1 2 nil... - 1 2 nil... - ``` - -- thread.hold(n,opt) [Ref. Issue](https://github.com/rayaman/multi/issues/24) - - Added option table to thread.hold - | 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 - - `n` can be a number and thread.hold will act like thread.sleep. When `n` is a number the option table will be ignored! - -Removed: ---- - -- N/A - -Fixed: ---- - -- Threaded functions not returning multiple values [Ref. Issue](https://github.com/rayaman/multi/issues/21) -- Priority Lists not containing Very_High and Very_Low from previous update -- All functions that should have chaining now do, reminder all functions that don't return any data return a reference to itself to allow chaining of method calls. - -ToDo ---- - -- Work on network parallelism (I really want to make this, but time and getting it right is proving much more difficult) -- Work on QOL changes to allow cleaner code like [this](#connection-can-now-be-added-together) - -# Update 15.0.0 - The art of faking it -Full Update Showcase ---- -```lua -local multi,thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available - -jq = multi:newSystemThreadedJobQueue(4) -- 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 - -multi:newThread("Standard Thread 1",function() - while true do - thread.sleep(1) - print("Testing 1 ...") - end -end) - -multi:newISOThread("ISO Thread 2",{test=true},function() - while true do - thread.sleep(1) - print("Testing 2 ...") - end -end) - -multi:mainloop() -``` -Note: ---- -This was supposed to be released over a year ago, but work and other things got in my way. Pesudo Threading now works. The goal of this is so you can write modules that can be scaled up to utilize threading features when available. - -Added: ---- -- multi:newISOThread(name,func,env) - - Creates an isolated thread that prevents both locals and globals from being accessed. - - Was designed for the pesudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intened for what I wanted them to do! -- Added new integration: pesudoManager, functions just like lanesManager and loveManager, but it's actually single threaded - - This was implemented because, you may want to build your code around being multi threaded, but some systems/implemetations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatcally are threaded if built correctly. I am planning on adding more threadedOjbects. -- In addition to adding pesudo Threading `multi.integration.threading` can now be used to autodetect which enviroment you are on and use the threading features. - ``` - GLOBAL,THREAD = require("multi.integration.threading"):init() - ``` - If you are using love2d it will use that, if you have lanes avaialble then it will use lanes. Otherwise it will use pesudo threading. This allows module creators to implement scalable features without having to worry about which enviroment they are in. Can now require a consistant module: `require("multi.integration.threading"):init()` - -Changed: ---- -- Documentation to reflect the changes made - -Removed: ---- -- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatiblity with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how enviroments worked which means that you can no longer modify an enviroment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded enviroment into a threadedfunction. This in hindsight wasn't the best pratice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyones code in a massive way. - -Fixed: ---- -- pseudoThreading and threads had an issue where they weren't executing properly -- lanesManager THREAD:get(STRING: name) not returning the value -- Issue where threaded function were not returning multiple values - -Todo: ---- -- Add more details to the documentation - -# Update 14.2.0 - Bloatware Removed -Full Update Showcase ---- -```lua -local multi,thread = require("multi"):init() - --- Testing destroying and fixed connections -c = multi:newConnection() -c1 = c(function() - print("called 1") -end) -c2 = c(function() - print("called 2") -end) -c3 = c(function() - print("called 3") -end) - -print(c1,c2.Type,c3) -c:Fire() -c2:Destroy() -print(c1,c2.Type,c3) -c:Fire() -c1:Destroy() -print(c1,c2.Type,c3) -c:Fire() - --- Destroying alarms and threads -local test = multi:newThread(function() - while true do - thread.sleep(1) - print("Hello!") - end -end) - -test.OnDeath(function() - os.exit() -- This is the last thing called. -end) - -local alarm = multi:newAlarm(4):OnRing(function(a) - print(a.Type) - a:Destroy() - print(a.Type) - test:Destroy() -end) - -multi:lightloop() -``` -Going Forward: ---- -- There is no longer any plans for sterilization! Functions do not play nice on different platforms and there is no simple way to ensure that things work. - -Quality Of Life: ---- -- threaded functions now return only the arguments that are needed, if it has trailing nils, they wont be returned like they used to. - -Added: ---- -- Type: destroyed - - A special state of an object that causes that object to become immutable and callable. The object Type is always "destroyed" it cannot be changed. The object can be indexed to infinity without issue. Every part of the object can be called as if it were a function including the indexed parts. This is done incase you destroy an object and still "use" it somewhere. However, if you are expecting something from the object then you may still encounter an error, though the returned type is an instance of the destroyed object which can be indexed and called like normal. This object can be used in any way and no errors will come about with it. - -Fixed: ---- -- thread.holdFor(n,func) and thread.holdWithin(n,func) now accept a connection object as the func argument -- Issue with threaded functions not handling nil properly from returns. This has been resolved and works as expected. -- Issue with system threaded job queues newFunction() not allowing nil returns! This has be addressed and is no longer an issue. -- Issue with hold like functions not being able to return `false` -- Issue with connections not returning a handle for managing a specific conn object. -- Issue with connections where connection chaining wasn't working properly. This has been addressed. - ```lua - local multi,thread = require("multi"):init() - test = multi:newConnection() - test(function(hmm) - print("hi",hmm.t) - hmm.t = 2 - end)(function(hmm) - print("hi2",hmm.t) - hmm.t = 3 - end)(function(hmm) - print("hi3",hmm.t) - end) - test:Fire({t=1}) - ``` - -Changed: ---- -- Destroying an object converts the object into a 'destroyed' type. -- connections now have type 'connector_link' - ```lua - OnExample = multi:newConnection() -- Type Connector, Im debating if I should change this name to multi:newConnector() and have connections to it have type connection - conn = OnExample(...) - print(conn.Type) -- connector_link - ``` - -Removed: (Cleaning up a lot of old features) ---- -- Removed multi:newProcessor(STRING: file) — Old feature that is not really needed anymore. Create your multi-objs on the multi object or use a thread -- bin dependency from the rockspec -- Example folder and .html variants of the .md files -- multi:newTrigger() — Connections do everything this thing could do and more. -- multi:newHyperThreadedProcess(name)* -- multi:newThreadedProcess(name)* -- multi.nextStep(func)* — The new job System can be used instead to achieve this -- multi.queuefinal(self) — An Old method for a feature long gone from the library -- multi:setLoad(n)* -- multi:setThrestimed(n)* -- multi:setDomainName(name)* -- multi:linkDomain(name)* -- multi:_Pause()* — Use multi:Stop() instead! -- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for chacking. -- multi.executeFunction(name,...)* -- multi:getError()* — Errors are nolonger gotten like that, multi.OnError(func) is the way to go -- multi.startFPSMonitior()* -- multi.doFPS(s)* - -*Many features have become outdated/redundant with new features and additions that have been added to the library - -# Update 14.1.0 - A whole new world of possibilities -Full Update Showcase ---- -Something I plan on doing each version going forward -```lua -local multi, thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -serv = multi:newService(function(self,data) - print("Service Uptime: ",self:GetUpTime(),data.test) -end) -serv.OnError(function(...) - print(...) -end) -serv.OnStarted(function(self,data) - print("Started!",self.Type,data) - data.test = "Testing..." - -- self as reference to the object and data is a reference to the datatable that the service has access to -end) -serv:Start() -serv:SetPriority(multi.Priority_Idle) -t = THREAD:newFunction(function(...) - print("This is a system threaded function!",...) - THREAD.sleep(1) -- This is handled within a system thread! Note: this creates a system thread that runs then ends. - return "We done!" -end) -print(t("hehe",1,2,3,true).connect(function(...) - print("connected:",...) -end)) -- The same features that work with thread:newFunction() are here as well -multi.OnLoad(function() - print("Code Loaded!") -- Connect to the load event -end) -t = os.clock() -co = 0 -multi.OnExit(function(n) - print("Code Exited: ".. os.clock()-t .." Count: ".. co) -- Lets print when things have ended -end) -test = thread:newFunction(function() - thread.sleep(1) -- Internally this throws a yield call which sends to the scheduler to sleep 1 second for this thread! - return 1,math.random(2,100) -end) -multi:newThread(function() - while true do - thread.skip() -- Even though we have a few metamethods "yielding" I used this as an example of things still happening and counting. It connects to the Code Exited event later on. - co = co + 1 - end -end) --- We can get around the yielding across metamethods by using a threadedFunction --- For Example -example = {} -setmetatable(example,{ - __newindex = function(t,k,v) -- Using a threaded function inside of a normal function - print("Inside metamethod",t,k,v) - local a,b = test().wait() -- This function holds the code and "yields" see comment inside the test function! - -- we should see a 1 seconde delay since the function sleeps for a second than returns - print("We did it!",a,b) - rawset(t,k,v) - -- This means by using a threaded function we can get around the yielding across metamethods. - -- This is useful if you aren't using luajit, or if you using lua in an enviroment that is on version 5.1 - -- There is a gotcha however, if using code that was meant to work with another coroutine based scheduler this may not work - end, - __index = thread:newFunction(function(t,k,v) -- Using a threaded function as the metamethod - -- This works by returning a table with a __call metamethod. Will this work? Will lua detect this as a function or a table? - thread.sleep(1) - return "You got a string" - end,true) -- Tell the code to force a wait. We need to do this for metamethods -}) -example["test"] = "We set a variable!" -print(example["test"]) -print(example.hi) --- When not in a threaded enviroment at root level we need to tell the code that we are waiting! Alternitavely after the function argument we can pass true to force a wait -c,d = test().wait() -print(c,d) -a,b = 6,7 -multi:newThread(function() - a,b = test().wait() -- Will modify Global - print("Waited:",a,b) - - --This returns instantly even though the function isn't done! - print("called") - test().connect(function(a,b) - print("Connected:",a,b) - os.exit() - end) - print("Returned") - -- This waits for the returns since we are demanding them -end) -local test = multi:newSystemThreadedJobQueue(4) -- Load up a queue that has 4 running threads -func = test:newFunction("test",function(a) -- register a function on the queue that has an async function feature - test2() -- Call the other registered function on the queue - return a..a -end,true) -func2 = test:newFunction("test2",function(a) - print("ooo") - console = THREAD:getConsole() - console.print("Hello!",true) -end,true) -- When called internally on the job queue the function is a normal sync function and not an async function. -multi:scheduleJob({min = 15, hour = 14},function() - -- This function will be called once everyday at 2:15 - -- Using a combination of the values above you are able to schedule a time -end) -print(func("1")) -print(func("Hello")) -print(func("sigh")) -multi:lightloop() -``` -Going Forward: ---- -- Finish the network manager -- Planning on finishing sterilization for the library. -- Might remove some objects: steps (multi:newStep()), tsteps (multi:newTStep), jobs (Planning to rework this), events (multi:newEvent()), tloop (multi:newTLoop()), triggers (multi:newTrigger()) - -Modified: ---- -- thread.Priority_* has been moved into THREAD since it pertains to system threads. This only applies to lanes threads! -- 2 new priority options have been added. This addition modified how all options except Core behave. Core is still the highest and Idle is still the lowest! - - multi.Priority_Core — Same: 1 - - multi.Priority_Very_High — Added: 4 - - multi.Priority_High — Was: 4 Now: 16 - - multi.Priority_Above_Normal — Was: 16 Now: 64 - - multi.Priority_Normal — Was: 64 Now: 256 - - multi.Priority_Below_Normal — Was: 256 Now: 1024 - - multi.Priority_Low — Was: 1024 Now: 4096 - - multi.Priority_Very_Low — Added: 16384 - - multi.Priority_Idle — Was: 4096 Now: 65536 - -Added: ---- -- multi:lightloop() — Works like multi:mainloop() - - Removes the extra features like priority management, load balancing and what not. Only the bare minimum is processed. If Priority management is something you liked to use the new Service object provides those features. You could alternatively use multi:mainloop() instead to use the non light features. -- serv = multi:newService(func) - - (func(self,datatable)) - - self — Reference to the service - - datatable — internal data space that the service can store data. Is reset/destroyed when the service is stopped. - - serv.Type: service - - serv.OnError(func) — Triggered if service crashes - - serv.OnStopped(func) — Triggered if service was stopped (In the case of a crash OnStopped is not called) - - serv.OnStarted(func) — Triggered when the service starts - - serv.SetScheme(n) — How to manage priorities - - n: 1 — **default** uses sleeping to manage priority (Less control, but much less expensive) - - n: 2 — uses skipping to manage priority (Finer control, but more expensive) - - serv.Stop() — Stops the service, the internal data table is wiped - - serv.Pause() — Pauses the service - - serv.Resume() — Resumes the service - - serv.Start() — Starts the service - - serv.GetUpTime() — Gets the uptime of the service. Time is only counted when the service is running, if the service is paused then it is nolonger being counted. If the service is started then the uptime reset! - - serv.SetPriority(priority) — Sets the priority level for the service - - multi.Priority_Core - - multi.Priority_High - - multi.Priority_Above_Normal - - multi.Priority_Normal **default** - - multi.Priority_Below_Normal - - multi.Priority_Low - - multi.Priority_Idle -- jq = multi:newSystemThreadedJobQueue(n) - - jq:newFunction([name optional],func,holup) — Provides a newFunction like syntax. If name is left blank a unique one is assigned. The second return after the function is the name of the function. -- console = THREAD:getConsole() — Now working on lanes and love2d, allows you to print from multiple threads while keeping the console from writing over each other - - console.print(...) - - console.write(str) -- multi:scheduleJob(time,func) - - time.min — Minute a value of (0-59) - - time.hour — Hour a value of (0-23) - - time.day — Day of month a value of (1-31) - - time.wday — Weekday a value of (0-6) - - time.month — Month a value of (1-12) -- THREAD:newFunction(func,holup) — A system threaded based variant to thread:newFunction(func,holup) works the same way. Though this should only be used for intensive functions! Calling a STfunction has a decent amount of overhead, use wisely. System threaded jobqueue may be a better choice depending on what you are planning on doing. -- multi:loveloop() — Handles the run function for love2d as well as run the multi mainloop. -- multi.OnLoad(func) — A special connection that allows you to connect to the an event that triggers when the multi engine starts! This is slightly different from multi.PreLoad(func) Which connects before any variables have been set up in the multi table, before any settings are cemented into the core. In most cases they will operate exactly the same. This is a feature that was created with module creators in mind. This way they can have code be loaded and managed before the main loop starts. -- multi.OnExit(func) — A special connection that allows you to connect onto the lua state closing event. - -Changed: ---- -- threaded functions no longer auto detect the presence of arguments when within a threaded function. However, you can use the holup method to produce the same effect. If you plan on using a function in different ways then you can use .wait() and .connect() without setting the holup argument -- thread:newFunction(func,holup) — Added an argument holup to always force the threaded funcion to wait. Meaning you don't need to tell it to func().wait() or func().connect() -- multi:newConnection(protect,callback,kill) — Added the kill argument. Makes connections work sort of like a stack. Pop off the connections as they get called. So a one time connection handler. - - I'm not sure callback has been documented in any form. callback gets called each and everytime conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. -- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both enviroments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistant. looping through upvalues and sterlizing them and sending them are very complex and slow. - -Removed: ---- -- multi:newTimeStamper() — schedulejob replaces timestamper - -Fixed: ---- -- Issue where setting the priority of lanes Threads were not working since we were using the data before one could have a chance to set it. This has been resolved! -- Issue where connections object:conn() was firing based on the existance of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing -- Issue where async functions connect wasn't properly triggering when a function returned -- Issue where async functions were not passing arguments properly. -- Issue where async functions were not handling errors properly - - nil, err = func().wait() — When waiting - - func().connect(function(err) end) — When connection -- Love2d had an issue where threads crashing would break the mainloop -- Issue where systemthreadedjobqueues pushJob() was not returning the jobID of the job that was pushed! -- Fixed bugs within the extensions.lua file for love threading -- Modified the thread.* methods to perform better (Tables were being created each time one of these methods were called! Which in turn slowed things down. One table is modified to get things working properly) - - thread.sleep() - - thread.hold() - - thread.holdFor() - - thread.holdWithin() - - thread.skip() - - thread.kill() - - thread.yield() - - -# Update 14.0.0 - Consistency, Additions and Stability - -Added: ---- -- multi.init() — Initlizes the library! Must be called for multiple files to have the same handle. Example below -- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but timesout when a certain amount of time has passed! -- multi.hold(function or number) — It's back and better than ever! Normal multi objs without threading will all be halted where threads will still run. If within a thread continue using thread.hold() and thread.sleep() -- thread.holdWithin(NUMBER; cycles,FUNCTION; condition) — Holds until the condition is met! If the number of cycles passed is equal to cycles, hold will return a timeout error -- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicing the functionality of thread.holdWithin -**Note:** when hold has a timeout the first argument will return nil and the second atgument will be TIMEOUT, if not timed out hold will return the values from the conditions -- thread objects now have hooks that allow you to interact with it in more refined ways! --- tObj.OnDeath(self,status,returns[...]) — This is a connection that passes a reference to the self, the status, whether or not the thread ended or was killed, and the returns of the thread. --- tObj.OnError(self,error) — returns a reference to self and the error as a string --- **Limitations:** only 7 returns are possible! This was done because creating and destroying table objects are slow. (The way the scheduler works this would happen every cycle and thats no good) Instead I capture the return values from coroutine.resume into local variables and only allowed it to collect 7 max. -- thread.run(function) — Can only be used within a thread, creates another thread that can do work, but automatically returns whatever from the run function — Use thread newfunctions for a more powerful version of thread.run() -- thread:newFunction(FUNCTION: func) --- returns a function that gives you the option to wait or connect to the returns of the function. --- func().wait() — waits for the function to return works both within a thread and outside of one --- func().connect() — connects to the function finishing --- func() — If your function does not return anything you dont have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing --- If the created function encounters an error, it will return nil, the error message! -- special variable multi.NIL was added to allow error handling in threaded functions. --- multi.NIL can be used in to force a nil value when using thread.hold() -- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. - - **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function is converted into a threaded function - -- lanes threads can now have their priority set using: sThread.priority = - - thread.Priority_Core - - thread.Priority_High - - thread.Priority_Above_Normal - - thread.Priority_Normal - - thread.Priority_Below_Normal - - thread.Priority_Low - - thread.Priority_Idle -- thread.hold() and multi.hold() now accept connections as an argument. See example below - -```lua -local multi, thread = require("multi"):init() -conn = multi:newConnection() -multi:newThread(function() - thread.hold(conn) - print("Connection Fired!!!") -end) -multi:newAlarm(3):OnRing(function() - conn:Fire() -end) -``` - -thread newFunction -```lua -func=thread:newFunction(function(...) - print("Function running...") - thread.sleep(1) - return {1,2,3},"done" -end) -multi:newThread("Test",function() - func().connect(function(...) - print(...) - end) -end) -----OUTPUT---- -> Function running... -> table: 0x008cf340 done nil nil nil nil nil -``` - -thread newFunction using auto convert -```lua -multi, thread = require("multi").init() -a=5 -multi:newThread("Test",function() - function hmm() -- Auto converted into a threaded function - return "Hello!",2 - end - print(a) - a=10 - print(hmm().wait()) -end) -multi:newAlarm(3):OnRing(function() - print(a) -end) -print(hmm) -multi:mainloop() ------OUTPUT----- -> nil -> 5 -> Hello! 2 nil nil nil nil nil -- The way I manage function returns is by allocating them to predefined locals. Because I pass these values regardless they technically get passed even when they are nil. This choice was made to keep the creation of tables to capture arguments then using unpack to pass them on when processing is done -> 10 -``` - -Fixed: ---- -- Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label. -- An internal mismanagement of the threads scheduler was fixed. Now it should be quicker and free of bugs -- Thread error management is the integrations was not properly implemented. This is now fixed - -Removed: ---- -- multi:newWatcher() — No real use -- multi:newCustomObject() — No real use - -Changed: ---- -- Connections connect function can now chain connections -```lua - - local multi, thread = require("multi").init() - test = multi:newConnection() - test(function(a) - print("test 1",a.Temp) - a.Temp = "No!" - end,function(a) - print("test 2",a.Temp) - a.Temp = "Maybe!" - end,function(a) - print("test 3",a.Temp) - end) - test:Fire({Temp="Yes!"}) -``` -- Ties in to the new function that has been added multi.init() -```lua -local multi, thread = require("multi").init() -- The require multi function still returns the multi object like before -``` -- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistant code as well - -Note: Using init allows you to get access to the thread handle. This was done because thread was modifying the global space as well as multi. I wanted to not modify the global space anymore. -internally most of your code can stay the same, you only need to change how the library is required. I do toy a bit with the global space, buy I use a variable name that is invalid as a variable name. The variable name is $multi. This is used internally to keep some records and maintain a clean space - -Also when using intergrations things now look more consistant. -```lua -local multi, thread = require("multi").init() -local GLOBSL, THREAD = require("multi.integration.lanesManager").init() -- or whichever manager you are using -local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti() -``` -Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome. - -Going forward: ---- -- Finish the rework of the networkManager - It "works", but there are packet losses that I cannot explain. I do not know what is causing this at all. Ill fix when I figure it out! -- If all goes well, the future will contain quality of code features. I'll keep an eye out for bugs - -# Update 13.1.0 - Bug fixes and features added -------------- -Added: ---- -- Connections:Lock() — Prevents a connection object form being fired -- Connections:Unlock() — Removes the restriction imposed by conn:Lock() -- new fucntions added to the thread namespace --- thread.request(THREAD handle,STRING cmd,VARARGS args) — allows you to push thread requests from outside the running thread! Extremely powerful. --- thread.exec(FUNCTION func) — Allows you to push code to run within the thread execution block! -- handle = multi:newThread() — now returns a thread handle to interact with the object outside fo the thread --- handle:Pause() --- handle:Resume() --- handle:Kill() - -Fixed: ---- -- Minor bug with multi:newThread() in how names and functions were managed -- Major bug with the system thread handler. Saw healthy threads as dead ones -- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler opperates. -- newSystemThread()'s returned object now matches both the lanes and love2d in terms of methods that are usable. Error handling of System threads now behave the same across both love and lanes implementations. -- looks like I found a typo, thread.yeild -> thread.yield - -Changed: ---- -- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details -- TID has been added to multi objects. They count up from 0 and no 2 objects will have the same number -- thread.hold() — As part of the memory leaks that I had to fix thread.hold() is slightly different. This change shouldn't impact previous code at all, but thread.hold() can not only return at most 7 arguments! -- You should notice some faster code execution from threads, the changes improve preformance of threads greatly. They are now much faster than before! -- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be seperated from the thread objects! -- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! - - -Tasks Details Table format ---- -``` -{ - ["Tasks"] = { - { - ["TID"] = 0, - ["Type"] = scheduler, - ["Name"] = multi.thread, - ["Priority"] = Core, - ["Uptime"] = 6.752 - ["Link"] = tableRef - }, - ... - } , - - ["Systemthreads"] = { - { - ["Uptime"] = 6.752 - ["Link"] = tableRef - ["Name"] = threadname - ["ThreadID"] = 0 - }, - ... - }, - - ["Threads"] = { - { - ["Uptime"] = 6.752 - ["Link"] = tableRef - ["Name"] = threadname - ["ThreadID"] = 0 - }, - ... - }, - - ["ProcessName"] = multi.root, - ["CyclesPerSecondPerTask"] = 3560300, - ["MemoryUsage"] = 1846, in KB returned as a number - ["ThreadCount"] = 1, - ["SystemLoad"] = 0, as a % 100 is max 0 is min - ["PriorityScheme"] = Round-Robin - ["SystemThreadCount"] = 1 -} -``` -# Update 13.0.0 - Added some documentation, and some new features too check it out! -------------- -**Quick note** on the 13.0.0 update: -This update I went all in finding bugs and improving performance within the library. I added some new features and the new task manager, which I used as a way to debug the library was a great help, so much so thats it is now a permanent feature. It's been about half a year since my last update, but so much work needed to be done. I hope you can find a use in your code to use my library. I am extremely proud of my work; 7 years of development, I learned so much about lua and programming through the creation of this library. It was fun, but there will always be more to add and bugs crawling there way in. I can't wait to see where this library goes in the future! - -Fixed: ---- -- Tons of bugs, I actually went through the entire library and did a full test of everything, I mean everything, while writing the documentation. - -Changed: ---- -- A few things, to make concepts in the library more clear. -- The way functions returned paused status. Before it would return "PAUSED" now it returns nil, true if paused -- Modified the connection object to allow for some more syntaxial suger! -- System threads now trigger an OnError connection that is a member of the object itself. multi.OnError() is no longer triggered for a system thread that crashes! - -Connection Example: -```lua -loop = multi:newTLoop(function(self) - self:OnLoops() -- new way to Fire a connection! Only works when used on a multi object, bin objects, or any object that contains a Type variable -end,1) -loop.OnLoops = multi:newConnection() -loop.OnLoops(function() - print("Looping") -end) -multi:mainloop() -``` - -Function Example: -```lua -func = multi:newFunction(function(self,a,b) - self:Pause() - return 1,2,3 -end) -print(func()) -- returns: 1, 2, 3 -print(func()) -- nil, true -``` - -Removed: ---- -- Ranges and conditions — corutine based threads can emulate what these objects did and much better! -- Due to the creation of hyper threaded processes the following objects are no more! - - ~~multi:newThreadedEvent()~~ - - ~~multi:newThreadedLoop()~~ - - ~~multi:newThreadedTLoop()~~ - - ~~multi:newThreadedStep()~~ - - ~~multi:newThreadedTStep()~~ - - ~~multi:newThreadedAlarm()~~ - - ~~multi:newThreadedUpdater()~~ - - ~~multi:newTBase()~~ — Acted as the base for creating the other objects - -These didn't have much use in their previous form, but with the addition of hyper threaded processes the goals that these objects aimed to solve are now possible using a process - -Fixed: ---- -- There were some bugs in the networkmanager.lua file. Desrtoy -> Destroy some misspellings. -- Massive object management bugs which caused performance to drop like a rock. -- Found a bug with processors not having the Destroy() function implemented properly. -- Found an issue with the rockspec which is due to the networkManager additon. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately -- Insane proformance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) -- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increading the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? -- Fixed an issue with processors not properly destroying objects within them and not being destroyable themselves -- Fixed a bug where pause and resume would duplicate objects! Not good -- Noticed that the switching of lua states, corutine based threading, is slower than multi-objs (Not by much though). -- multi:newSystemThreadedConnection(name,protect) — I did it! It works and I believe all the gotchas are fixed as well. - - Issue one, if a thread died that was connected to that connection all connections would stop since the queue would get clogged! FIXED - - There is one thing, the connection does have some handshakes that need to be done before it functions as normal! - -Added: ---- -- Documentation, the purpose of 13.0.0, orginally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. -- multi:newHyperThreadedProcess(STRING name) — This is a version of the threaded process that gives each object created its own coroutine based thread which means you can use thread.* without affecting other objects created within the hyper threaded processes. Though, creating a self contained single thread is a better idea which when I eventually create the wiki page I'll discuss -- multi:newConnector() — A simple object that allows you to use the new connection Fire syntax without using a multi obj or the standard object format that I follow. -- multi:purge() — Removes all references to objects that are contained withing the processes list of tasks to do. Doing this will stop all objects from functioning. Calling Resume on an object should make it work again. -- multi:getTasksDetails(STRING format) — Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format -- multi:endTask(TID) — Use multi:getTasksDetails("t") to get the tid of a task -- multi:enableLoadDetection() — Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it -- THREAD.getID() — returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! -- multi.print(...) works like normal print, but only prints if the setting print is set to true -- setting: `print` enables multi.print() to work -- STC: IgnoreSelf defaults to false, if true a Fire command will not be sent to the self -- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are genereated for you. -- STC: FireTo(id,...) — Described above. - -```lua -local multi = require("multi") -conn = multi:newConnector() -conn.OnTest = multi:newConnection() -conn.OnTest(function() - print("Yes!") -end) -test = multi:newHyperThreadedProcess("test") -test:newTLoop(function() - print("HI!") - conn:OnTest() -end,1) -test:newLoop(function() - print("HEY!") - thread.sleep(.5) -end) -multi:newAlarm(3):OnRing(function() - test:Sleep(10) -end) -test:Start() -multi:mainloop() -``` -Table format for getTasksDetails(STRING format) -```lua -{ - {TID = 1,Type="",Priority="",Uptime=0} - {TID = 2,Type="",Priority="",Uptime=0} - ... - {TID = n,Type="",Priority="",Uptime=0} - ThreadCount = 0 - threads={ - [Thread_Name]={ - Uptime = 0 - } - } -} -``` -**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance. - -**Going forward:** -- Work on system threaded functions -- work on the node manager -- patch up bugs -- finish documentstion - -# Update 12.2.2 - Time for some more bug fixes! - -Fixed: ---- -- multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes. - -# Update 12.2.1 - Time for some bug fixes! - -Fixed: SystemThreadedJobQueues -- You can now make as many job queues as you want! Just a warning when using a large amount of cores for the queue it takes a second or 2 to set up the jobqueues for data transfer. I am unsure if this is a lanes thing or not, but love2d has no such delay when setting up the jobqueue! -- You now connect to the OnReady in the jobqueue object. No more holding everything else as you wait for a job queue to be ready -- Jobqueues:doToAll now passes the queues multi interface as the first and currently only argument -- No longer need to use jobqueue.OnReady() The code is smarter and will send the pushed jobs automatically when the threads are ready - -Fixed: SystemThreadedConnection -- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that ecist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. - -Removed: multi:newQueuer -- This feature has no real use after corutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. - -Going forward: -- Will I ever finish steralization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. -- I need to make better documentation for this library as well. In its current state, all I have are examples and not a list of what is what. - -Example ---- -```lua -multi = require("multi") -GLOBAL, THREAD = require("multi.integration.lanesManager").init() -jq = multi:newSystemThreadedJobQueue() -jq:registerJob("test",function(a) - return "Hello",a -end) -jq.OnJobCompleted(function(ID,...) - print(ID,...) -end) -for i=1,16 do - jq:pushJob("test",5) -end -multi:mainloop() -``` - -# Update 12.2.0 - The chains of binding -**Added:** -- multi.nextStep(func) -- Method chaining -- Priority 3 has been added! -- ResetPriority() — This will set a flag for a process to be re evaluated for how much of an impact it is having on the performance of the system. -- setting: auto_priority added! — If only lua os.clock was more fine tuned... milliseconds are not enough for this to work -- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lowbound for pirority. The defualt is a hyrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that -- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priotity -- setting: auto_delay added! — sets the time in seconds that the system will recheck for low performing processes and manage them. Will also upgrade a process if it starts to run better. -```lua --- All methods that did not return before now return a copy of itself. Thus allowing chaining. Most if not all mutators returned nil, so chaining can now be done. I will eventually write up a full documentation of everything which will show this. -multi = require("multi") -multi:newStep(1,100):OnStep(function(self,i) - print("Index: "..i) -end):OnEnd(function(self) - print("Step is done!") -end) -multi:mainloop{ - priority = 3 -} -``` -Priority 3 works a bit differently than the other 2. - -P1 follows a forumla that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. -``` -C: 3322269 ~I*7 -H: 2847660 ~I*6 -A: 2373050 ~I*5 -N: 1898440 ~I*4 -B: 1423830 ~I*3 -L: 949220 ~I*2 -I: 474610 ~I -~n=I*PRank -``` -P2 follows a formula that resembles this: ~n=n*4 where n is the idle time, see chart below. The goal of this one was to make core process' higher while keeping idle process' low. -``` -C: 6700821 -H: 1675205 -A: 418801 -N: 104700 -B: 26175 -L: 6543 -I: 1635 -~n=n*4 -``` -P3 Ignores using a basic function and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! -``` -C: 2120906 -H: 2120906 -A: 2120906 -N: 2120906 -B: 2120906 -L: 2120906 -I: 2120506 -``` - -Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. This is nolonger the case in version 16.0.0 multi has evolved ;) - -**Improved:** -- Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. -Note: If you write slow code this library's improvements wont make much of a difference. -- Loops have been optimised as well! Being the most used objects I felt they needed to be made as fast as possible - -I usually give an example of the changes made, but this time I have an explantion for `multi.nextStep()`. It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep adds a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. - -Note: -The upper limit of this libraries performance on my machine is ~39mil. This is simply a while loop counting up from 0 and stops after 1 second. The 20mil that I am currently getting is probably as fast as it can get since its half of the max performance possible, and each layer I have noticed that it doubles complexity. Throughout the years with this library I have seen massive improvements in speed. In the beginning we had only ~2000 steps per second. Fast right? then after some tweaks we went to about 300000 steps per second, then 600000. Some more tweaks brought me to ~1mil steps per second, then to ~4 mil then ~9 mil and now finally ~20 mil... the doubling effect that i have now been seeing means that odds are I have reach the limit. I will aim to add more features and optimize individule objects. If its possible to make the library even faster then I will go for it. - - -# Update 12.1.0 - Threads just can't hold on anymore -Fixed: ---- -- bug causing arguments when spawning a new thread not going through - -Changed: ---- -- thread.hold() now returns the arguments that were pass by the event function -- event objects now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object - -```lua -multi = require("multi") -local a = 0 -multi:newThread("test",function() - print("lets go") - b,c = thread.hold(function() -- This now returns what was managed here - return b,"We did it!" - end) - print(b,c) -end) -multi:newTLoop(function() - a=a+1 - if a == 5 then - b = "Hello" - end -end,1) -multi:mainloop() -``` -**Note:** Only if the first return is non-nil/false will any other returns be passed! So while variable b above is nil the string "We did it!" will not be passed. Also while this seems simple enough to get working, I had to modify a bit on how the scheduler worked to add such a simple feature. Quite a bit is going on behind the scenes which made this a bit tricky to implement, but not hard. Just needed a bit of tinkering. Plus event objects have not been edited since the creation of the EventManager. They have remained mostly the same since 2011 - -Going forward: ---- -Contunue to make small changes as I come about them. This change was inspired when working of the net library. I was addind simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been recieved. While upvalues did work, i felt returning data was cleaner and added this feature. - -# Update: 12.0.0 - Big update (Lots of additions some changes) - -**Note:** ~~After doing some testing, I have noticed that using multi-objects are slightly, quite a bit, faster than using (coroutines)multi:newthread(). Only create a thread if there is no other possibility! System threads are different and will improve performance if you know what you are doing. Using a (coroutine)thread as a loop with a -is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with seperate objects. And finally, reduced function overhead help keeps the threads running better. - -**Note:** The nodeManager is being reworked! This will take some time before it is in a stable state. The old version had some major issues that caused it to perform poorly. - -**Note:** Version names were brought back to reality this update. When transistioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library - -Added: ---- -- `nGLOBAL = require("multi.integration.networkManager").init()` -- `node = multi:newNode(tbl: settings)` -- `master = multi:newMaster(tbl: settings)` -- `multi:nodeManager(port)` -- `thread.isThread()` — for coroutine based threads -- New setting to the main loop, stopOnError which defaults to true. This will cause the objects that crash, when under protect, to be destroyed. So the error does not keep happening. -- multi:threadloop(settings) works just like mainloop, but prioritizes (corutine based) threads. Regular multi-objects will still work. This improves the preformance of (coroutine based) threads greatly. -- multi.OnPreLoad — an event that is triggered right before the mainloop starts - -Changed: -- When a (corutine based)thread errors it does not print anymore! Conect to multi.OnError() to get errors when they happen! -- Connections get yet another update. Connect takes an additional argument now which is the position in the table that the func should be called. Note: Fire calls methods backwards so 1 is the back and the # of connections (the default value) is the beginning of the call table -- The love2d compat layer has now been revamped allowing module creators to connect to events without the user having to add likes of code for those events. Its all done automagically. -- This library is about 8 years old and using 2.0.0 makes it seem young. I changed it to 12.0.0 since it has some huge changes and there were indeed 12 major releases that added some cool things. Going forward I'll use major.minor.bugfix -- multi.OnError() is now required to capture errors that are thrown when in prorected mode. - -Node: ---- -- node:sendTo(name,data) -- node:pushTo(name,data) -- node:peek() -- node:pop() -- node:getConsole() — has only 1 function print which allows you to print to the master. - -Master: ---- -- master:doToAll(func) -- master:register(name,node,func) -- master:execute(name,node,...) -- master:newNetworkThread(tname,func,name,...) -- master:getFreeNode() -- master:getRandomNode() -- master:sendTo(name,data) -- master:pushTo(name,data) -- master:peek() -- master:pop() -- master:OnError(nodename, error) — if a node has an error this is triggered. - -Bugs ---- -- Fixed a small typo I made which caused a hard crash when a (coroutine) thread crashes. This only happened if protect was true. - -Going forward: ---- -- I am really excited to finally get this update out there, but left one important thing out. enabling of enviroments for each master connected to a node. This would allow a node to isolate code from multiple masters so they cannot interact with each other. This will come out in version 12.1.0 But might take a while due to the job hunt that I am currently going through. -- Another feature that I am on the fence about is adding channels. They would work like queues, but are named so you can seperate the data from different channels where only one portion of can see certain data. -- I also might add a feature that allows different system threads to consume from a network queue if they are spaned on the same physical machine. This is possible at the moment, just doesn't have a dedicated object for handling this seamlessly. You can do this yourself though. -- Another feature that I am thinking of adding is crosstalk which is a setting that would allow nodes to talk to other nodes. I did not add it in this release since there are some issues that need to be worked out and its very messy atm. however since nodes are named. I may allow by default pushing data to another node, but not have the global table to sync since this is where the issue lies. -- Improve Performance -- Fix supporting libraries (Bin, and net need tons of work) -- Look for the bugs -- Figure out what I can do to make this library more awesome - - -**Note On Queues:** When it comes to network queues, they only send 1 way. What I mean by that is that if the master sends a message to a node, its own queue will not get populated at all. The reason for this is because syncing between which popped from what network queue would make things really slow and would not perform well at all. This means you have to code a bit differently. Use: master getFreeNode() to get the name of the node under the least amount of load. Then handle the sending of data to each node that way. - -Now there is a little trick you can do. If you combine both networkmanager and systemthreading manager, then you could have a proxy queue for all system threads that can pull from that "node". Now data passing within a lan network, (And wan network if using the node manager, though p2p isn't working as i would like and you would need to open ports and make things work. Remember you can define an port for your node so you can port forward that if you want), is fast enough, but the waiting problem is something to consider. Ask yourseld what you are coding and if network paralisim is worth using. - -**Note:** These examples assume that you have already connected the nodes to the node manager. Also you do not need to use the node manager, but sometimes broadcast does not work as expected and the master doesnot connect to the nodes. Using the node manager offers nice features like: removing nodes from the master when they have disconnected, and automatically telling the master when nodes have been added. A more complete example showing connections regardless of order will be shown in the example folder check it out. New naming scheme too. - -**NodeManager.lua** -```lua -multi = require("multi") -local GLOBAL, THREAD = require("multi.integration.lanesManager").init() -nGLOBAL = require("multi.integration.networkManager").init() -multi:nodeManager(12345) -- Host a node manager on port: 12345 -print("Node Manager Running...") -settings = { - priority = 0, — 1 or 2 - protect = false, -} -multi:mainloop(settings) --- Thats all you need to run the node manager, everything else is done automatically - -``` - -Side note: I had a setting called cross talk that would allow nodes to talk to each other. After some tought I decided to not allow nodes to talk to each other directly! You however can create another master withing the node. (The node will connect to its own master as well). This will give you the ability "Cross talk" with each node. Reimplementing the master features into each node directly was unnecessary. - -**Node.lua** -```lua -multi = require("multi") -local GLOBAL, THREAD = require("multi.integration.lanesManager").init() -nGLOBAL = require("multi.integration.networkManager").init() -master = multi:newNode{ - allowRemoteRegistering = true, -- allows you to register functions from the master on the node, default is false - name = nil, -- default value - noBroadCast = true, -- if using the node manager, set this to true to prevent the node from broadcasting - managerDetails = {"localhost",12345}, -- connects to the node manager if one exists -} -function RemoteTest(a,b,c) -- a function that we will be executing remotely - print("Yes I work!",a,b,c) -end -settings = { - priority = 0, -- 1 or 2 - protect = false, -- if something goes wrong we will crash hard, but the speed gain is good -} -multi:mainloop(settings) -``` - -**Master.lua** -```lua --- Import the libraries -local multi = require("multi") -local GLOBAL, THREAD = require("multi.integration.lanesManager").init() -nGLOBAL = require("multi.integration.networkManager").init() --- Act as a master node -master = multi:newMaster{ - name = "Main", -- the name of the master - noBroadCast = true, -- if using the node manager, set this to true to avoid double connections - managerDetails = {"localhost",12345}, -- the details to connect to the node manager (ip,port) -} --- Send to all the nodes that are connected to the master -master:doToAll(function(node_name) - master:register("TestFunc",node_name,function(msg) - print("It works: "..msg) - end) - multi:newAlarm(2):OnRing(function(alarm) - master:execute("TestFunc",node_name,"Hello!") - alarm:Destroy() - end) - multi:newThread("Checker",function() - while true do - thread.sleep(1) - if nGLOBAL["test"] then - print(nGLOBAL["test"]) - thread.kill() - end - end - end) - nGLOBAL["test2"]={age=22} -end) - --- Starting the multitasker -settings = { - priority = 0, -- 0, 1 or 2 - protect = false, -} -multi:mainloop(settings) -``` - -**Note:** There are many ways to work this. You could send functions/methods to a node like haw systemThreadedJobQueue work. Or you could write the methods you want in advance in each node file and send over the command to run the method with arguments ... and it will return the results. Network threading is different than system threading. Data transfer is really slow compared to system threading. In fact the main usage for this feature in the library is mearly for experments. Right now I honestly do not know what I want to do with this feature and what I am going to add to this feature. The ablitiy to use this frature like a system thread will be possible, but there are some things that differ. - -Changed: ---- -- multi:mainloop(settings) — now takes a table of settings -- multi:uManager(settings) — now takes a table of settings -- connections:holdUT(n) can take a number now. Where they will not continue until it gets triggered **n** times Added 3 updated ago, forgot to list it as a new feature -- The way you require the library has changed a bit! This will change how you start your code, but it isn't a big change. -- These changes have led to significant performance improvements - -Modifying the global stack is not the best way to manage or load in the library. -```lua --- Base Library -multi = require("multi") --- In Lanes -multi = require("multi") -local GLOBAL, THREAD = require("multi.integration.lanesManager").init() --- In Love2d -multi = require("multi") -GLOBAL, THREAD = require("multi.integration.loveManager").init() --- In Luvit -local timer = require("timer") -local thread = require("thread") -multi = require("multi") -require("multi.integration.luvitManager").init(thread,timer) -- Luvit does not cuttently have support for the global table or threads. -``` - -Improvements: ---- -- Updated the ThreadedConsole, now 100x faster! -- Updated the ThreadedConections, .5x faster! -- Both multi:uManager(settings) and multi:mainloop(settings) provide about the same performance! Though uManager is slightly slower due to function overhead, but still really close. -- Revamped pausing mulit-objects they now take up less memory when being used - -Removed: ---- -- require("multi.all") — We are going into a new version of the library so this is nolonger needed -- require("multi.compat.backwards[1,5,0]") — This is really old and is no longer supported going forward -- multi:Do_Order() -- multi:enablePriority() -- multi:enablePriority2() -- multi:protect() -- multi:unProtect() -- multi:protectedMainloop() -- multi:unprotectedMainloop() -- multi:prioritizedMainloop1() -- multi:prioritizedMainloop2() -- Removed Tasks -- multi:oneTime(func,...) — never seen use of this, plus multi-functions can do this by pausing the function after the first use, and is much faster anyway -- multi:reboot() — removed due to having no real use -- multi:hold() — removed due to threads being able to do the same thing and way better too -- multi:waitFor() — the thread variant does something completely different -- multi.resurrect() — removed due to being useless - -The new settings table makes all of these possible and removes a lot of function overhead that was going on before. - -```lua -multi:mainloop{ - priority = 1, -- 1 or 2 - protect = true, -- Should I use pcall to ignore errors? - preLoop = function(self) -- a function that is called before the mainloop does its thing - multi:newTLoop(function() - print("Hello whats up!") - error(":P") - end,1) - multi.OnError(function(obj,err) - print(err) - obj:Destroy() - end) - end, -} -``` -# Update: 1.11.1 - Small Clarification on Love -Love2d change: -I didn't fully understand how the new love.run() function worked. -So, it works by returning a function that allows updating the mainloop. So, this means that we can do something like this: - -```lua -multi:newLoop(love.run()) -- Run the mainloop here, cannot use thread.* when using this object - --- or - -multi:newThread("MainLoop",love.run()) -- allows you to use the thread.* - ---And you'll need to throw this in at the end -multi:mainloop() -``` - -The priority management system should be quite useful with this change. -NOTE: **multiobj:hold() will be removed in the next version!** This is something I feel should be changed, since threads(coroutines) do the job great, and way better than my holding method that I throw together 5 years ago. I doubt this is being used by many anyway. Version 1.11.2 or version 2.0.0 will have this change. The next update may be either, bug fixes if any or network parallelism. - -TODO: Add auto priority adjustments when working with priority and stuff... If the system is under heavy load it will dial some things deemed as less important down and raise the core processes. - -# Update: 1.11.0 -Added: -- SystemThreadedConsole(name) — Allow each thread to print without the sync issues that make prints merge and hard to read. - -```lua --- MainThread: -console = multi:newSystemThreadedConsole("console"):init() --- Thread: -console = THREAD.waitFor("console"):init() - --- using the console -console:print(...) -console:write(...) -- kind of useless for formatting code though. other threads can eaisly mess this up. -``` - -Fixed/Updated: -- Love2d 11.1 support is now here! Will now require these lines in your main.lua file - -```lua -function love.update(dt) - multi:uManager(dt) -- runs the main loop of the multitasking library -end -function love.draw() - multi.dManager() -- If using my guimanager, if not omit this -end -``` - - -# Update: 1.10.0 -**Note:** The library is now considered to be stable! -**Upcoming:** Network parallelism is on the way. It is in the works and should be released soon - -Added: ---- -- isMainThread true/nil -- multi:newSystemThreadedConnection(name,protect) — Works like normal connections, but are able to trigger events across threads - -Example of threaded connections -```lua -local GLOBAL,THREAD=require("multi.integration.lanesManager").init() -multi:newSystemThread("Test_Thread_1",function() - connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - connOut(function(arg) - print(THREAD.getName(),arg) - end) - multi:mainloop() -end) -multi:newSystemThread("Test_Thread_2",function() - connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() - connOut(function(arg) - print(THREAD.getName(),arg) - end) - multi:mainloop() -end) -connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() -a=0 -connOut(function(arg) - print("Main",arg) -end) -multi:newTLoop(function() - a=a+1 - connOut:Fire("Test From Main Thread: "..a.."\n") -end,1) -``` - -Fixed: ---- -**loveManager** and **shared threading objects** -- sThread.waitFor() -- sThread.hold() -- some typos -- SystemThreadedTables (They now work on both lanes and love2d as expected) - -Example of threaded tables -```lua -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -multi:newSystemThread("Test_Thread_1",function() - require("multi") - test = sThread.waitFor("testthing"):init() - multi:newTLoop(function() - print("------") - for i,v in pairs(test.tab) do - print("T1",i,v) - end - end,1) - multi:mainloop() -end) -multi:newSystemThread("Test_Thread_1",function() - require("multi") - test = sThread.waitFor("testthing"):init() - multi:newTLoop(function() - print("------") - for i,v in pairs(test.tab) do - print("T2",i,v) - end - end,1) - multi:mainloop() -end) -test = multi:newSystemThreadedTable("testthing"):init() -multi:newTLoop(function() - local a,b = multi.randomString(8),multi.randomString(4) - print(">",a,b) - test[a]=b -end,1) -multi:mainloop() -``` - -# Update: 1.9.2 -Added: ---- -- (THREAD).kill() kills a thread. Note: THREAD is based on what you name it -- newTimeStamper() Part of the persistent systems... Useful for when you are running this library for a long amount of time... like months and years! Though daily, hourly, minute events do also exist. -Allows one to hook to timed events such as whenever the clock strikes midnight or when the day turns to Monday. The event is only done once though. so as soon as Monday is set it would trigger then not trigger again until next Monday -works for seconds, minutes, days, months, year. -```lua -stamper = multi:newTimeStamper() -stamper:OnTime(int hour,int minute,int second,func) or stamper:OnTime(string time,func) time as 00:00:00 -stamper:OnHour(int hour,func) -stamper:OnMinute(int minute,func) -stamper:OnSecond(int second,func) -stamper:OnDay(int day,func) or stamper:OnDay(string day,func) Mon, Tues, Wed, etc... -stamper:OnMonth(int month,func) -stamper:OnYear(int year,func) -``` -Improved: ---- -- LoadBalancing, well better load balancing than existed before. This one allowed for multiple processes to have their own load reading. Calling this on the multi object will return the total load for the entire multi environment... loads of other processes are indeed affected by what other processes are doing. However, if you combine propriety to the mix of things then you will get differing results... these results however will most likely be higher than normal... different priorities will have different default thresholds of performance. - -Fixed: ---- -- Thread.getName() should now work on lanes and love2d, haven't tested it much with the luvit side of things... -- A bug with the lovemanager table.remove arguments were backwards -- The queue object in the love2d threading has been fixed! It now supports sending all objects (even functions if no upvalues are present!) - -Changed: ---- -- SystemThreadedJobQueues now have built in load management so they are not constantly at 100% CPU usage. -- SystemThreadedJobQueues pushJob now returns an id of that job which will match the same one that OnJobCompleted returns - - -# Update: 1.9.1 - Threads can now argue -Added: ---- -- Integration "multi.integration.luvitManager" -- Limited... Only the basic multi:newSystemThread(...) will work -- Not even data passing will work other than arguments... If using the bin library, you can pass tables and function... Even full objects if inner recursion is not present. - -Updated: ---- -- multi:newSystemThread(name,func,...) -- It will now pass the ... to the func(). Do not know why this wasn't done in the first place -- Also multi:getPlatform(will now return "luvit" if using luvit... Though Idk if module creators would use the multi library when inside the luvit environment - -# Update: 1.9.0 -Added: ---- -- multiobj:ToString() — returns a string representing the object -- multi:newFromString(str) — creates an object from a string - -Works on threads and regular objects. Requires the latest bin library to work! -```lua -talarm=multi:newThreadedAlarm("AlarmTest",5) -talarm:OnRing(function() - print("Ring!") -end) -bin.new(talarm:ToString()):tofile("test.dat") --- multi:newFromString(bin.load("test.dat")) -``` -A more seamless way to use this will be made in the form of state saving. -This is still a WIP -processes, timers, timemasters, watchers, and queuers have not been worked on yet - -# Update: 1.8.7 -Added: ---- -- multi.timer(func,...) - -```lua -function test(a,b,c) - print("Running...") - a=0 - for i=1,1000000000 do - a=a+1 - end - return a,b+c -end -print(multi.timer(test,1,2,3)) -print(multi.timer(test,1,2,3)) --- multi.timer returns the time taken then the arguments from the function... Uses unpack so careful of nil values! -``` - -# Update: 1.8.6 -Added: -- jobQueue:doToAll(function) -- jobQueue:start() is now required Call this after all calls to registerJob()'s. Calling it afterwards will not guarantee your next push job with that job will work. Not calling this will make pushing jobs impossible! -- Fixed a bug with love2d Threaded Queue -- Fixed some bugs -- Old versions of this library! It stems back from 2012 see rambling for more info... - -This will run said function in every thread. -```lua --- Going to use love2d code this time, almost the same as last time... See ramblings -require("core.Library") -GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library -require("core.GuiManager") -gui.ff.Color=Color.Black -jQueue=multi:newSystemThreadedJobQueue() -jQueue:registerJob("TEST_JOB",function(a,s) - math.randomseed(s) - TEST_JOB2() - return math.random(0,255) -end) -jQueue:registerJob("TEST_JOB2",function() - print("Test Works!") -end) --- 1.8.6 EXAMPLE Change -jQueue:start() -- This is now needed! --- -jQueue:doToAll(function() - print("Doing this 2? times!") -end) -tableOfOrder={} -jQueue.OnJobCompleted(function(JOBID,n) - tableOfOrder[JOBID]=n - if #tableOfOrder==10 then - t.text="We got all of the pieces!" - end -end) -for i=1,10 do -- Job Name of registered function, ... varargs - jQueue:pushJob("TEST_JOB","This is a test!",math.random(1,1000000)) -end -t=gui:newTextLabel("no done yet!",0,0,300,100) -t:centerX() -t:centerY() -``` - -# Update: 1.8.5 -Added: ---- -- SystemThreadedExecute(cmd) - -Allows the execution of system calls without hold up. It is possible to do the same using io.popen()! You decide which works best for you! -```lua -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -cmd=multi:newSystemThreadedExecute("SystemThreadedExecuteTest.lua") -- This file is important! -cmd.OnCMDFinished(function(code) -- callback function to grab the exit code... Called when the command goes through - print("Got Code: "..code) -end) -multi:newTLoop(function() - print("...") -- lets show that we aren't being held up -end,1) -multi:mainloop() -``` - -# Update: 1.8.4 -Added: ---- -- multi:newSystemThreadedJobQueue() -- Improved stability of the library -- Fixed a bug that made the benchmark and getload commands non-thread(coroutine) safe -- Tweaked the loveManager to help improve idle CPU usage -- Minor tweaks to the coroutine scheduling - -Using multi:newSystemThreadedJobQueue() ---- -First you need to create the object -This works the same way as love2d as it does with lanes... It is getting harder to make both work the same way with speed in mind... Anyway... -```lua --- Creating the object using lanes manager to show case this. Examples has the file for love2d -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -jQueue=multi:newSystemThreadedJobQueue(n) -- this internally creates System threads. By default it will use the # of processors on your system You can set this number though. --- Only create 1 jobqueue! For now, making more than 1 is not supported. You only really need one though. Just register new functions if you want 1 queue to do more. The one reason though is keeping track of jobIDs. I have an idea that I will roll out in the ~~next update~~ eventually. -jQueue:registerJob("TEST_JOB",function(a,s) - math.randomseed(s) - -- We will push a random # - TEST_JOB2() -- You can call other registered functions as well! - return math.random(0,255) -- send the result to the main thread -end) -jQueue:registerJob("TEST_JOB2",function() - print("Test Works!") -- this is called from the job since it is registered on the same queue -end) -tableOfOrder={} -- This is how we will keep order of our completed jobs. There is no guarantee that the order will be correct -jQueue.OnJobCompleted(function(JOBID,n) -- whenever a job is completed you hook to the event that is called. This passes the JOBID filled by the returns of the job - -- JOBID is the completed job, starts at 1 and counts up by 1. - -- Threads finish at different times so jobIDs may be passed out of order! Be sure to have a way to order them - tableOfOrder[JOBID]=n -- we order ours by putting them into a table - if #tableOfOrder==10 then - print("We got all of the pieces!") - end -end) --- Lets push the jobs now -for i=1,10 do -- Job Name of registered function, ... varargs - jQueue:pushJob("TEST_JOB","This is a test!",math.random(1,1000000)) -end -print("I pushed all of the jobs :)") -multi:mainloop() -- Start the main loop :D -``` - -That’s it from this version! - -# Update: 1.8.3 - Mainloop recieves some needed overhauling -Added: ---- -**New Mainloop functions** Below you can see the slight differences... Function overhead is not too bad in lua but has a real difference. multi:mainloop() and multi:unprotectedMainloop() use the same algorithm yet the dedicated unprotected one is slightly faster due to having less function overhead. -- multi:mainloop()\* — Bench: 16830003 Steps in 3 second(s)! -- multi:protectedMainloop() — Bench: 16699308 Steps in 3 second(s)! -- multi:unprotectedMainloop() — Bench: 16976627 Steps in 3 second(s)! -- multi:prioritizedMainloop1() — Bench: 15007133 Steps in 3 second(s)! -- multi:prioritizedMainloop2() — Bench: 15526248 Steps in 3 second(s)! - -\* The OG mainloop function remains the same and old methods to achieve what we have with the new ones still exist - -These new methods help by removing function overhead that is caused through the original mainloop function. The one downside is that you no longer have the flexibility to change the processing during runtime. - -However there is a work around! You can use processes to run multiobjs as well and use the other methods on them. - -I may make a full comparison between each method and which is faster, but for now trust that the dedicated ones with less function overhead are infect faster. Not by much but still faster. - -# Update: 1.8.2 -Added: ---- -- multi:newsystemThreadedTable(name) NOTE: Metatables are not supported in transfers. However there is a work around obj:init() does this. Look in the multi/integration/shared/shared.lua files to see how I did it! -- Modified the GLOBAL metatable to sync before doing its tests -- multi._VERSION was multi.Version, felt it would be more consistent this way... I left the old way of getting the version just in case someone has used that way. It will eventually be gone. Also multi:getVersion() will do the job just as well and keep your code nice and update related bug free! -- Also everything that is included in the: multi/integration/shared/shared.lua (Which is loaded automatically) works in both lanes and love2d environments! - -The threaded table is setup just like the threaded queue.
-It provids GLOBAL like features without having to write to GLOBAL!
-This is useful for module creators who want to keep their data private, but also use GLOBAL like coding.
-It has a few features that makes it a bit better than plain ol GLOBAL (For now...) -(ThreadedTable - TT for short) This was modified by a recent version that removed the need for a sync command -- TT:waitFor(name) -- TT:sync() -- TT["var"]=value -- print(TT["var"]) - -we also have the "sync" method, this one was made for love2d because we do a syncing trick to get data in a table format. The lanes side has a sync method as well so no worries. Using indexing calls sync once and may grab your variable. This allows you to have the lanes indexing 'like' syntax when doing regular indexing in love2d side of the module. As of right now both sides work flawlessly! And this effect is now the GLOBAL as well
- -On GLOBALS sync is a internal method for keeping the GLOBAL table in order. You can still use sThread.waitFor(name) to wait for variables that may or may not yet exist! - -Time for some examples: -Using multi:newSystemThreadedTable(name) ---- -```lua --- lanes Desktop lua! NOTE: this is in lanesintergratetest6.lua in the examples folder -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -test=multi:newSystemThreadedTable("YO"):init() -test["test1"]="lol" -multi:newSystemThread("test",function() - tab=sThread.waitFor("YO"):init() - print(tab:has("test1")) - sThread.sleep(3) - tab["test2"]="Whats so funny?" -end) -multi:newThread("test2",function() - print(test:waitFor("test2")) -end) -multi:mainloop() -``` - -```lua --- love2d lua! NOTE: this is in main4.lua in the love2d examples -require("core.Library") -GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library -require("core.GuiManager") -gui.ff.Color=Color.Black -test=multi:newSystemThreadedTable("YO"):init() -test["test1"]="lol" -multi:newSystemThread("test",function() - tab=sThread.waitFor("YO"):init() - print(tab["test1"]) - sThread.sleep(3) - tab["test2"]="Whats so funny?" -end) -multi:newThread("test2",function() - print(test:waitFor("test2")) - t.text="DONE!" -end) -t=gui:newTextLabel("not done yet!",0,0,300,100) -t:centerX() -t:centerY() -``` - -# Update: 1.8.1 -No real change!
-Changed the structure of the library. Combined the coroutine based threads into the core!
-Only compat and integrations are not part of the core and never will be by nature.
-This should make the library more convent to use.
-I left multi/all.lua file so if anyone had libraries/projects that used that it will still work!
-Updated from 1.7.6 to 1.8.0
(How much thread could a thread htread if a thread could thread thread?) -Added:
-- multi:newSystemThreadedQueue() -- multi:systemThreadedBenchmark() -- More example files -- multi:canSystemThread() — true if an integration was added false otherwise (For module creation) -- Fixed a few bugs in the loveManager - -Using multi:systemThreadedBenchmark() ---- -```lua -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -multi:systemThreadedBenchmark(3):OnBench(function(self,count) - print("First Bench: "..count) - multi:systemThreadedBenchmark(3,"All Threads: ") -end) -multi:mainloop() -``` - -Using multi:newSystemThreadedQueue() ---- -Quick Note: queues shared across multiple objects will be pulling from the same "queue" keep this in mind when coding! ~~Also the queue respects direction a push on the thread side cannot be popped on the thread side... Same goes for the mainthread!
~~ Turns out I was wrong about this... -```lua --- in love2d, this file will be in the same example folder as before, but is named main2.lua -require("core.Library") -GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library ---IMPORTANT --- Do not make the above local, this is the one difference that the lanesManager does not have --- If these are local the functions will have the upvalues put into them that do not exist on the threaded side --- You will need to ensure that the function does not refer to any upvalues in its code. It will print an error if it does though --- Also, each thread has a .1 second delay! This is used to generate a random value for each thread! -require("core.GuiManager") -gui.ff.Color=Color.Black -queue=multi:newSystemThreadedQueue("QUEUE"):init() -queue:push("This is a test") -queue:push("This is a test2") -queue:push("This is a test3") -queue:push("This is a test4") -multi:newSystemThread("test2",function() - queue=sThread.waitFor("QUEUE"):init() - data=queue:pop() - while data do - print(data) - data=queue:pop() - end - queue:push("DONE!") -end) -multi:newThread("test!",function() - thread.hold(function() return queue:pop() end) - t.text="Done!" -end) -t=gui:newTextLabel("no done yet!",0,0,300,100) -t:centerX() -t:centerY() -``` -In Lanes ---- -```lua --- The code is compatible with each other, I just wanted to show different things you can do in both examples --- This file can be found in the examples folder as lanesintegrationtest4.lua -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -queue=multi:newSystemThreadedQueue("QUEUE"):init() -queue:push("This is a test") -queue:push("This is a test2") -queue:push("This is a test3") -queue:push("This is a test4") -multi:newSystemThread("test2",function() - queue=sThread.waitFor("QUEUE"):init() - data=queue:pop() - while data do - print(data) - data=queue:pop() - end - queue:push("This is a test5") - queue:push("This is a test6") - queue:push("This is a test7") - queue:push("This is a test8") -end) -multi:newThread("test!",function() -- this is a lua thread - thread.sleep(.1) - data=queue:pop() - while data do - print(data) - data=queue:pop() - end -end) -multi:mainloop() -``` - -# Update: 1.7.6 -Fixed: ---- -Typos like always -Added: ---- -- multi:getPlatform() — returns "love2d" if using the love2d platform or returns "lanes" if using lanes for threading -- examples files -- In Events added method setTask(func) --The old way still works and is more convent to be honest, but I felt a method to do this was needed for completeness. - -Improved: ---- -- Some example files to reflect changes to the core. Changes allow for less typing
-loveManager to require the compat if used so you don't need 2 require line to retrieve the library
- -# Update: 1.7.5 -Fixed some typos in the readme... (I am sure there are more there are always more)
-Added more features for module support
-TODO:
-Work on performance of the library... I see 3 places where I can make this thing run quicker
- -I'll show case some old versions of the multitasking library eventually so you can see its changes in days past!
- -# Update: 1.7.4 -Added: the example folder which will be populated with more examples in the near future!
-The loveManager integration that mimics the lanesManager integration almost exactly to keep coding in both environments as close to possible. This is done mostly for library creation support!
-An example of the loveManager in action using almost the same code as the lanesintergreationtest2.lua
-NOTE: This code has only been tested to work on love2d version 1.10.2 though it should work version 0.9.0 -```lua -require("core.Library") -- Didn't add this to a repo yet! Will do eventually... Allows for injections and other cool things -require("multi.compat.love2d") -- allows for multitasking and binds my libraries to the love2d engine that i am using -GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager ---IMPORTANT --- Do not make the above local, this is the one difference that the lanesManager does not have --- If these are local the functions will have the upvalues put into them that do not exist on the threaded side --- You will need to ensure that the function does not refer to any upvalues in its code. It will print an error if it does though --- Also each thread has a .1 second delay! This is used to generate a random values for each thread! -require("core.GuiManager") -- allows the use of graphics in the program. -gui.ff.Color=Color.Black -function comma_value(amount) - local formatted = amount - while true do - formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') - if (k==0) then - break - end - end - return formatted -end -multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded enviroment as well as a call to multi:mainloop() - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 1"):OnBench(function(self,c) GLOBAL["T1"]=c multi:Stop() end) -end) -multi:newSystemThread("test2",function() -- spawns a thread in another lua process - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 2"):OnBench(function(self,c) GLOBAL["T2"]=c multi:Stop() end) -end) -multi:newSystemThread("test3",function() -- spawns a thread in another lua process - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 3"):OnBench(function(self,c) GLOBAL["T3"]=c multi:Stop() end) -end) -multi:newSystemThread("test4",function() -- spawns a thread in another lua process - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 4"):OnBench(function(self,c) GLOBAL["T4"]=c multi:Stop() end) -end) -multi:newSystemThread("test5",function() -- spawns a thread in another lua process - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 5"):OnBench(function(self,c) GLOBAL["T5"]=c multi:Stop() end) -end) -multi:newSystemThread("test6",function() -- spawns a thread in another lua process - multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 6"):OnBench(function(self,c) GLOBAL["T6"]=c multi:Stop() end) -end) -multi:newSystemThread("Combiner",function() -- spawns a thread in another lua process - function comma_value(amount) - local formatted = amount - while true do - formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') - if (k==0) then - break - end - end - return formatted - end - local b=comma_value(tostring(sThread.waitFor("T1")+sThread.waitFor("T2")+sThread.waitFor("T3")+sThread.waitFor("T4")+sThread.waitFor("T5")+sThread.waitFor("T6"))) - GLOBAL["DONE"]=b -end) -multi:newThread("test0",function() - -- sThread.waitFor("DONE") -- lets hold the main thread completely so we don't eat up cpu - -- os.exit() - -- when the main thread is holding there is a chance that error handling on the system threads may not work! - -- instead we can do this - while true do - thread.skip(1) -- allow error handling to take place... Otherwise let’s keep the main thread running on the low - -- Before we held just because we could... But this is a game and we need to have logic continue - --sThreadM.sleep(.001) -- Sleeping for .001 is a great way to keep cpu usage down. Make sure if you aren't doing work to rest. Abuse the hell out of GLOBAL if you need to :P - if GLOBAL["DONE"] then - t.text="Bench: "..GLOBAL["DONE"] - end - end -end) -GLOBAL["Bench"]=3 -t=gui:newTextLabel("no done yet!",0,0,300,100) -t:centerX() -t:centerY() -``` - -# Update: 1.7.3 -Changed how requiring the library works! -`require("multi.all")` Will still work as expected; however, with the exception of threading, compat, and integrations everything else has been moved into the core of the library. -```lua --- This means that these are no longer required and will cause an error if done so -require("multi.loop") -require("multi.alarm") -require("multi.updater") -require("multi.tloop") -require("multi.watcher") -require("multi.tstep") -require("multi.step") -require("multi.task") --- ^ they are all part of the core now -``` - -# Update: 1.7.2 -Moved updaters, loops, and alarms into the init.lua file. I consider them core features and they are referenced in the init.lua file so they need to exist there. Threaded versions are still separate though. Added another example file - -# Update: 1.7.1 - Bug Fixes Only -¯\\_(ツ)_/¯ ---- -# Update: 1.7.0 - Threading the systems -Modified: multi.integration.lanesManager.lua -It is now in a stable and simple state works with the latest lanes version! Tested with version 3.11 I cannot promise that everything will work with earlier versions. Future versions are good though.
-Example Usage:
-sThread is a handle to a global interface for system threads to interact with themselves
-thread is the interface for multithreads as seen in the threading section
- -GLOBAL a table that can be used throughout each and every thread - -sThreads have a few methods
-sThread.set(name,val) — you can use the GLOBAL table instead modifies the same table anyway
-sThread.get(name) — you can use the GLOBAL table instead modifies the same table anyway
-sThread.waitFor(name) — waits until a value exists, if it does it returns it
-sThread.getCores() — returns the number of cores on your cpu
-sThread.sleep(n) — sleeps for a bit stopping the entire thread from running
-sThread.hold(n) — sleeps until a condition is met
-```lua -local GLOBAL,sThread=require("multi.integration.lanesManager").init() -require("multi.all") -multi:newAlarm(2):OnRing(function(self) - GLOBAL["NumOfCores"]=sThread.getCores() -end) -multi:newAlarm(7):OnRing(function(self) - GLOBAL["AnotherTest"]=true -end) -multi:newAlarm(13):OnRing(function(self) - GLOBAL["FinalTest"]=true -end) -multi:newSystemThread("test",function() -- spawns a thread in another lua process - require("multi.all") -- now you can do all of your coding with the multi library! You could even spawn more threads from here with the integration. You would need to require the interaction again though - print("Waiting for variable: NumOfCores") - print("Got it: ",sThread.waitFor("NumOfCores")) - sThread.hold(function() - return GLOBAL["AnotherTest"] -- note this would hold the entire systemthread. Spawn a coroutine thread using multi:newThread() or multi:newThreaded... - end) - print("Holding works!") - multi:newThread("tests",function() - thread.hold(function() - return GLOBAL["FinalTest"] -- note this will not hold the entire systemthread. As seen with the TLoop constantly going! - end) - print("Final test works!") - os.exit() - end) - local a=0 - multi:newTLoop(function() - a=a+1 - print(a) - end,.5) - multi:mainloop() -end) -multi:mainloop() -``` - -# Update: 1.6.0 -Changed: ---- -- steps -- loops -```lua --- Was -step:OnStep(function(pos,self) -- same goes for tsteps as well - print(pos) -end) -multi:newLoop(function(dt,self) - print(dt) -end) --- Is now -step:OnStep(function(self,pos) -- same goes for tsteps as wellc - print(pos) -end) -multi:newLoop(function(self,dt) - print(dt) -end) -``` -Reasoning I wanted to keep objects consistent, but a lot of my older libraries use the old way of doing things. Therefore, I added a backwards module - -**Note from the future:** That module has been canned. To be honest most features this low in the changelog are outdated and probably do not work. -```lua -require("multi.all") -require("multi.compat.backwards[1,5,0]") -- allows for the use of features that were scrapped/changed in 1.6.0+ -``` -# Update: 1.5.0 -Added: ---- -- An easy way to manage timeouts -- Small bug fixes - -# Update: 1.4.1 (4/10/2017) - First Public release of the library -Added: -- SystemThreading very infancy - -Change: ---- -- small change to the hold method to make it a bit more lightweight - - Using a timer instead of an alarm object! -- Limits to hold: - - cannot hold more than 1 object at a time, and doing so could cause a deadlock! - -**Note:** Wow you looked back this far. Nice, while your at it take a look at the old versions to view the code how it was before my first initial release - -Upcomming: ---- -- Threaded objects wrapped in coroutines, so you can hold/sleep without problems! - -# Update: 1.4.0 (3/20/2017) -Added: ---- -- multiobj:reallocate(ProcessObj) — changes the parent process of an object -- ProcessObj:getController() — returns the mThread so you can opperate on it like a multiobj -Example 1 ---- -```lua -require("multimanager") -- require the library -int1=multi:newProcess() -- create a process -int1.NAME="int1" -- give it a name for example purposes -int2=multi:newProcess() -- create another process to reallocate -int2.NAME="int2" -- name this a different name -step=int1:newTStep(1,10) -- create a TStep so we can slowly see what is going on -step:OnStep(function(p,s) -- connect to the onstep event - print(p,s.Parent.NAME) -- print the position and process name -end) -step:OnEnd(function(s) -- when the step ends lets reallocate it to the other process - if s.Parent.NAME=="int1" then -- lets only do this if it is in the int1 process - s:reallocate(int2) -- send it to int2 - s:Reset() -- reset the object - else - print("We are done!") - os.exit() -- end the program when int2 did its thing - end -end) -int1:Start() -- start process 1 -int2:Start() -- start process 2 -multi:mainloop() -- start the main loop -``` -Fixed/Updated: ---- -- queuer=multi:newQueuer([string: file]) -- Alarms now preform as they should on a queuer -Example 2 ---- -```lua -int=multi:newQueuer() -step=int:newTStep(1,10,1,.5) -alarm=int:newAlarm(2) -step2=int:newTStep(1,5,1,.5) -step:OnStep(function(p,s) - print(p) -end) -step2:OnStep(function(p,s) - print(p,"!") -end) -alarm:OnRing(function(a) - print("Ring1!!!") -end) -int:OnQueueCompleted(function(s) - s:Pause() - print("Done!") - os.exit() -end) -int:Start() -multi:mainloop() -``` - -# Update: 1.3.0 (1/29/2017) -Added: ---- -- Load detection! - - multi.threshold — minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 - - multi.threstimed — amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 - - multi:setThreshold(n) — method used to set multi.threshold - - multi:setThrestimed(n) — method used to set multi.threstimed - - multi:getLoad() — returns a number between 0 and 100 - -# Update: 1.2.0 (12.31.2016) -Added: ---- -- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can organize data before hand you can route info to certain connections thus saving a lot of cpu time. - -**NOTE:** Only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used - -Changed: ---- -- Started keeping track of dates -- obj=multi:newConnection() - - obj:connect(func,name) and obj(func,name) - - Added the name argument to allow indexing specific connection objects... Useful when creating an async library - -# Update: 1.1.0 -Changed: ---- -- multi:newConnection(protect) — Changed the way you are able to interact with it by adding the __call metamethod -Old Usage: -```lua -OnUpdate=multi:newConnection() -OnUpdate:connect(function(...) - print("Updating",...) -end) -OnUpdate:Fire(1,2,3) -``` -New Usage: -```lua -OnUpdate=multi:newConnection() -OnUpdate(function(...) - print("Updating",...) -end) -OnUpdate:Fire(1,2,3) -``` - -# Update: 1.0.0 -Added: ---- -- Start of official changelog (internal) - -# Update: 0.6.3 -**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work - -# Update: 0.6.2 -**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work - -# Update: 0.6.1-6 -**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work - -# Update: 0.5.1-6 -**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work - -# Update: 0.4.1 -**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work - -# Update: 0.3.0 - The update that started it all -Changed: ---- -- Renamed the library to multi from EventManager and a ton of resturcturing -- Started the version numbering over - -# Update: EventManager 2.0.0 -Changed: ---- -- Everything, complete restructuring of the library from function based to object based. Resembles the modern version of the library - -Added: ---- -- Love2d support basic -- event:benchMark() -- event:lManager() — like uManager, but for love2d -- event:uManager(dt) — you could pass the change in time to the method -- event:cManager() -- event:manager() — old version of mainloop() -- @onClose(func) — modifidable -- event:onDraw(func) -- event:onUpdate(func) -- @event:onStart() — modifidable -- event:stop() -- event:createTrigger(func) -- event:createTStep(start,reset,timer,count) -- event:createStep(start,reset,count,skip) -- event:createLoop() -- event:newTask(func) -- event:newAlarm(set,func,start) -- event:hold(task) -- event:oneETime(func) — One time function call -- event:oneTime(func) -# Update: EventManager 1.2.0 -Changed: ---- -- Made Alarms have their own load order, before alarms were grouped with events -- events started becoming more object like -- event:setAlarm => event:newAlarm() returns alarm object -Note: Weird period where both fuction based and object based features worked! - -Added: ---- -- event:Hold(task) -- event:destroyEvent(tag) — destroys an event - -# Update: EventManager 1.1.0 -Added: ---- -- LoadOrder — allow you to change what was processed first events, steps, or updates -- event:setLoadOrder(str) — ESU,EUS,USE,UES,SEU,SUE -- event:createTStep(tag,reset,timer,endc) — works like steps but sleeps between loops -# Update: EventManager 1.0.0 - Error checking -Changed: ---- -- Added error checking which would alert you when something was done wrong - -# Version: EventManager 0.0.1 - In The Beginning things were very different -Usage: ---- -```lua -require("BasicCommands") -require("EventManager") -event:setAlarm("test",1) -a=0 -b=0 -function Alarm_test(tag) - print("Alarm: "..tag) - a = 1 - b=b+1 - event:updateAlarm("test",1) -end -event:setEvent("test()","a == 1") -event:setEvent("test2()","b == 5") -function Event_test() - print("Event! A=1 which means the alarm rang") - a = 0 -end -function Event_test2() - event:Stop() -end --- this might feel somewhat familiar -step = event:createStep("test",10,true) -function Step_test(pos) - print(pos,self) -end -function Step_test_End(tag) - step:Remove() -end -function event:OnCreate() - print(event:stepExist("test")) - print(event:eventExist("test")) - print(event:alarmExist("test")) -end -function event:OnUpdate() - -- Called every cycle -end -function event:OnClose() - print("Manager was stopped!") -end -event:Manager() ---event:CManager() ---event:UManager() -- One of the few things that lived in name and spirit the u just became lowercase haha +# Changelog +Table of contents +--- +[Update 16.3.0 - Bug Fixes](#update-1620---bug-fixes)
+[Update 16.2.0 - TimeStamps and UUIDs](#update-1620---timestamps-and-uuids)
+[Update 16.1.0 - The Flow State](#update-1610---the-flow-state)
+[Update 16.0.1 - Bug fix](#update-1601---bug-fix)
+[Update 16.0.0 - Connecting the dots](#update-1600---getting-the-priorities-straight)
+[Update 15.3.1 - Bug fix](#update-1531---bug-fix)
+[Update 15.3.0 - A world of connections](#update-1530---a-world-of-connections)
+[Update 15.2.1 - Bug fix](#update-1521---bug-fix)
+[Update 15.2.0 - Upgrade Complete](#update-1520---upgrade-complete)
+[Update 15.1.0 - Hold the thread!](#update-1510---hold-the-thread)
+[Update 15.0.0 - The art of faking it](#update-1500---the-art-of-faking-it)
+[Update 14.2.0 - Bloatware Removed](#update-1420---bloatware-removed)
+[Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
+[Update 14.0.0 - Consistency, Additions and Stability](#update-1400---consistency-additions-and-stability)
+[Update 13.1.0 - Bug fixes and features added](#update-1310---bug-fixes-and-features-added)
+[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300---added-some-documentation-and-some-new-features-too-check-it-out)
+[Update 12.2.2 - Time for some more bug fixes!](#update-1222---time-for-some-more-bug-fixes)
+[Update 12.2.1 - Time for some bug fixes!](#update-1221---time-for-some-bug-fixes)
+[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
+[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
+[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200---big-update-lots-of-additions-some-changes)
+[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
+[Update: 1.11.0](#update-1110)
+[Update: 1.10.0](#update-1100)
+[Update: 1.9.2](#update-192)
+[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
+[Update: 1.9.0](#update-190)
+[Update: 1.8.7](#update-187)
+[Update: 1.8.6](#update-186)
+[Update: 1.8.5](#update-185)
+[Update: 1.8.4](#update-184)
+[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
+[Update: 1.8.2](#update-182)
+[Update: 1.8.1](#update-181)
+[Update: 1.7.6](#update-176)
+[Update: 1.7.5](#update-175)
+[Update: 1.7.4](#update-174)
+[Update: 1.7.3](#update-173)
+[Update: 1.7.2](#update-172)
+[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
+[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
+[Update: 1.6.0](#update-160)
+[Update: 1.5.0](#update-150)
+[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
+[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
+[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
+[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
+[Update: 1.1.0](#update-110)
+[Update: 1.0.0](#update-100)
+[Update: 0.6.3](#update-063)
+[Update: 0.6.2](#update-062)
+[Update: 0.6.1-6](#update-061-6)
+[Update: 0.5.1-6](#update-051-6)
+[Update: 0.4.1](#update-041)
+[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
+[Update: EventManager 2.0.0](#update-eventmanager-200)
+[Update: EventManager 1.2.0](#update-eventmanager-120)
+[Update: EventManager 1.1.0](#update-eventmanager-110)
+[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
+[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) + +# Update 16.3.0 - Bug Fixes + +Added +--- + +Changed +--- +- `thread.kill(msg)` now accepts a message. If omitted will use default `thread killed!` + +Fixed +--- +- [Issue](https://github.com/rayaman/multi/issues/77) with concat of connections where conn .. func did not work properly +- [Issue](https://github.com/rayaman/multi/issues/67) with connection multiplying + +# Update 16.2.0 - TimeStamps and UUIDs and bugfixes +Added +--- +- threadedfunctions, multiobjs and threads contain new method: `GetCreationTimestamp()` With will return a string formated date time using ISO 8601 + +Changed +--- +- Internal uuid functions to not use bitwise operators + +Fixed +--- +- [Bitwise operations break lua 5.1/luajit](https://github.com/rayaman/multi/issues/73) + +# Update 16.1.0 - The Flow State +Added +--- +- `multi.UUID()` generates a uuid7, if chronos is installed that will be used as the initial time seed +- `More control over processors` + - `multi:newProcessor(name, opts, priority)` -- Now accepts a opts table. Old param still works + + | Option | Description | Default | + | --- | --- | --- | + | Attach | If true a hook will be attached to the parent process and will run when Start is called or Start is set | false | + | MaxObjects | Maximum number of objects that a processor can spawn | -1 (disabled) | + | MaxThreads | Maximum number of threads that a processor can spawn | -1 (disabled) | + | Start | If true the processor will start instantlly | false | + | Priority | If true the processor will use the priority handler | false | + | TaskDelay | Sets the task delay in seconds between each tasks execution | 0 | + | TashHandler | If false will disable the task feature of a processor | true | + - `proc:newThread()` returns nil, "errorstring" if it was unable to create a thread + - `proc:new[Object]` now returns {unscheduled}, "errorstring", used to just return the {scheduledObject} and no errorstring + - `proc:getMaxThreads()` -- returns the max thread limit + - `proc:setMaxThreads(n)` -- sets the max thread limit + - `proc:getMaxObjects()` -- gets the max object limit + - `proc:setMaxObjects(n)` -- sets the max object limit + - `proc.Status` -- A table that contains all status errors that occured in a process + Example: + ```lua + local multi, thread = require("multi"):init() + + proc = multi:newProcessor("thread test",{ + Start = true, + MaxThreads = 1, + MaxObjects = 1, + Attach = true, + TaskDelay = .1, + Priority = true, + TaskHandler = false + }) + + print(proc:newThread("testing",function() + while true do + print("STATUS:\n---") + for i,v in ipairs(proc.Status) do + print(i,v) + end + thread.sleep(1) + end + end)) + proc:newThread("testing 2",function() + while true do + print("testing 2...") + thread.sleep(1) + end + end) + proc:newThread("testing 3",function() + while true do + print("testing 3...") + thread.sleep(1) + end + end) + + proc:newLoop(function() + -- + end) + + print(proc:newLoop(function() + -- + end)) + + multi:mainloop() + ``` +- `multi.hasType(typ)` returns true if a type has been registered +- `multi.isMultiObj(obj)` returns true if the object is a multi object +- `multi.forwardConnection(src, dest)` forwards events from one connection to another connection. Doesn't modify anything and both connections are triggered when src is Fired, but not when dest is fired. +- `multi.isTimeout(res)` returns true if the response it gets is a timeout type or a string equal to `multi.TIMEOUT`'s value +- `multi:newTimeout(seconds)` returns a connection that will trigger after a certain amount of time. See example below: +```lua +local multi, thread = require("multi"):init() + +data = multi:newConnection() + +-- This alarm takes too long... We will timeout +multi:newAlarm(4):OnRing(function() + data:Fire({Type="request"},"data is tasty") +end) + +multi:newThread(function() + res, data = thread.hold(data + multi:newTimeout(3)) -- combined connections allow this to work + if multi.isTimeout(res) then + print("We timed out!") + else + print("We got the data:", data) + end + os.exit() +end) + +multi:mainloop() +``` +- `connection % function` can now modify the arguments of a connection. See above example modified below +```lua +local multi, thread = require("multi"):init() + +local data = multi:newAlarm(1).OnRing % function() return {Type="request"}, "data is tasty" end + +multi:newThread(function() + res, data = thread.hold(data + multi:newTimeout(3)) + if multi.isTimeout(res) then + print("We timed out!") + else + print("We got the data:", data) + end + os.exit() +end) + +multi:mainloop() +``` + +If the alarm takes longer the the timeout: `We timed out!` If the alarm is shorter: `We got the data: data is tasty` + +Changed +--- +- `multi:newBase(tp,ins,callback)` now accepts a tp param and a callback function. Callback is expected to return true/false, errorstring. If callback is false it will cancel scheduling of an object. If type is set the type of the object will be set. + + **Note:** This is all internal functionality + +# Update 16.0.1 - Bug fix +Fixed +--- +- thread.pushStatus() wasn't properly working when forwarding events from THREAD.pushStatus OnStatus connection. This bug also caused stack overflow errors with the following code +```lua +func = thread:newFunction(function() + for i=1,10 do + thread.sleep(1) + thread.pushStatus(i) + end +end) + +func2 = thread:newFunction(function() + local ref = func() + ref.OnStatus(function(num) + -- do stuff with this data + + thread.pushStatus(num*2) -- Technically this is not ran within a thread. This is ran outside of a thread inside the thread handler. + end) +end) + +local handler = func2() +handler.OnStatus(function(num) + print(num) +end) + +multi:mainloop() +``` + +# Update 16.0.0 - Getting the priorities straight + +## Added New Integration: **priorityManager** + +Allows the user to have multi auto set priorities (Requires chronos). Also adds the functionality to create your own runners (multi:mainloop(), multi:umanager()) that you can set using the priority manager. Even if you do not have `chronos` installed all other features will still work! +- Allows the creation of custom priorityManagers + +Added +--- +- thread.defer(func) -- When using a co-routine thread or co-routine threaded function, defer will call it's function at the end of the the threads life through normal execution or an error. In the case of a threaded function, when the function returns or errors. +- multi:setTaskDelay(delay), Tasks which are now tied to a processor can have an optional delay between the execution between each task. Useful perhaps for rate limiting. Without a delay all grouped tasks will be handled in one step. `delay` can be a function as well and will be processed as if thread.hold was called. +- processor's now have a boost function which causes it to run its processes the number of times specified in the `boost(count)` function +- thread.hold will now use a custom hold method for objects with a `Hold` method. This is called like `obj:Hold(opt)`. The only argument passed is the optional options table that thread.hold can pass. There is an exception for connection objects. While they do contain a Hold method, the Hold method isn't used and is there for proxy objects, though they can be used in non proxy/thread situations. Hold returns all the arguments that the connection object was fired with. +- shared_table = STP:newSharedTable(tbl_name) -- Allows you to create a shared table that all system threads in a process have access to. Returns a reference to that table for use on the main thread. Sets `_G[tbl_name]` on the system threads so you can access it there. + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor(8) + + local shared = stp:newSharedTable("shared") + + shared["test"] = "We work!" + + for i=1,5 do + -- There is a bit of overhead when creating threads on a process. Takes some time, mainly because we are creating a proxy. + stp:newThread(function() + local multi, thread = require("multi"):init() + local shared = _G["shared"] + print(THREAD_NAME, shared.test, shared.test2) + multi:newAlarm(.5):OnRing(function() -- Play around with the time. System threads do not create instantly. They take quite a bit of time to get spawned. + print(THREAD_NAME, shared.test, shared.test2) + end) + end) + end + + shared["test2"] = "We work!!!" + + multi:mainloop() + ``` + + Output: + ``` + INFO: Integrated Lanes Threading! + STJQ_cPXT8GOx We work! nil + STJQ_hmzdYDVr We work! nil + STJQ_3lwMhnfX We work! nil + STJQ_hmzdYDVr We work! nil + STJQ_cPXT8GOx We work! nil + STJQ_cPXT8GOx We work! We work!!! + STJQ_hmzdYDVr We work! We work!!! + STJQ_3lwMhnfX We work! We work!!! + STJQ_hmzdYDVr We work! We work!!! + STJQ_cPXT8GOx We work! We work!!! + ``` + +- multi:chop(obj) -- We cannot directly interact with a local object on lanes, so we chop the object and set some globals on the thread side. Should use like: `mulit:newProxy(multi:chop(multi:newThread(function() ... end)))` +- multi:newProxy(ChoppedObject) -- Creates a proxy object that allows you to interact with an object on a thread + + **Note:** Objects with __index=table do not work with the proxy object! The object must have that function in it's own table for proxy to pick it up and have it work properly. Connections on a proxy allow you to subscribe to an event on the thread side of things. The function that is being connected to happens on the thread! +- multi:newSystemThreadedProcessor(name) -- Works like newProcessor(name) each object created returns a proxy object that you can use to interact with the objects on the system thread + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor("Test STP") + + alarm = stp:newAlarm(3) + + alarm._OnRing:Connect(function(alarm) + print("Hmm...", THREAD_NAME) + end) + ``` + Output: + ``` + Hmm... SystemThreadedJobQueue_A5tp + ``` + Internally the SystemThreadedProcessor uses a JobQueue to handle things. The proxy function allows you to interact with these objects as if they were on the main thread, though there actions are carried out on the main thread. + + Proxies can also be shared between threads, just remember to use proxy:getTransferable() before transferring and proxy:init() on the other end. (We need to avoid copying over coroutines) + + The work done with proxies negates the usage of multi:newSystemThreadedConnection(), the only difference is you lose the metatables from connections. + + You cannot connect directly to a proxy connection on the non proxy thread, you can however use proxy_conn:Hold() or thread.hold(proxy_conn) to emulate this, see below. + + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true, warn=true, error=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor(8) + + tloop = stp:newTLoop(nil, 1) + + multi:newSystemThread("Testing proxy copy",function(tloop) + local function tprint (tbl, indent) + if not indent then indent = 0 end + for k, v in pairs(tbl) do + formatting = string.rep(" ", indent) .. k .. ": " + if type(v) == "table" then + print(formatting) + tprint(v, indent+1) + else + print(formatting .. tostring(v)) + end + end + end + local multi, thread = require("multi"):init() + tloop = tloop:init() + print("tloop type:",tloop.Type) + print("Testing proxies on other threads") + thread:newThread(function() + while true do + thread.hold(tloop.OnLoop) + print(THREAD_NAME,"Loopy") + end + end) + tloop.OnLoop(function(a) + print(THREAD_NAME, "Got loop...") + end) + multi:mainloop() + end, tloop:getTransferable()).OnError(multi.error) + + print("tloop", tloop.Type) + + thread:newThread(function() + print("Holding...") + thread.hold(tloop.OnLoop) + print("Held on proxied no proxy connection 1") + end).OnError(print) + + thread:newThread(function() + tloop.OnLoop:Hold() + print("held on proxied no proxy connection 2") + end) + + tloop.OnLoop(function() + print("OnLoop",THREAD_NAME) + end) + + thread:newThread(function() + while true do + tloop.OnLoop:Hold() + print("OnLoop",THREAD_NAME) + end + end).OnError(multi.error) + + multi:mainloop() + ``` + Output: + ``` + INFO: Integrated Lanes Threading! 1 + tloop proxy + Holding... + tloop type: proxy + Testing proxies on other threads + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + OnLoop MAIN_THREAD + Testing proxy copy Loopy + Held on proxied no proxy connection 1 + held on proxied no proxy connection 2 + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + Testing proxy copy Loopy + OnLoop MAIN_THREAD + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + + ... (Will repeat every second) + + Testing proxy copy Loopy + OnLoop MAIN_THREAD + OnLoop STJQ_W9SZGB6Y + STJQ_W9SZGB6Y Got loop... + + ... + ``` + + The proxy version can only subscribe to events on the proxy thread, which means that connection metamethods will not work with the proxy version (`_OnRing` on the non proxy thread side), but the (`OnRing`) version will work. Cleverly handling the proxy thread and the non proxy thread will allow powerful connection logic. Also this is not a full system threaded connection. **Proxies should only be used between 2 threads!** To keep things fast I'm using simple queues to transfer data. There is no guarantee that things will work! + + Currently supporting: + - proxyLoop = STP:newLoop(...) + - proxyTLoop = STP:newTLoop(...) + - proxyUpdater = STP:newUpdater(...) + - proxyEvent = STP:newEvent(...) + - proxyAlarm = STP:newAlarm(...) + - proxyStep = STP:newStep(...) + - proxyTStep = STP:newTStep(...) + - proxyThread = STP:newThread(...) + - proxyService = STP:newService(...) + - threadedFunction = STP:newFunction(...) + + Unique: + - STP:newSharedTable(name) + +
+ + **STP** functions (The ones above) cannot be called within coroutine based thread when using lanes. This causes thread.hold to break. Objects(proxies) returned by these functions are ok to use in coroutine based threads! + ```lua + package.path = "?/init.lua;?.lua;"..package.path + + multi, thread = require("multi"):init({print=true}) + THREAD, GLOBAL = require("multi.integration.lanesManager"):init() + + stp = multi:newSystemThreadedProcessor() + + alarm = stp:newAlarm(3) + + alarm.OnRing:Connect(function(alarm) + print("Hmm...", THREAD_NAME) + end) + + thread:newThread(function() + print("Holding...") + local a = thread.hold(alarm.OnRing) -- it works :D + print("We work!") + end) + + multi:mainloop() + ``` + +- multi.OnObjectDestroyed(func(obj, process)) now supplies obj, process just like OnObjectCreated +- thread:newProcessor(name) -- works mostly like a normal process, but all objects are wrapped within a thread. So if you create a few loops, you can use thread.hold() call threaded functions and wait and use all features that using coroutines provide. +- multi.Processors:getHandler() -- returns the thread handler for a process +- multi.OnPriorityChanged(self, priority) -- Connection is triggered whenever the priority of an object is changed! +- multi.setClock(clock_func) -- If you have access to a clock function that works like os.clock() you can set it using this function. The priorityManager if chronos is installed sets the clock to it's current version. +- multi:setCurrentTask() -- Used to set the current processor. Used in custom processors. +- multi:setCurrentProcess() -- Used to set the current processor. It should only be called on a processor object +- multi.success(...) -- Sends a success. Green `SUCCESS` mainly used for tests +- multi.warn(...) -- Sends a warning. Yellow `WARNING` +- multi.error(err) -- When called this function will gracefully kill multi, cleaning things up. Red `ERROR` + + **Note:** If you want to have multi.print, multi.warn and multi.error to work you need to enable them in settings + ```lua + multi, thread = require("multi"):init { + print=true, + warn=true, + error=true -- Errors will throw regardless. Setting to true will + -- cause the library to force hard crash itself! + } + ``` +- THREAD.exposeEnv(name) -- Merges set env into the global namespace of the system thread it was called in. +- THREAD.setENV(table [, name]) -- Set a simple table that will be merged into the global namespace. If a name is supplied the global namespace will not be merged. Call THREAD.exposeEnv(name) to expose that namespace within a thread. + + **Note:** To maintain compatibility between each integration use simple tables. No self references, and string indices only. + ```lua + THREAD.setENV({ + shared_function = function() + print("I am shared!") + end + }) + ``` + When this function is used it writes to a special variable that is read at thread spawn time. If this function is then ran later it can be used to set a different env and be applied to future spawned threads. +- THREAD.getENV() can be used to manage advanced uses of the setENV() functionality +- Connection objects now support the % function. This supports a function % connection object. What it does is allow you to **mod**ify the incoming arguments of a connection event. + ```lua + local conn1 = multi:newConnection() + local conn2 = function(a,b,c) return a*2, b*2, c*2 end % conn1 + conn2(function(a,b,c) + print("Conn2",a,b,c) + end) + conn1(function(a,b,c) + print("Conn1",a,b,c) + end) + conn1:Fire(1,2,3) + conn2:Fire(1,2,3) + ``` + Output: + ``` + Conn2 2 4 6 + Conn1 1 2 3 + Conn2 1 2 3 + ``` + **Note:** Conn1 does not get modified, however firing conn1 will also fire conn2 and have it's arguments modified. Also firing conn2 directly **does not** modify conn2's arguments! + See it's implementation below: + ```lua + __mod = function(obj1, obj2) + local cn = multi:newConnection() + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + else + error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end + ``` +- The len operator `#` will return the number of connections in the object! + ``` + local conn = multi:newConnection() + conn(function() print("Test 1") end) + conn(function() print("Test 2") end) + conn(function() print("Test 3") end) + conn(function() print("Test 4") end) + print(#conn) + ``` + Output: + ``` + 4 + ``` +- Connection objects can be negated -conn returns self so conn = -conn, reverses the order of connection events + ```lua + local conn = multi:newConnection() + conn(function() print("Test 1") end) + conn(function() print("Test 2") end) + conn(function() print("Test 3") end) + conn(function() print("Test 4") end) + + print("Fire 1") + conn:Fire() + conn = -conn + print("Fire 2") + conn:Fire() + ``` + Output: + ``` + Fire 1 + Test 1 + Test 2 + Test 3 + Test 4 + Fire 2 + Test 4 + Test 3 + Test 2 + Test 1 + ``` +- Connection objects can be divided, function / connection + This is a mix between the behavior between mod and concat, where the original connection can forward it's events to the new one as well as do a check like concat can. View it's implementation below: + ```lua + __div = function(obj1, obj2) -- / + local cn = self:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + local args = {obj1(...)} + if args[1] then + cn:Fire(multi.unpack(args)) + end + end) + else + multi.error("Invalid divide! ", type(obj1), type(obj2)," Expected function/connection(table)") + end + return cn + end + ``` +- Connection objects can now be concatenated with functions, not each other. For example: + ```lua + multi, thread = require("multi"):init{print=true,findopt=true} + + local conn1, conn2 = multi:newConnection(), multi:newConnection() + conn3 = conn1 + conn2 + + conn1(function() + print("Hi 1") + end) + + conn2(function() + print("Hi 2") + end) + + conn3(function() + print("Hi 3") + end) + + function test(a,b,c) + print("I run before all and control if execution should continue!") + return a>b + end + + conn4 = test .. conn1 + + conn5 = conn2 .. function() print("I run after it all!") end + + conn4:Fire(3,2,3) + + -- This second one won't trigger the Hi's + conn4:Fire(1,2,3) + + conn5(function() + print("Test 1") + end) + + conn5(function() + print("Test 2") + end) + + conn5(function() + print("Test 3") + end) + + conn5:Fire() + ``` + + Output: + ``` + I run before all and control if things go! + Hi 3 + Hi 1 + Test 1 + Test 2 + Test 3 + I run after it all! + ``` + + **Note:** Concat of connections does modify internal events on both connections depending on the direction func .. conn or conn .. func See implemention below: + ```lua + __concat = function(obj1, obj2) + local cn = multi:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) then + obj2:Fire(...) + end + end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + obj1:Fire(...) + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + else + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + end + return cn + end + ``` + +Changed +--- +- multi:newTask(task) is not tied to the processor it is created on. +- `multi:getTasks()` renamed to `multi:getRunners()`, should help with confusion between multi:newTask() +- changed how multi adds unpack to the global namespace. Instead we capture that value into multi.unpack. +- multi:newUpdater(skip, func) -- Now accepts func as the second argument. So you don't need to call OnUpdate(func) after creation. +- multi errors now internally call `multi.error` instead of `multi.print` +- Actors Act() method now returns true when the main event is fired. Steps/Loops always return true. Nil is returned otherwise. +- Connection:Connect(func, name) Now you can supply a name and name the connection. +- Connection:getConnection(name) This will return the connection function which you can do what you will with it. +- Fast connections are the only connections. Legacy connections have been removed completely. Not much should change on the users end. Perhaps some minor changes. +- conn:Lock(conn) When supplied with a connection reference (What is returned by Connect(func)) it will only lock that connection Reference and not the entire connection. Calling without any arguments will lock the entire connection. +- connUnlock(conn) When supplied with a connection reference it restores that reference and it can be fired again. When no arguments are supplied it unlocks the entire connection. + + **Note:** Lock and Unlock when supplied with arguments and not supplied with arguments operate on different objects. If you unlock an entire connection. Individual connection refs will not unlock. The same applies with locking. The entire connection and references are treated differently. + +- multi.OnObjectCreated is only called when an object is created in a particular process. Proc.OnObjectCreated is needed to detect when an object is created within a process. +- multi.print shows "INFO" before it's message. Blue `INFO` +- Connections internals changed, not too much changed on the surface. +- newConnection(protect, func, kill) + - `protect` disables fastmode, but protects the connection + - `func` uses `..` and appends func to the connection so it calls it after all connections run. There is some internal overhead added when using this, but it isn't much. + - `kill` removes the connection when fired + + **Note:** When using protect/kill connections are triggered in reverse order + +Removed +--- +- multi.CONNECTOR_LINK -- No longer used +- multi:newConnector() -- No longer used +- THREAD.getName() use THREAD_NAME instead +- THREAD.getID() use THREAD_ID instead +- conn:SetHelper(func) -- With the removal of old Connect this function is no longer needed +- connection events can no longer can be chained with connect. Connect only takes a function that you want to connect + +Fixed +--- +- Issue with luajit w/5.2 compat breaking with coroutine.running(), fixed the script to properly handle so thread.isThread() returns as expected! +- Issue with coroutine based threads where they weren't all being scheduled due to a bad for loop. Replaced with a while to ensure all threads are consumed properly. If a thread created a thread that created a thread that may or may not be on the same process, things got messed up due to the original function not being built with these abstractions in mind. +- Issue with thread:newFunction() where a threaded function will keep a record of their returns and pass them to future calls of the function. +- Issue with multi:newTask(func) not properly handling tasks to be removed. Now uses a thread internally to manage things. +- multi.isMainThread was not properly handled in each integration. This has been resolved. +- Issue with pseudo threading env's being messed up. Required removal of getName and getID! +- connections being multiplied together would block the entire connection object from pushing events! This is not the desired effect I wanted. Now only the connection reference involved in the multiplication is locked! +- multi:reallocate(processor, index) has been fixed to work with the current changes of the library. +- Issue with lanes not handling errors properly. This is now resolved +- Oversight with how pushStatus worked with nesting threaded functions, connections and forwarding events. Changes made and this works now! + ```lua + func = thread:newFunction(function() + for i=1,10 do + thread.sleep(1) + thread.pushStatus(i) + end + end) + + func2 = thread:newFunction(function() + local ref = func() + ref.OnStatus(function(num) + -- do stuff with this data + + thread.pushStatus(num*2) -- Technically this is not ran within a thread. This is ran outside of a thread inside the thread handler. + end) + end) + + local handler = func2() + handler.OnStatus(function(num) + print(num) + end) + ``` + +ToDo +--- +- Network Manager, I know I said it will be in this release, but I'm still planning it out. + +# Update 15.3.1 - Bug fix +Fixed +--- +- Issue where multiplying connections triggered events improperly +```lua +local multi, thread = require("multi"):init() +conn1 = multi:newConnection() +conn2 = multi:newConnection(); -- To remove function ambiguity + +(conn1 * conn2)(function() print("Triggered!") end) + +conn1:Fire() +conn2:Fire() + +-- Looks like this is triggering a response. It shouldn't. We need to account for this +conn1:Fire() +conn1:Fire() +-- Triggering conn1 twice counted as a valid way to trigger the virtual connection (conn1 * conn2) + +-- Now in 15.3.1, this works properly and the above doesn't do anything. Internally connections are locked until the conditions are met. +conn2:Fire() +``` + +# Update 15.3.0 - A world of Connections + +Full Update Showcase +```lua +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + +local conn = multi:newSystemThreadedConnection("conn"):init() + +multi:newSystemThread("Thread_Test_1",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function() + print(THREAD:getName().." was triggered!") + end) + multi:mainloop() +end) + +multi:newSystemThread("Thread_Test_2",function() + local multi, thread = require("multi"):init() + local conn = GLOBAL["conn"]:init() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + print("Fire 2!!!") + conn:Fire(4,5,6) + THREAD.kill() + end) + + multi:mainloop() +end) + +conn(function(a,b,c) + print("Mainloop conn got triggered!",a,b,c) +end) + +alarm = multi:newAlarm(1) +alarm:OnRing(function() + 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() + conn(function(a,b,c) + print(THREAD:getName().." was triggered!",a,b,c) + end) + multi:newAlarm(2):OnRing(function() + 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() + multi:newAlarm(2):OnRing(function() + conn2:Fire() + end) + multi:newThread(function() + print("Conn Test!") + thread.hold(conn + conn2) + print("It held!") + end) + multi:mainloop() +end) + +multi:mainloop() +``` + +Added +--- +- `multi:newConnection():Unconnect(conn_link)` Fastmode previously didn't have the ability to be unconnected to. This method works with both fastmode and non fastmode. `fastMode` will be made the default in v16.0.0 (This is a breaking change for those using the Destroy method, use this time to migrate to using `Unconnect()`) +- `thread.chain(...)` allows you to chain `thread.hold(FUNCTIONs)` together + ```lua + while true do + thread.chain(hold_function_1, hold_function_2) + end + ``` + If the first function returns true, it moves on to the next one. if expanded it follows: + ```lua + while true do + thread.hold(hold_function_1) + thread.hold(hold_function_2) + end + ``` +- Experimental option to multi settings: `findopt`. When set to `true` it will print out a message when certain pattern are used with this library. For example if an anonymous function is used in thread.hold() within a loop. The library will trigger a message alerting you that this isn't the most performant way to use thread.hold(). +- `multi:newSystemThreadedConnection()` + + Allows one to trigger connection events across threads. Works like how any connection would work. Supports all of the features, can even be `added` with non SystemThreadedConnections as demonstrated in the full showcase. +- `multi:newConnection():SetHelper(func)` + + Sets the helper function that the connection object uses when creating connection links. + +- `multi.ForEach(table, callback_function)` + + Loops through the table and calls callback_function with each element of the array. + +- If a name is not supplied when creating threads and threaded objects; a name is randomly generated. Unless sending through an established channel/queue you might not be able to easily init the object. + +Changed +--- +- Internally all `OnError` events are now connected to with multi.print, you must pass `print=true` to the init settings when initializing the multi object. `require("multi"):init{print=true}` +- All actors now use fastmode on connections +- Performance enhancement with processes that are pumped. Instead of automatically running, by suppressing the creation of an internal loop object that would manage the process, we bypass that freeing up memory and adding a bit more speed. +- `Connection:fastMode() or Connection:SetHelper()` now returns a reference to itself +- `Connection:[connect, hasConnections, getConnection]` changed to be `Connection:[Connect, HasConnections, getConnections]`. This was done in an attempt to follow a consistent naming scheme. The old methods still will work to prevent old code breaking. +- `Connections when added(+) together now act like 'or', to get the 'and' feature multiply(*) them together.` + + **Note:** This is a potentially breaking change for using connections. + + ```lua + multi, thread = require("multi"):init{print=true} + -- GLOBAL, THREAD = require("multi.integration.lanesManager"):init() + + local conn1, conn2, conn3 = multi:newConnection(), multi:newConnection(), multi:newConnection() + + thread:newThread(function() + print("Awaiting status") + thread.hold(conn1 + (conn2 * conn3)) + print("Conn or Conn2 and Conn3") + end) + + multi:newAlarm(1):OnRing(function() + print("Conn") + conn1:Fire() + end) + + multi:newAlarm(2):OnRing(function() + print("Conn2") + conn2:Fire() + end) + + multi:newAlarm(3):OnRing(function() + print("Conn3") + conn3:Fire() + end) + ``` + +Removed +--- +- Connection objects methods removed: + - holdUT(), HoldUT() -- With the way `thread.hold(conn)` interacts with connections this method was no longer needed. To emulate this use `multi.hold(conn)`. `multi.hold()` is able to emulate what `thread.hold()` outside of a thread, albeit with some drawbacks. + +Fixed +--- +- SystemThreaded Objects variables weren't consistent. +- Issue with connections being multiplied only being able to have a combined fire once + +ToDo +--- + +- Work on network parallelism (I am really excited to start working on this. Not because it will have much use, but because it seems like a cool addition/project to work on. I just need time to actually do work on stuff) + +# Update 15.2.1 - Bug fix +Fixed issue #41 +--- + +# Update 15.2.0 - Upgrade Complete + +Full Update Showcase + +```lua +package.path = "./?/init.lua;"..package.path +multi, thread = require("multi"):init{print=true} +GLOBAL, THREAD = require("multi.integration.threading"):init() + +-- Using a system thread, but both system and local threads support this! +-- Don't worry if you don't have lanes or love2d. PesudoThreading will kick in to emulate the threading features if you do not have access to system threading. +func = THREAD:newFunction(function(count) + print("Starting Status test: ",count) + local a = 0 + while true do + a = a + 1 + THREAD.sleep(.1) + -- Push the status from the currently running threaded function to the main thread + THREAD.pushStatus(a,count) + if a == count then break end + end + return "Done" +end) + +thread:newThread("test",function() + local ret = func(10) + ret.OnStatus(function(part,whole) + print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + print("TEST",func(5).wait()) + -- The results from the OnReturn connection is passed by thread.hold + print("Status:",thread.hold(ret.OnReturn)) + print("Function Done!") +end).OnError(function(...) + print("Error:",...) +end) + +local ret = func(10) +local ret2 = func(15) +local ret3 = func(20) +local s1,s2,s3 = 0,0,0 +ret.OnError(function(...) + print("Error:",...) +end) +ret2.OnError(function(...) + print("Error:",...) +end) +ret3.OnError(function(...) + print("Error:",...) +end) +ret.OnStatus(function(part,whole) + s1 = math.ceil((part/whole)*1000)/10 + print(s1) +end) +ret2.OnStatus(function(part,whole) + s2 = math.ceil((part/whole)*1000)/10 + print(s2) +end) +ret3.OnStatus(function(part,whole) + s3 = math.ceil((part/whole)*1000)/10 + print(s3) +end) + +loop = multi:newTLoop() + +function loop:testing() + print("testing haha") +end + +loop:Set(1) +t = loop:OnLoop(function() + print("Looping...") +end):testing() + +local proc = multi:newProcessor("Test") +local proc2 = multi:newProcessor("Test2") +local proc3 = proc2:newProcessor("Test3") +proc.Start() +proc2.Start() +proc3.Start() +proc:newThread("TestThread_1",function() + while true do + thread.sleep(1) + end +end) +proc:newThread("TestThread_2",function() + while true do + thread.sleep(1) + end +end) +proc2:newThread("TestThread_3",function() + while true do + thread.sleep(1) + end +end) + +thread:newThread(function() + thread.sleep(1) + local tasks = multi:getStats() + + for i,v in pairs(tasks) do + print("Process: " ..i.. "\n\tTasks:") + for ii,vv in pairs(v.tasks) do + print("\t\t"..vv:getName()) + end + print("\tThreads:") + for ii,vv in pairs(v.threads) do + print("\t\t"..vv:getName()) + end + end + + thread.sleep(10) -- Wait 10 seconds then kill the process! + os.exit() +end) + +multi:mainloop() +``` + +Added: +--- +- `multi:getStats()` + - Returns a structured table where you can access data on processors there tasks and threads: + ```lua + -- Upon calling multi:getStats() the table below is returned + get_Stats_Table { + proc_1 -- table + proc_2 -- table + ... + proc_n -- table + } + proc_Table { + tasks = {alarms,steps,loops,etc} -- All multi objects + threads = {thread_1,thread_2,thread_3,etc} -- Thread objects + } + -- Refer to the objects documentation to see how you can interact with them + ``` + - Reference the Full update showcase for the method in action +- `multi:newProcessor(name, nothread)` + - If no thread is true auto sets the processor as Active, so proc.run() will start without the need for proc.Start() + +- `multi:getProcessors()` + - Returns a list of all processors + +- `multi:getName()` + - Returns the name of a processor + +- `multi:getFullName()` + - Returns the fullname/entire process tree of a process + +- Processors can be attached to processors + +- `multi:getTasks()` + - Returns a list of all non thread based objects (loops, alarms, steps, etc) + +- `multi:getThreads()` + - Returns a list of all threads on a process + +- `multi:newProcessor(name, nothread).run()` + - New function run to the processor object to + +- `multi:newProcessor(name, nothread):newFunction(func, holdme)` + - Acts like thread:newFunction(), but binds the execution of that threaded function to the processor + +- `multi:newTLoop()` member functions + - `TLoop:Set(set)` - Sets the time to wait for the TLoop + +- `multi:newStep()` member functions + - `Step:Count(count)` - Sets the amount a step should count by + +- `multi:newTStep()` member functions + - `TStep:Set(set)` - Sets the time to wait for the TStep + + +Changed: +--- +- `thread.hold(connectionObj)` now passes the returns of that connection to `thread.hold()`! See Exampe below: + ```lua + multi, thread = require("multi"):init() + + func = thread:newFunction(function(count) + local a = 0 + while true do + a = a + 1 + thread.sleep(.1) + thread.pushStatus(a,count) + if a == count then break end + end + return "Done", 1, 2, 3 + end) + + thread:newThread("test",function() + local ret = func(10) + ret.OnStatus(function(part,whole) + print("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + print("Status:",thread.hold(ret.OnReturn)) + print("Function Done!") + os.exit() + end).OnError(function(...) + print("Error:",...) + end) + + multi:mainloop() + ``` + Output: + ``` + Ret1: 10% + Ret1: 20% + Ret1: 30% + Ret1: 40% + Ret1: 50% + Ret1: 60% + Ret1: 70% + Ret1: 80% + Ret1: 90% + Ret1: 100% + Status: Done 1 2 3 nil nil nil nil nil nil nil nil nil nil nil nil + Function Done! + ``` + +- Modified how threads are handled internally. This changes makes it so threads "regardless of amount" should not impact performance. What you do in the threads might. This change was made by internally only processing one thread per step per processor. If you have 10 processors that are all active expect one step to process 10 threads. However if one processor has 10 threads each step will only process one thread. Simply put each addition of a thread shouldn't impact performance as it did before. +- Moved `multi:newThread(...)` into the thread interface (`thread:newThread(...)`), code using `multi:newThread(...)` will still work. Also using `process:newThread(...)` binds the thread to the process, meaning if the process the thread is bound to is paused so is the thread. + +- multi:mainloop(~~settings~~)/multi:uManager(~~settings~~) no longer takes a settings argument, that has been moved to multi:init(settings) + | Setting | Description | + ---|--- + print | When set to true parts of the library will print out updates otherwise no internal printing will be done + priority | When set to true, the library will prioritize different objects based on their priority +- `multi:newProcessor(name,nothread)` The new argument allows you to tell the system you won't be using the Start() and Stop() functions, rather you will handle the process yourself. Using the proc.run() function. This function needs to be called to pump the events. + - Processors now also use lManager instead of uManager. +- `multi.hold(n,opt)` now supports an option table like thread.hold does. +- Connection Objects now pass on the parent object if created on a multiobj. This was to allow chaining to work properly with the new update + + ```lua + multi,thread = require("multi"):init() + + loop = multi:newTLoop() + + function loop:testing() + print("testing haha") + end + + loop:Set(1) + t = loop:OnLoop(function() + print("Looping...") + end):testing() + + multi:mainloop() + + --[[Returns as expected: + + testing haha + Looping... + Looping... + Looping... + ... + Looping... + Looping... + Looping... + ]] + ``` + + While chaining on the OnSomeEventMethod() wasn't really a used feature, I still wanted to keep it just incase someone was relying on this working. And it does have it uses + +- All Multi Objects now use Connection objects + + `multiobj:OnSomeEvent(func)` or `multiobj.OnSomeEvent(func)` + +- Connection Objects no longer Fire with syntax sugar when attached to an object: + + `multiobj:OnSomeEvent(...)` No longer triggers the Fire event. As part of the update to make all objects use connections internally this little used feature had to be scrapped! + +- multi:newTStep now derives it's functionality from multi:newStep (Cut's down on code length a bit) + +Removed: +--- +- `multi:getTasksDetails()` Remade completely and now called `multi:getStats()` +- `multi:getError()` Removed when setting protect was removed +- `multi:FreeMainEvent()` The new changes with connections make's this function unnecessary +- `multi:OnMainConnect(func)` See above +- `multi:connectFinal(func)` See above +- `multi:lightloop()` Cleaned up the mainloop/uManager method, actually faster than lightloop (Which should have been called liteloop) +- `multi:threadloop()` See above for reasons +- `multi setting: protect` This added extra complexity to the mainloop and not much benefit. If you feel a function will error use pcall yourself. This saves a decent amount of cycles, about 6.25% increase in performance. +- `multi:GetParentProcess()` use `multi.getCurrentProcess()` instead +- priority scheme 2, 3 and auto-priority have been removed! Only priority scheme 1 actually performed in a reasonable fashion so that one remained. +- `multi:newFunction(func)` + - `thread:newFunction(func)` Has many more features and replaces what multi:newFunction did +- `multi.holdFor()` Now that multi.hold takes the option table that thread.hold has this feature can be emulated using that. + +- Calling Fire on a connection no longer returns anything! Now that internal features use connections, I noticed how slow connections are and have increased their speed quite a bit. From 50,000 Steps per seconds to almost 7 Million. All other features should work just fine. Only returning values has been removed + +Fixed: +--- + +- [Issue](https://github.com/rayaman/multi/issues/30) with Lanes crashing the lua state. Issue seemed to be related to my filesystem, since remounting the drive caused the issue to stop. (Windows) + +- [Issue](https://github.com/rayaman/multi/issues/29) where System threaded functions not being up to date with threaded functions + +- Issue where gettasksdetails() would try to process a destroyed object causing it to crash + +- Issue with multi.hold() not pumping the mainloop and only the scheduler + +ToDo: +--- + +- Work on network parallelism + + +# Update 15.1.0 - Hold the thread! + +Full Update Showcase + +```lua +local 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("Ret1: ",math.ceil((part/whole)*1000)/10 .."%") + end) + ret2.OnStatus(function(part,whole) + print("Ret2: ",math.ceil((part/whole)*1000)/10 .."%") + end) + ret3.OnStatus(function(part,whole) + print("Ret3: ",math.ceil((part/whole)*1000)/10 .."%") + end) + -- 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) + +test = thread:newFunction(function() + return 1,2,nil,3,4,5,6,7,8,9 +end,true) +print(test()) +multi:newThread("testing",function() + print("#Test = ",test()) + print(thread.hold(function() + print("Hello!") + return false + end,{ + interval = 2, + cycles = 3 + })) -- End result, 3 attempts within 6 seconds. If still false then timeout + print("held") +end).OnError(function(...) + print(...) +end) + +sandbox = multi:newProcessor() +sandbox:newTLoop(function() + print("testing...") +end,1) + +test2 = multi:newTLoop(function() + print("testing2...") +end,1) + +sandbox:newThread("Test Thread",function() + local a = 0 + while true do + thread.sleep(1) + a = a + 1 + print("Thread Test: ".. multi.getCurrentProcess().Name) + if a == 10 then + sandbox.Stop() + end + end +end).OnError(function(...) + print(...) +end) +multi:newThread("Test Thread",function() + while true do + thread.sleep(1) + print("Thread Test: ".. multi.getCurrentProcess().Name) + end +end).OnError(function(...) + print(...) +end) + +sandbox.Start() + +multi:mainloop() +``` + +Added: +--- + +- multi:newSystemThreadedJobQueue(n) + + `queue:isEmpty()` + + Returns true if the queue is empty, false if there are items in the queue. + +**Note:** a queue might be empty, but the job may still be running and not finished yet! Also if a registered function is called directly instead of pushed, it will not reflect inside the queue until the next cycle! + +Example: +```lua +local multi,thread = require("multi"):init() +GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available + +jq = multi:newSystemThreadedJobQueue(5) -- Job queue with 4 worker threads +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("Empty:",jq:isEmpty()) + end +end).OnError(function(self,msg) + print(msg) +end) +multi:mainloop() +``` + +## multi.TIMEOUT + +`multi.TIMEOUT` is equal to "TIMEOUT", it is reccomended to use this incase things change later on. There are plans to change the timeout value to become a custom object instead of a string. + +## new connections on threaded functions + +- `func.OnStatus(...)` + + Allows you to connect to the status of a function see [thread.pushStatus()](#status-added-to-threaded-functions) + +- `func.OnReturn(...)` + + Allows you to connect to the functions return event and capture its returns see [Example](#status-added-to-threaded-functions) for an example of it in use. + +## multi:newProcessor(name) + +```lua +local 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 +``` + +**Note:** Processor objects have been added and removed many times in the past, but will remain with this update. + +| 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 + +**Note:** All tasks/threads created on a process are linked to that process. If a process is stopped all tasks/threads will be halted until the process is started back up. + +## Connection can now be added together + +Very useful when using thread.hold for multiple connections to trigger. + +Iif you had multiple holds and one finished before others and wasn't consumed it would lock forever! This is now fixed + +`print(conn + conn2 + conn3 + connN)` + +Can be chained as long as you want! See example below + +## Status added to threaded functions +- `thread.pushStatus(...)` + + Allows a developer to push a status from a function. + +- `tFunc.OnStatus(func(...))` + + A connection that can be used on a function to view the status of the threaded function + +Example: + +```lua +local 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) +``` + +Changed: +--- + +- `f = thread:newFunction(func,holdme)` + - Nothing changed that will affect how the object functions by default. The returned function is now a table that is callable and 3 new methods have been added: + + Method | Description + ---|--- + Pause() | Pauses the function, Will cause the function to return `nil, Function is paused` + Resume() | Resumes the function + holdMe(set) | Sets the holdme argument that existed at function creation + + ```lua + local multi, thread = require("multi"):init() + + test = thread:newFunction(function(a,b) + thread.sleep(1) + return a,b + end, true) + + print(test(1,2)) + + test:Pause() + + print(test(1,2)) + + test:Resume() + + print(test(1,2)) + + --[[ -- If you left holdme nil/false + + print(test(1,2).connect(function(...) + print(...) + end)) + + test:Pause() + + print(test(1,2).connect(function(...) + print(...) + end)) + + test:Resume() + + print(test(1,2).connect(function(...) + print(...) + end)) + + ]] + + multi:mainloop() + ``` + + **Output:** + + ``` + 1 2 + nil Function is paused + 1 2 + ``` + + **If holdme is nil/false:** + + ``` + nil Function is paused + + + 1 2 nil... + 1 2 nil... + ``` + +- thread.hold(n,opt) [Ref. Issue](https://github.com/rayaman/multi/issues/24) + - Added option table to thread.hold + | 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 + - `n` can be a number and thread.hold will act like thread.sleep. When `n` is a number the option table will be ignored! + +Removed: +--- + +- N/A + +Fixed: +--- + +- Threaded functions not returning multiple values [Ref. Issue](https://github.com/rayaman/multi/issues/21) +- Priority Lists not containing Very_High and Very_Low from previous update +- All functions that should have chaining now do, reminder all functions that don't return any data return a reference to itself to allow chaining of method calls. + +ToDo +--- + +- Work on network parallelism (I really want to make this, but time and getting it right is proving much more difficult) +- Work on QOL changes to allow cleaner code like [this](#connection-can-now-be-added-together) + +# Update 15.0.0 - The art of faking it +Full Update Showcase +--- +```lua +local multi,thread = require("multi"):init() +GLOBAL,THREAD = require("multi.integration.threading"):init() -- Auto detects your enviroment and uses what's available + +jq = multi:newSystemThreadedJobQueue(4) -- 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 + +multi:newThread("Standard Thread 1",function() + while true do + thread.sleep(1) + print("Testing 1 ...") + end +end) + +multi:newISOThread("ISO Thread 2",{test=true},function() + while true do + thread.sleep(1) + print("Testing 2 ...") + end +end) + +multi:mainloop() +``` +Note: +--- +This was supposed to be released over a year ago, but work and other things got in my way. Pesudo Threading now works. The goal of this is so you can write modules that can be scaled up to utilize threading features when available. + +Added: +--- +- multi:newISOThread(name,func,env) + - Creates an isolated thread that prevents both locals and globals from being accessed. + - Was designed for the pesudoManager so it can emulate threads. You can use it as a super sandbox, but remember upvalues are also stripped which was intened for what I wanted them to do! +- Added new integration: pesudoManager, functions just like lanesManager and loveManager, but it's actually single threaded + - This was implemented because, you may want to build your code around being multi threaded, but some systems/implemetations of lua may not permit this. Since we now have a "single threaded" implementation of multi threading. We can actually create scalable code where things automatcally are threaded if built correctly. I am planning on adding more threadedOjbects. +- In addition to adding pesudo Threading `multi.integration.threading` can now be used to autodetect which enviroment you are on and use the threading features. + ``` + GLOBAL,THREAD = require("multi.integration.threading"):init() + ``` + If you are using love2d it will use that, if you have lanes avaialble then it will use lanes. Otherwise it will use pesudo threading. This allows module creators to implement scalable features without having to worry about which enviroment they are in. Can now require a consistant module: `require("multi.integration.threading"):init()` + +Changed: +--- +- Documentation to reflect the changes made + +Removed: +--- +- CBT (Coroutine Based threading) has lost a feature, one that hasn't been used much, but broke compatiblity with anything above lua 5.1. My goal is to make my library work with all versions of lua above 5.1, including 5.4. Lua 5.2+ changed how enviroments worked which means that you can no longer modify an enviroment of function without using the debug library. This isn't ideal for how things in my library worked, but it is what it is. The feature lost is the one that converted all functions within a threaded enviroment into a threadedfunction. This in hindsight wasn't the best pratice and if it is the desired state you as the user can manually do that anyway. This shouldn't affect anyones code in a massive way. + +Fixed: +--- +- pseudoThreading and threads had an issue where they weren't executing properly +- lanesManager THREAD:get(STRING: name) not returning the value +- Issue where threaded function were not returning multiple values + +Todo: +--- +- Add more details to the documentation + +# Update 14.2.0 - Bloatware Removed +Full Update Showcase +--- +```lua +local multi,thread = require("multi"):init() + +-- Testing destroying and fixed connections +c = multi:newConnection() +c1 = c(function() + print("called 1") +end) +c2 = c(function() + print("called 2") +end) +c3 = c(function() + print("called 3") +end) + +print(c1,c2.Type,c3) +c:Fire() +c2:Destroy() +print(c1,c2.Type,c3) +c:Fire() +c1:Destroy() +print(c1,c2.Type,c3) +c:Fire() + +-- Destroying alarms and threads +local test = multi:newThread(function() + while true do + thread.sleep(1) + print("Hello!") + end +end) + +test.OnDeath(function() + os.exit() -- This is the last thing called. +end) + +local alarm = multi:newAlarm(4):OnRing(function(a) + print(a.Type) + a:Destroy() + print(a.Type) + test:Destroy() +end) + +multi:lightloop() +``` +Going Forward: +--- +- There is no longer any plans for sterilization! Functions do not play nice on different platforms and there is no simple way to ensure that things work. + +Quality Of Life: +--- +- threaded functions now return only the arguments that are needed, if it has trailing nils, they wont be returned like they used to. + +Added: +--- +- Type: destroyed + - A special state of an object that causes that object to become immutable and callable. The object Type is always "destroyed" it cannot be changed. The object can be indexed to infinity without issue. Every part of the object can be called as if it were a function including the indexed parts. This is done incase you destroy an object and still "use" it somewhere. However, if you are expecting something from the object then you may still encounter an error, though the returned type is an instance of the destroyed object which can be indexed and called like normal. This object can be used in any way and no errors will come about with it. + +Fixed: +--- +- thread.holdFor(n,func) and thread.holdWithin(n,func) now accept a connection object as the func argument +- Issue with threaded functions not handling nil properly from returns. This has been resolved and works as expected. +- Issue with system threaded job queues newFunction() not allowing nil returns! This has be addressed and is no longer an issue. +- Issue with hold like functions not being able to return `false` +- Issue with connections not returning a handle for managing a specific conn object. +- Issue with connections where connection chaining wasn't working properly. This has been addressed. + ```lua + local multi,thread = require("multi"):init() + test = multi:newConnection() + test(function(hmm) + print("hi",hmm.t) + hmm.t = 2 + end)(function(hmm) + print("hi2",hmm.t) + hmm.t = 3 + end)(function(hmm) + print("hi3",hmm.t) + end) + test:Fire({t=1}) + ``` + +Changed: +--- +- Destroying an object converts the object into a 'destroyed' type. +- connections now have type 'connector_link' + ```lua + OnExample = multi:newConnection() -- Type Connector, Im debating if I should change this name to multi:newConnector() and have connections to it have type connection + conn = OnExample(...) + print(conn.Type) -- connector_link + ``` + +Removed: (Cleaning up a lot of old features) +--- +- Removed multi:newProcessor(STRING: file) — Old feature that is not really needed anymore. Create your multi-objs on the multi object or use a thread +- bin dependency from the rockspec +- Example folder and .html variants of the .md files +- multi:newTrigger() — Connections do everything this thing could do and more. +- multi:newHyperThreadedProcess(name)* +- multi:newThreadedProcess(name)* +- multi.nextStep(func)* — The new job System can be used instead to achieve this +- multi.queuefinal(self) — An Old method for a feature long gone from the library +- multi:setLoad(n)* +- multi:setThrestimed(n)* +- multi:setDomainName(name)* +- multi:linkDomain(name)* +- multi:_Pause()* — Use multi:Stop() instead! +- multi:isHeld()/multi:IsHeld()* Holding is handled differently so a held variable is no longer needed for chacking. +- multi.executeFunction(name,...)* +- multi:getError()* — Errors are nolonger gotten like that, multi.OnError(func) is the way to go +- multi.startFPSMonitior()* +- multi.doFPS(s)* + +*Many features have become outdated/redundant with new features and additions that have been added to the library + +# Update 14.1.0 - A whole new world of possibilities +Full Update Showcase +--- +Something I plan on doing each version going forward +```lua +local multi, thread = require("multi"):init() +GLOBAL,THREAD = require("multi.integration.lanesManager"):init() +serv = multi:newService(function(self,data) + print("Service Uptime: ",self:GetUpTime(),data.test) +end) +serv.OnError(function(...) + print(...) +end) +serv.OnStarted(function(self,data) + print("Started!",self.Type,data) + data.test = "Testing..." + -- self as reference to the object and data is a reference to the datatable that the service has access to +end) +serv:Start() +serv:SetPriority(multi.Priority_Idle) +t = THREAD:newFunction(function(...) + print("This is a system threaded function!",...) + THREAD.sleep(1) -- This is handled within a system thread! Note: this creates a system thread that runs then ends. + return "We done!" +end) +print(t("hehe",1,2,3,true).connect(function(...) + print("connected:",...) +end)) -- The same features that work with thread:newFunction() are here as well +multi.OnLoad(function() + print("Code Loaded!") -- Connect to the load event +end) +t = os.clock() +co = 0 +multi.OnExit(function(n) + print("Code Exited: ".. os.clock()-t .." Count: ".. co) -- Lets print when things have ended +end) +test = thread:newFunction(function() + thread.sleep(1) -- Internally this throws a yield call which sends to the scheduler to sleep 1 second for this thread! + return 1,math.random(2,100) +end) +multi:newThread(function() + while true do + thread.skip() -- Even though we have a few metamethods "yielding" I used this as an example of things still happening and counting. It connects to the Code Exited event later on. + co = co + 1 + end +end) +-- We can get around the yielding across metamethods by using a threadedFunction +-- For Example +example = {} +setmetatable(example,{ + __newindex = function(t,k,v) -- Using a threaded function inside of a normal function + print("Inside metamethod",t,k,v) + local a,b = test().wait() -- This function holds the code and "yields" see comment inside the test function! + -- we should see a 1 seconde delay since the function sleeps for a second than returns + print("We did it!",a,b) + rawset(t,k,v) + -- This means by using a threaded function we can get around the yielding across metamethods. + -- This is useful if you aren't using luajit, or if you using lua in an enviroment that is on version 5.1 + -- There is a gotcha however, if using code that was meant to work with another coroutine based scheduler this may not work + end, + __index = thread:newFunction(function(t,k,v) -- Using a threaded function as the metamethod + -- This works by returning a table with a __call metamethod. Will this work? Will lua detect this as a function or a table? + thread.sleep(1) + return "You got a string" + end,true) -- Tell the code to force a wait. We need to do this for metamethods +}) +example["test"] = "We set a variable!" +print(example["test"]) +print(example.hi) +-- When not in a threaded enviroment at root level we need to tell the code that we are waiting! Alternitavely after the function argument we can pass true to force a wait +c,d = test().wait() +print(c,d) +a,b = 6,7 +multi:newThread(function() + a,b = test().wait() -- Will modify Global + print("Waited:",a,b) + + --This returns instantly even though the function isn't done! + print("called") + test().connect(function(a,b) + print("Connected:",a,b) + os.exit() + end) + print("Returned") + -- This waits for the returns since we are demanding them +end) +local test = multi:newSystemThreadedJobQueue(4) -- Load up a queue that has 4 running threads +func = test:newFunction("test",function(a) -- register a function on the queue that has an async function feature + test2() -- Call the other registered function on the queue + return a..a +end,true) +func2 = test:newFunction("test2",function(a) + print("ooo") + console = THREAD:getConsole() + console.print("Hello!",true) +end,true) -- When called internally on the job queue the function is a normal sync function and not an async function. +multi:scheduleJob({min = 15, hour = 14},function() + -- This function will be called once everyday at 2:15 + -- Using a combination of the values above you are able to schedule a time +end) +print(func("1")) +print(func("Hello")) +print(func("sigh")) +multi:lightloop() +``` +Going Forward: +--- +- Finish the network manager +- Planning on finishing sterilization for the library. +- Might remove some objects: steps (multi:newStep()), tsteps (multi:newTStep), jobs (Planning to rework this), events (multi:newEvent()), tloop (multi:newTLoop()), triggers (multi:newTrigger()) + +Modified: +--- +- thread.Priority_* has been moved into THREAD since it pertains to system threads. This only applies to lanes threads! +- 2 new priority options have been added. This addition modified how all options except Core behave. Core is still the highest and Idle is still the lowest! + - multi.Priority_Core — Same: 1 + - multi.Priority_Very_High — Added: 4 + - multi.Priority_High — Was: 4 Now: 16 + - multi.Priority_Above_Normal — Was: 16 Now: 64 + - multi.Priority_Normal — Was: 64 Now: 256 + - multi.Priority_Below_Normal — Was: 256 Now: 1024 + - multi.Priority_Low — Was: 1024 Now: 4096 + - multi.Priority_Very_Low — Added: 16384 + - multi.Priority_Idle — Was: 4096 Now: 65536 + +Added: +--- +- multi:lightloop() — Works like multi:mainloop() + - Removes the extra features like priority management, load balancing and what not. Only the bare minimum is processed. If Priority management is something you liked to use the new Service object provides those features. You could alternatively use multi:mainloop() instead to use the non light features. +- serv = multi:newService(func) + - (func(self,datatable)) + - self — Reference to the service + - datatable — internal data space that the service can store data. Is reset/destroyed when the service is stopped. + - serv.Type: service + - serv.OnError(func) — Triggered if service crashes + - serv.OnStopped(func) — Triggered if service was stopped (In the case of a crash OnStopped is not called) + - serv.OnStarted(func) — Triggered when the service starts + - serv.SetScheme(n) — How to manage priorities + - n: 1 — **default** uses sleeping to manage priority (Less control, but much less expensive) + - n: 2 — uses skipping to manage priority (Finer control, but more expensive) + - serv.Stop() — Stops the service, the internal data table is wiped + - serv.Pause() — Pauses the service + - serv.Resume() — Resumes the service + - serv.Start() — Starts the service + - serv.GetUpTime() — Gets the uptime of the service. Time is only counted when the service is running, if the service is paused then it is nolonger being counted. If the service is started then the uptime reset! + - serv.SetPriority(priority) — Sets the priority level for the service + - multi.Priority_Core + - multi.Priority_High + - multi.Priority_Above_Normal + - multi.Priority_Normal **default** + - multi.Priority_Below_Normal + - multi.Priority_Low + - multi.Priority_Idle +- jq = multi:newSystemThreadedJobQueue(n) + - jq:newFunction([name optional],func,holup) — Provides a newFunction like syntax. If name is left blank a unique one is assigned. The second return after the function is the name of the function. +- console = THREAD:getConsole() — Now working on lanes and love2d, allows you to print from multiple threads while keeping the console from writing over each other + - console.print(...) + - console.write(str) +- multi:scheduleJob(time,func) + - time.min — Minute a value of (0-59) + - time.hour — Hour a value of (0-23) + - time.day — Day of month a value of (1-31) + - time.wday — Weekday a value of (0-6) + - time.month — Month a value of (1-12) +- THREAD:newFunction(func,holup) — A system threaded based variant to thread:newFunction(func,holup) works the same way. Though this should only be used for intensive functions! Calling a STfunction has a decent amount of overhead, use wisely. System threaded jobqueue may be a better choice depending on what you are planning on doing. +- multi:loveloop() — Handles the run function for love2d as well as run the multi mainloop. +- multi.OnLoad(func) — A special connection that allows you to connect to the an event that triggers when the multi engine starts! This is slightly different from multi.PreLoad(func) Which connects before any variables have been set up in the multi table, before any settings are cemented into the core. In most cases they will operate exactly the same. This is a feature that was created with module creators in mind. This way they can have code be loaded and managed before the main loop starts. +- multi.OnExit(func) — A special connection that allows you to connect onto the lua state closing event. + +Changed: +--- +- threaded functions no longer auto detect the presence of arguments when within a threaded function. However, you can use the holup method to produce the same effect. If you plan on using a function in different ways then you can use .wait() and .connect() without setting the holup argument +- thread:newFunction(func,holup) — Added an argument holup to always force the threaded funcion to wait. Meaning you don't need to tell it to func().wait() or func().connect() +- multi:newConnection(protect,callback,kill) — Added the kill argument. Makes connections work sort of like a stack. Pop off the connections as they get called. So a one time connection handler. + - I'm not sure callback has been documented in any form. callback gets called each and everytime conn:Fire() gets called! As well as being triggered for each connfunc that is part of the connection. +- modified the lanes manager to create globals GLOBAL and THREAD when a thread is started. This way you are now able to more closely mirror code between lanes and love. As of right now parity between both enviroments is now really good. Upvalues being copied by default in lanes is something that I will not try and mirror in love. It's better to pass what you need as arguments, this way you can keep things consistant. looping through upvalues and sterlizing them and sending them are very complex and slow. + +Removed: +--- +- multi:newTimeStamper() — schedulejob replaces timestamper + +Fixed: +--- +- Issue where setting the priority of lanes Threads were not working since we were using the data before one could have a chance to set it. This has been resolved! +- Issue where connections object:conn() was firing based on the existance of a Type field. Now this only fires if the table contains a reference to itself. Otherwise it will connect instead of firing +- Issue where async functions connect wasn't properly triggering when a function returned +- Issue where async functions were not passing arguments properly. +- Issue where async functions were not handling errors properly + - nil, err = func().wait() — When waiting + - func().connect(function(err) end) — When connection +- Love2d had an issue where threads crashing would break the mainloop +- Issue where systemthreadedjobqueues pushJob() was not returning the jobID of the job that was pushed! +- Fixed bugs within the extensions.lua file for love threading +- Modified the thread.* methods to perform better (Tables were being created each time one of these methods were called! Which in turn slowed things down. One table is modified to get things working properly) + - thread.sleep() + - thread.hold() + - thread.holdFor() + - thread.holdWithin() + - thread.skip() + - thread.kill() + - thread.yield() + + +# Update 14.0.0 - Consistency, Additions and Stability + +Added: +--- +- multi.init() — Initlizes the library! Must be called for multiple files to have the same handle. Example below +- thread.holdFor(NUMBER sec, FUNCTION condition) — Works like hold, but timesout when a certain amount of time has passed! +- multi.hold(function or number) — It's back and better than ever! Normal multi objs without threading will all be halted where threads will still run. If within a thread continue using thread.hold() and thread.sleep() +- thread.holdWithin(NUMBER; cycles,FUNCTION; condition) — Holds until the condition is met! If the number of cycles passed is equal to cycles, hold will return a timeout error +- multi.holdFor(NUMBER; seconds,FUNCTION; condition) — Follows the same rules as multi.hold while mimicing the functionality of thread.holdWithin +**Note:** when hold has a timeout the first argument will return nil and the second atgument will be TIMEOUT, if not timed out hold will return the values from the conditions +- thread objects now have hooks that allow you to interact with it in more refined ways! +-- tObj.OnDeath(self,status,returns[...]) — This is a connection that passes a reference to the self, the status, whether or not the thread ended or was killed, and the returns of the thread. +-- tObj.OnError(self,error) — returns a reference to self and the error as a string +-- **Limitations:** only 7 returns are possible! This was done because creating and destroying table objects are slow. (The way the scheduler works this would happen every cycle and thats no good) Instead I capture the return values from coroutine.resume into local variables and only allowed it to collect 7 max. +- thread.run(function) — Can only be used within a thread, creates another thread that can do work, but automatically returns whatever from the run function — Use thread newfunctions for a more powerful version of thread.run() +- thread:newFunction(FUNCTION: func) +-- returns a function that gives you the option to wait or connect to the returns of the function. +-- func().wait() — waits for the function to return works both within a thread and outside of one +-- func().connect() — connects to the function finishing +-- func() — If your function does not return anything you dont have to use wait or connect at all and the function will return instantly. You could also use wait() to hold until the function does it thing +-- If the created function encounters an error, it will return nil, the error message! +- special variable multi.NIL was added to allow error handling in threaded functions. +-- multi.NIL can be used in to force a nil value when using thread.hold() +- All functions created in the root of a thread are now converted to threaded functions, which allow for wait and connect features. + + **Note:** these functions are local to the function! And are only converted if they aren't set as local! Otherwise the function is converted into a threaded function + +- lanes threads can now have their priority set using: sThread.priority = + - thread.Priority_Core + - thread.Priority_High + - thread.Priority_Above_Normal + - thread.Priority_Normal + - thread.Priority_Below_Normal + - thread.Priority_Low + - thread.Priority_Idle +- thread.hold() and multi.hold() now accept connections as an argument. See example below + +```lua +local multi, thread = require("multi"):init() +conn = multi:newConnection() +multi:newThread(function() + thread.hold(conn) + print("Connection Fired!!!") +end) +multi:newAlarm(3):OnRing(function() + conn:Fire() +end) +``` + +thread newFunction +```lua +func=thread:newFunction(function(...) + print("Function running...") + thread.sleep(1) + return {1,2,3},"done" +end) +multi:newThread("Test",function() + func().connect(function(...) + print(...) + end) +end) +----OUTPUT---- +> Function running... +> table: 0x008cf340 done nil nil nil nil nil +``` + +thread newFunction using auto convert +```lua +multi, thread = require("multi").init() +a=5 +multi:newThread("Test",function() + function hmm() -- Auto converted into a threaded function + return "Hello!",2 + end + print(a) + a=10 + print(hmm().wait()) +end) +multi:newAlarm(3):OnRing(function() + print(a) +end) +print(hmm) +multi:mainloop() +-----OUTPUT----- +> nil +> 5 +> Hello! 2 nil nil nil nil nil -- The way I manage function returns is by allocating them to predefined locals. Because I pass these values regardless they technically get passed even when they are nil. This choice was made to keep the creation of tables to capture arguments then using unpack to pass them on when processing is done +> 10 +``` + +Fixed: +--- +- Connections had a preformance issue where they would create a non function when using connection.getConnection() of a non existing label. +- An internal mismanagement of the threads scheduler was fixed. Now it should be quicker and free of bugs +- Thread error management is the integrations was not properly implemented. This is now fixed + +Removed: +--- +- multi:newWatcher() — No real use +- multi:newCustomObject() — No real use + +Changed: +--- +- Connections connect function can now chain connections +```lua + + local multi, thread = require("multi").init() + test = multi:newConnection() + test(function(a) + print("test 1",a.Temp) + a.Temp = "No!" + end,function(a) + print("test 2",a.Temp) + a.Temp = "Maybe!" + end,function(a) + print("test 3",a.Temp) + end) + test:Fire({Temp="Yes!"}) +``` +- Ties in to the new function that has been added multi.init() +```lua +local multi, thread = require("multi").init() -- The require multi function still returns the multi object like before +``` +- love/lanesManager system threading integration has been reworked. Faster and cleaner code! Consistant code as well + +Note: Using init allows you to get access to the thread handle. This was done because thread was modifying the global space as well as multi. I wanted to not modify the global space anymore. +internally most of your code can stay the same, you only need to change how the library is required. I do toy a bit with the global space, buy I use a variable name that is invalid as a variable name. The variable name is $multi. This is used internally to keep some records and maintain a clean space + +Also when using intergrations things now look more consistant. +```lua +local multi, thread = require("multi").init() +local GLOBSL, THREAD = require("multi.integration.lanesManager").init() -- or whichever manager you are using +local nGLOBAL, nTHREAD = require("multi.intergration.networkManager).inti() +``` +Note: You can mix and match integrations together. You can create systemthreads within network threads, and you can also create cotoutine based threads within bothe network and system threads. This gives you quite a bit of flexibility to create something awesome. + +Going forward: +--- +- Finish the rework of the networkManager - It "works", but there are packet losses that I cannot explain. I do not know what is causing this at all. Ill fix when I figure it out! +- If all goes well, the future will contain quality of code features. I'll keep an eye out for bugs + +# Update 13.1.0 - Bug fixes and features added +------------- +Added: +--- +- Connections:Lock() — Prevents a connection object form being fired +- Connections:Unlock() — Removes the restriction imposed by conn:Lock() +- new fucntions added to the thread namespace +-- thread.request(THREAD handle,STRING cmd,VARARGS args) — allows you to push thread requests from outside the running thread! Extremely powerful. +-- thread.exec(FUNCTION func) — Allows you to push code to run within the thread execution block! +- handle = multi:newThread() — now returns a thread handle to interact with the object outside fo the thread +-- handle:Pause() +-- handle:Resume() +-- handle:Kill() + +Fixed: +--- +- Minor bug with multi:newThread() in how names and functions were managed +- Major bug with the system thread handler. Saw healthy threads as dead ones +- Major bug the thread scheduler was seen creating a massive amount of 'event' causing memory leaks and hard crashes! This has been fixed by changing how the scheduler opperates. +- newSystemThread()'s returned object now matches both the lanes and love2d in terms of methods that are usable. Error handling of System threads now behave the same across both love and lanes implementations. +- looks like I found a typo, thread.yeild -> thread.yield + +Changed: +--- +- getTasksDetails("t"), the table varaiant, formats threads, and system threads in the same way that tasks are formatted. Please see below for the format of the task details +- TID has been added to multi objects. They count up from 0 and no 2 objects will have the same number +- thread.hold() — As part of the memory leaks that I had to fix thread.hold() is slightly different. This change shouldn't impact previous code at all, but thread.hold() can not only return at most 7 arguments! +- You should notice some faster code execution from threads, the changes improve preformance of threads greatly. They are now much faster than before! +- multi:threadloop() — No longer runs normal multi objects at all! The new change completely allows the multi objects to be seperated from the thread objects! +- local multi, thread = require("multi") — Since coroutine based threading has seen a change to how it works, requring the multi library now returns the namespace for the threading interface as well. For now I will still inject into global the thread namespace, but in release 13.2.0 or 14.0.0 It will be removed! + + +Tasks Details Table format +--- +``` +{ + ["Tasks"] = { + { + ["TID"] = 0, + ["Type"] = scheduler, + ["Name"] = multi.thread, + ["Priority"] = Core, + ["Uptime"] = 6.752 + ["Link"] = tableRef + }, + ... + } , + + ["Systemthreads"] = { + { + ["Uptime"] = 6.752 + ["Link"] = tableRef + ["Name"] = threadname + ["ThreadID"] = 0 + }, + ... + }, + + ["Threads"] = { + { + ["Uptime"] = 6.752 + ["Link"] = tableRef + ["Name"] = threadname + ["ThreadID"] = 0 + }, + ... + }, + + ["ProcessName"] = multi.root, + ["CyclesPerSecondPerTask"] = 3560300, + ["MemoryUsage"] = 1846, in KB returned as a number + ["ThreadCount"] = 1, + ["SystemLoad"] = 0, as a % 100 is max 0 is min + ["PriorityScheme"] = Round-Robin + ["SystemThreadCount"] = 1 +} +``` +# Update 13.0.0 - Added some documentation, and some new features too check it out! +------------- +**Quick note** on the 13.0.0 update: +This update I went all in finding bugs and improving performance within the library. I added some new features and the new task manager, which I used as a way to debug the library was a great help, so much so thats it is now a permanent feature. It's been about half a year since my last update, but so much work needed to be done. I hope you can find a use in your code to use my library. I am extremely proud of my work; 7 years of development, I learned so much about lua and programming through the creation of this library. It was fun, but there will always be more to add and bugs crawling there way in. I can't wait to see where this library goes in the future! + +Fixed: +--- +- Tons of bugs, I actually went through the entire library and did a full test of everything, I mean everything, while writing the documentation. + +Changed: +--- +- A few things, to make concepts in the library more clear. +- The way functions returned paused status. Before it would return "PAUSED" now it returns nil, true if paused +- Modified the connection object to allow for some more syntaxial suger! +- System threads now trigger an OnError connection that is a member of the object itself. multi.OnError() is no longer triggered for a system thread that crashes! + +Connection Example: +```lua +loop = multi:newTLoop(function(self) + self:OnLoops() -- new way to Fire a connection! Only works when used on a multi object, bin objects, or any object that contains a Type variable +end,1) +loop.OnLoops = multi:newConnection() +loop.OnLoops(function() + print("Looping") +end) +multi:mainloop() +``` + +Function Example: +```lua +func = multi:newFunction(function(self,a,b) + self:Pause() + return 1,2,3 +end) +print(func()) -- returns: 1, 2, 3 +print(func()) -- nil, true +``` + +Removed: +--- +- Ranges and conditions — corutine based threads can emulate what these objects did and much better! +- Due to the creation of hyper threaded processes the following objects are no more! + - ~~multi:newThreadedEvent()~~ + - ~~multi:newThreadedLoop()~~ + - ~~multi:newThreadedTLoop()~~ + - ~~multi:newThreadedStep()~~ + - ~~multi:newThreadedTStep()~~ + - ~~multi:newThreadedAlarm()~~ + - ~~multi:newThreadedUpdater()~~ + - ~~multi:newTBase()~~ — Acted as the base for creating the other objects + +These didn't have much use in their previous form, but with the addition of hyper threaded processes the goals that these objects aimed to solve are now possible using a process + +Fixed: +--- +- There were some bugs in the networkmanager.lua file. Desrtoy -> Destroy some misspellings. +- Massive object management bugs which caused performance to drop like a rock. +- Found a bug with processors not having the Destroy() function implemented properly. +- Found an issue with the rockspec which is due to the networkManager additon. The net Library and the multi Library are now codependent if using that feature. Going forward you will have to now install the network library separately +- Insane proformance bug found in the networkManager file, where each connection to a node created a new thread (VERY BAD) If say you connected to 100s of threads, you would lose a lot of processing power due to a bad implementation of this feature. But it goes further than this, the net library also creates a new thread for each connection made, so times that initial 100 by about 3, you end up with a system that quickly eats itself. I have to do tons of rewriting of everything. Yet another setback for the 13.0.0 release (Im releasing 13.0.0 though this hasn't been ironed out just yet) +- Fixed an issue where any argument greater than 256^2 or 65536 bytes is sent the networkmanager would soft crash. This was fixed by increading the limit to 256^4 or 4294967296. The fix was changing a 2 to a 4. Arguments greater than 256^4 would be impossible in 32 bit lua, and highly unlikely even in lua 64 bit. Perhaps someone is reading an entire file into ram and then sending the entire file that they read over a socket for some reason all at once!? +- Fixed an issue with processors not properly destroying objects within them and not being destroyable themselves +- Fixed a bug where pause and resume would duplicate objects! Not good +- Noticed that the switching of lua states, corutine based threading, is slower than multi-objs (Not by much though). +- multi:newSystemThreadedConnection(name,protect) — I did it! It works and I believe all the gotchas are fixed as well. + - Issue one, if a thread died that was connected to that connection all connections would stop since the queue would get clogged! FIXED + - There is one thing, the connection does have some handshakes that need to be done before it functions as normal! + +Added: +--- +- Documentation, the purpose of 13.0.0, orginally going to be 12.2.3, but due to the amount of bugs and features added it couldn't be a simple bug fix update. +- multi:newHyperThreadedProcess(STRING name) — This is a version of the threaded process that gives each object created its own coroutine based thread which means you can use thread.* without affecting other objects created within the hyper threaded processes. Though, creating a self contained single thread is a better idea which when I eventually create the wiki page I'll discuss +- multi:newConnector() — A simple object that allows you to use the new connection Fire syntax without using a multi obj or the standard object format that I follow. +- multi:purge() — Removes all references to objects that are contained withing the processes list of tasks to do. Doing this will stop all objects from functioning. Calling Resume on an object should make it work again. +- multi:getTasksDetails(STRING format) — Simple function, will get massive updates in the future, as of right now It will print out the current processes that are running; listing their type, uptime, and priority. More useful additions will be added in due time. Format can be either a string "s" or "t" see below for the table format +- multi:endTask(TID) — Use multi:getTasksDetails("t") to get the tid of a task +- multi:enableLoadDetection() — Reworked how load detection works. It gives better values now, but it still needs some work before I am happy with it +- THREAD.getID() — returns a unique ID for the current thread. This varaiable is visible to the main thread as well by accessing it through the returned thread object. OBJ.Id Do not confuse this with thread.* this refers to the system threading interface. Each thread, including the main thread has a threadID the main thread has an ID of 0! +- multi.print(...) works like normal print, but only prints if the setting print is set to true +- setting: `print` enables multi.print() to work +- STC: IgnoreSelf defaults to false, if true a Fire command will not be sent to the self +- STC: OnConnectionAdded(function(connID)) — Is fired when a connection is added you can use STC:FireTo(id,...) to trigger a specific connection. Works like the named non threaded connections, only the id's are genereated for you. +- STC: FireTo(id,...) — Described above. + +```lua +local multi = require("multi") +conn = multi:newConnector() +conn.OnTest = multi:newConnection() +conn.OnTest(function() + print("Yes!") +end) +test = multi:newHyperThreadedProcess("test") +test:newTLoop(function() + print("HI!") + conn:OnTest() +end,1) +test:newLoop(function() + print("HEY!") + thread.sleep(.5) +end) +multi:newAlarm(3):OnRing(function() + test:Sleep(10) +end) +test:Start() +multi:mainloop() +``` +Table format for getTasksDetails(STRING format) +```lua +{ + {TID = 1,Type="",Priority="",Uptime=0} + {TID = 2,Type="",Priority="",Uptime=0} + ... + {TID = n,Type="",Priority="",Uptime=0} + ThreadCount = 0 + threads={ + [Thread_Name]={ + Uptime = 0 + } + } +} +``` +**Note:** After adding the getTasksDetails() function I noticed many areas where threads, and tasks were not being cleaned up and fixed the leaks. I also found out that a lot of tasks were starting by default and made them enable only. If you compare the benchmark from this version to last version you;ll notice a signifacant increase in performance. + +**Going forward:** +- Work on system threaded functions +- work on the node manager +- patch up bugs +- finish documentstion + +# Update 12.2.2 - Time for some more bug fixes! + +Fixed: +--- +- multi.Stop() not actually stopping due to the new pirority management scheme and preformance boost changes. + +# Update 12.2.1 - Time for some bug fixes! + +Fixed: SystemThreadedJobQueues +- You can now make as many job queues as you want! Just a warning when using a large amount of cores for the queue it takes a second or 2 to set up the jobqueues for data transfer. I am unsure if this is a lanes thing or not, but love2d has no such delay when setting up the jobqueue! +- You now connect to the OnReady in the jobqueue object. No more holding everything else as you wait for a job queue to be ready +- Jobqueues:doToAll now passes the queues multi interface as the first and currently only argument +- No longer need to use jobqueue.OnReady() The code is smarter and will send the pushed jobs automatically when the threads are ready + +Fixed: SystemThreadedConnection +- They work the exact same way as before, but actually work as expected now. The issue before was how i implemented it. Now each connection knows the number of instances of that object that ecist. This way I no longer have to do fancy timings that may or may not work. I can send exactly enough info for each connection to consume from the queue. + +Removed: multi:newQueuer +- This feature has no real use after corutine based threads were introduced. You can use those to get the same effect as the queuer and do it better too. + +Going forward: +- Will I ever finish steralization? Who knows, but being able to save state would be nice. The main issue is there is no simple way to save state. While I can provide methods to allow one to turn the objects into strings and back, there is no way for me to make your code work with it in a simple way. For now only the basic functions will be here. +- I need to make better documentation for this library as well. In its current state, all I have are examples and not a list of what is what. + +Example +--- +```lua +multi = require("multi") +GLOBAL, THREAD = require("multi.integration.lanesManager").init() +jq = multi:newSystemThreadedJobQueue() +jq:registerJob("test",function(a) + return "Hello",a +end) +jq.OnJobCompleted(function(ID,...) + print(ID,...) +end) +for i=1,16 do + jq:pushJob("test",5) +end +multi:mainloop() +``` + +# Update 12.2.0 - The chains of binding +**Added:** +- multi.nextStep(func) +- Method chaining +- Priority 3 has been added! +- ResetPriority() — This will set a flag for a process to be re evaluated for how much of an impact it is having on the performance of the system. +- setting: auto_priority added! — If only lua os.clock was more fine tuned... milliseconds are not enough for this to work +- setting: auto_lowerbound added! — when using auto_priority this will allow you to set the lowbound for pirority. The defualt is a hyrid value that was calculated to reach the max potential with a delay of .001, but can be changed to whatever. Remember this is set to processes that preform really badly! If lua could handle more detail in regards to os.clock() then i would set the value a bit lower like .0005 or something like that +- setting: auto_stretch added! — This is another way to modify the extent of the lowest setting. This reduces the impact that a low preforming process has! Setting this higher reduces the number of times that a process is called. Only in effect when using auto_priotity +- setting: auto_delay added! — sets the time in seconds that the system will recheck for low performing processes and manage them. Will also upgrade a process if it starts to run better. +```lua +-- All methods that did not return before now return a copy of itself. Thus allowing chaining. Most if not all mutators returned nil, so chaining can now be done. I will eventually write up a full documentation of everything which will show this. +multi = require("multi") +multi:newStep(1,100):OnStep(function(self,i) + print("Index: "..i) +end):OnEnd(function(self) + print("Step is done!") +end) +multi:mainloop{ + priority = 3 +} +``` +Priority 3 works a bit differently than the other 2. + +P1 follows a forumla that resembles this: ~n=I*PRank where n is the amount of steps given to an object with PRank and where I is the idle time see chart below. The aim of this priority scheme was to make core objects run fastest while letting idle processes get decent time as well. +``` +C: 3322269 ~I*7 +H: 2847660 ~I*6 +A: 2373050 ~I*5 +N: 1898440 ~I*4 +B: 1423830 ~I*3 +L: 949220 ~I*2 +I: 474610 ~I +~n=I*PRank +``` +P2 follows a formula that resembles this: ~n=n*4 where n is the idle time, see chart below. The goal of this one was to make core process' higher while keeping idle process' low. +``` +C: 6700821 +H: 1675205 +A: 418801 +N: 104700 +B: 26175 +L: 6543 +I: 1635 +~n=n*4 +``` +P3 Ignores using a basic function and instead bases its processing time on the amount of cpu time is there. If cpu-time is low and a process is set at a lower priority it will get its time reduced. There is no formula, at idle almost all process work at the same speed! +``` +C: 2120906 +H: 2120906 +A: 2120906 +N: 2120906 +B: 2120906 +L: 2120906 +I: 2120506 +``` + +Auto Priority works by seeing what should be set high or low. Due to lua not having more persicion than milliseconds, I was unable to have a detailed manager that can set things to high, above normal, normal, ect. This has either high or low. If a process takes longer than .001 millisecond it will be set to low priority. You can change this by using the setting auto_lowest = multi.Priority_[PLevel] the defualt is low, not idle, since idle tends to get about 1 process each second though you can change it to idle using that setting. This is nolonger the case in version 16.0.0 multi has evolved ;) + +**Improved:** +- Performance at the base level has been doubled! On my machine benchmark went from ~9mil to ~20 mil steps/s. +Note: If you write slow code this library's improvements wont make much of a difference. +- Loops have been optimised as well! Being the most used objects I felt they needed to be made as fast as possible + +I usually give an example of the changes made, but this time I have an explantion for `multi.nextStep()`. It's not an entirely new feature since multi:newJob() does something like this, but is completely different. nextStep adds a function that is executed first on the next step. If multiple things are added to next step, then they will be executed in the order that they were added. + +Note: +The upper limit of this libraries performance on my machine is ~39mil. This is simply a while loop counting up from 0 and stops after 1 second. The 20mil that I am currently getting is probably as fast as it can get since its half of the max performance possible, and each layer I have noticed that it doubles complexity. Throughout the years with this library I have seen massive improvements in speed. In the beginning we had only ~2000 steps per second. Fast right? then after some tweaks we went to about 300000 steps per second, then 600000. Some more tweaks brought me to ~1mil steps per second, then to ~4 mil then ~9 mil and now finally ~20 mil... the doubling effect that i have now been seeing means that odds are I have reach the limit. I will aim to add more features and optimize individule objects. If its possible to make the library even faster then I will go for it. + + +# Update 12.1.0 - Threads just can't hold on anymore +Fixed: +--- +- bug causing arguments when spawning a new thread not going through + +Changed: +--- +- thread.hold() now returns the arguments that were pass by the event function +- event objects now contain a copy of what returns were made by the function that called it in a table called returns that exist inside of the object + +```lua +multi = require("multi") +local a = 0 +multi:newThread("test",function() + print("lets go") + b,c = thread.hold(function() -- This now returns what was managed here + return b,"We did it!" + end) + print(b,c) +end) +multi:newTLoop(function() + a=a+1 + if a == 5 then + b = "Hello" + end +end,1) +multi:mainloop() +``` +**Note:** Only if the first return is non-nil/false will any other returns be passed! So while variable b above is nil the string "We did it!" will not be passed. Also while this seems simple enough to get working, I had to modify a bit on how the scheduler worked to add such a simple feature. Quite a bit is going on behind the scenes which made this a bit tricky to implement, but not hard. Just needed a bit of tinkering. Plus event objects have not been edited since the creation of the EventManager. They have remained mostly the same since 2011 + +Going forward: +--- +Contunue to make small changes as I come about them. This change was inspired when working of the net library. I was addind simple binary file support over tcp, and needed to pass the data from the socket when the requested amount has been recieved. While upvalues did work, i felt returning data was cleaner and added this feature. + +# Update: 12.0.0 - Big update (Lots of additions some changes) + +**Note:** ~~After doing some testing, I have noticed that using multi-objects are slightly, quite a bit, faster than using (coroutines)multi:newthread(). Only create a thread if there is no other possibility! System threads are different and will improve performance if you know what you are doing. Using a (coroutine)thread as a loop with a +is slower than using a TLoop! If you do not need the holding features I strongly recommend that you use the multi-objects. This could be due to the scheduler that I am using, and I am looking into improving the performance of the scheduler for (coroutine)threads. This is still a work in progress so expect things to only get better as time passes!~~ This was the reason threadloop was added. It binds the thread scheduler into the mainloop allowing threads to run much faster than before. Also the use of locals is now possible since I am not dealing with seperate objects. And finally, reduced function overhead help keeps the threads running better. + +**Note:** The nodeManager is being reworked! This will take some time before it is in a stable state. The old version had some major issues that caused it to perform poorly. + +**Note:** Version names were brought back to reality this update. When transistioning from EventManager to multi I stopped counting when in reality it was simply an overhaul of the previous library + +Added: +--- +- `nGLOBAL = require("multi.integration.networkManager").init()` +- `node = multi:newNode(tbl: settings)` +- `master = multi:newMaster(tbl: settings)` +- `multi:nodeManager(port)` +- `thread.isThread()` — for coroutine based threads +- New setting to the main loop, stopOnError which defaults to true. This will cause the objects that crash, when under protect, to be destroyed. So the error does not keep happening. +- multi:threadloop(settings) works just like mainloop, but prioritizes (corutine based) threads. Regular multi-objects will still work. This improves the preformance of (coroutine based) threads greatly. +- multi.OnPreLoad — an event that is triggered right before the mainloop starts + +Changed: +- When a (corutine based)thread errors it does not print anymore! Conect to multi.OnError() to get errors when they happen! +- Connections get yet another update. Connect takes an additional argument now which is the position in the table that the func should be called. Note: Fire calls methods backwards so 1 is the back and the # of connections (the default value) is the beginning of the call table +- The love2d compat layer has now been revamped allowing module creators to connect to events without the user having to add likes of code for those events. Its all done automagically. +- This library is about 8 years old and using 2.0.0 makes it seem young. I changed it to 12.0.0 since it has some huge changes and there were indeed 12 major releases that added some cool things. Going forward I'll use major.minor.bugfix +- multi.OnError() is now required to capture errors that are thrown when in prorected mode. + +Node: +--- +- node:sendTo(name,data) +- node:pushTo(name,data) +- node:peek() +- node:pop() +- node:getConsole() — has only 1 function print which allows you to print to the master. + +Master: +--- +- master:doToAll(func) +- master:register(name,node,func) +- master:execute(name,node,...) +- master:newNetworkThread(tname,func,name,...) +- master:getFreeNode() +- master:getRandomNode() +- master:sendTo(name,data) +- master:pushTo(name,data) +- master:peek() +- master:pop() +- master:OnError(nodename, error) — if a node has an error this is triggered. + +Bugs +--- +- Fixed a small typo I made which caused a hard crash when a (coroutine) thread crashes. This only happened if protect was true. + +Going forward: +--- +- I am really excited to finally get this update out there, but left one important thing out. enabling of enviroments for each master connected to a node. This would allow a node to isolate code from multiple masters so they cannot interact with each other. This will come out in version 12.1.0 But might take a while due to the job hunt that I am currently going through. +- Another feature that I am on the fence about is adding channels. They would work like queues, but are named so you can seperate the data from different channels where only one portion of can see certain data. +- I also might add a feature that allows different system threads to consume from a network queue if they are spaned on the same physical machine. This is possible at the moment, just doesn't have a dedicated object for handling this seamlessly. You can do this yourself though. +- Another feature that I am thinking of adding is crosstalk which is a setting that would allow nodes to talk to other nodes. I did not add it in this release since there are some issues that need to be worked out and its very messy atm. however since nodes are named. I may allow by default pushing data to another node, but not have the global table to sync since this is where the issue lies. +- Improve Performance +- Fix supporting libraries (Bin, and net need tons of work) +- Look for the bugs +- Figure out what I can do to make this library more awesome + + +**Note On Queues:** When it comes to network queues, they only send 1 way. What I mean by that is that if the master sends a message to a node, its own queue will not get populated at all. The reason for this is because syncing between which popped from what network queue would make things really slow and would not perform well at all. This means you have to code a bit differently. Use: master getFreeNode() to get the name of the node under the least amount of load. Then handle the sending of data to each node that way. + +Now there is a little trick you can do. If you combine both networkmanager and systemthreading manager, then you could have a proxy queue for all system threads that can pull from that "node". Now data passing within a lan network, (And wan network if using the node manager, though p2p isn't working as i would like and you would need to open ports and make things work. Remember you can define an port for your node so you can port forward that if you want), is fast enough, but the waiting problem is something to consider. Ask yourseld what you are coding and if network paralisim is worth using. + +**Note:** These examples assume that you have already connected the nodes to the node manager. Also you do not need to use the node manager, but sometimes broadcast does not work as expected and the master doesnot connect to the nodes. Using the node manager offers nice features like: removing nodes from the master when they have disconnected, and automatically telling the master when nodes have been added. A more complete example showing connections regardless of order will be shown in the example folder check it out. New naming scheme too. + +**NodeManager.lua** +```lua +multi = require("multi") +local GLOBAL, THREAD = require("multi.integration.lanesManager").init() +nGLOBAL = require("multi.integration.networkManager").init() +multi:nodeManager(12345) -- Host a node manager on port: 12345 +print("Node Manager Running...") +settings = { + priority = 0, — 1 or 2 + protect = false, +} +multi:mainloop(settings) +-- Thats all you need to run the node manager, everything else is done automatically + +``` + +Side note: I had a setting called cross talk that would allow nodes to talk to each other. After some tought I decided to not allow nodes to talk to each other directly! You however can create another master withing the node. (The node will connect to its own master as well). This will give you the ability "Cross talk" with each node. Reimplementing the master features into each node directly was unnecessary. + +**Node.lua** +```lua +multi = require("multi") +local GLOBAL, THREAD = require("multi.integration.lanesManager").init() +nGLOBAL = require("multi.integration.networkManager").init() +master = multi:newNode{ + allowRemoteRegistering = true, -- allows you to register functions from the master on the node, default is false + name = nil, -- default value + noBroadCast = true, -- if using the node manager, set this to true to prevent the node from broadcasting + managerDetails = {"localhost",12345}, -- connects to the node manager if one exists +} +function RemoteTest(a,b,c) -- a function that we will be executing remotely + print("Yes I work!",a,b,c) +end +settings = { + priority = 0, -- 1 or 2 + protect = false, -- if something goes wrong we will crash hard, but the speed gain is good +} +multi:mainloop(settings) +``` + +**Master.lua** +```lua +-- Import the libraries +local multi = require("multi") +local GLOBAL, THREAD = require("multi.integration.lanesManager").init() +nGLOBAL = require("multi.integration.networkManager").init() +-- Act as a master node +master = multi:newMaster{ + name = "Main", -- the name of the master + noBroadCast = true, -- if using the node manager, set this to true to avoid double connections + managerDetails = {"localhost",12345}, -- the details to connect to the node manager (ip,port) +} +-- Send to all the nodes that are connected to the master +master:doToAll(function(node_name) + master:register("TestFunc",node_name,function(msg) + print("It works: "..msg) + end) + multi:newAlarm(2):OnRing(function(alarm) + master:execute("TestFunc",node_name,"Hello!") + alarm:Destroy() + end) + multi:newThread("Checker",function() + while true do + thread.sleep(1) + if nGLOBAL["test"] then + print(nGLOBAL["test"]) + thread.kill() + end + end + end) + nGLOBAL["test2"]={age=22} +end) + +-- Starting the multitasker +settings = { + priority = 0, -- 0, 1 or 2 + protect = false, +} +multi:mainloop(settings) +``` + +**Note:** There are many ways to work this. You could send functions/methods to a node like haw systemThreadedJobQueue work. Or you could write the methods you want in advance in each node file and send over the command to run the method with arguments ... and it will return the results. Network threading is different than system threading. Data transfer is really slow compared to system threading. In fact the main usage for this feature in the library is mearly for experments. Right now I honestly do not know what I want to do with this feature and what I am going to add to this feature. The ablitiy to use this frature like a system thread will be possible, but there are some things that differ. + +Changed: +--- +- multi:mainloop(settings) — now takes a table of settings +- multi:uManager(settings) — now takes a table of settings +- connections:holdUT(n) can take a number now. Where they will not continue until it gets triggered **n** times Added 3 updated ago, forgot to list it as a new feature +- The way you require the library has changed a bit! This will change how you start your code, but it isn't a big change. +- These changes have led to significant performance improvements + +Modifying the global stack is not the best way to manage or load in the library. +```lua +-- Base Library +multi = require("multi") +-- In Lanes +multi = require("multi") +local GLOBAL, THREAD = require("multi.integration.lanesManager").init() +-- In Love2d +multi = require("multi") +GLOBAL, THREAD = require("multi.integration.loveManager").init() +-- In Luvit +local timer = require("timer") +local thread = require("thread") +multi = require("multi") +require("multi.integration.luvitManager").init(thread,timer) -- Luvit does not cuttently have support for the global table or threads. +``` + +Improvements: +--- +- Updated the ThreadedConsole, now 100x faster! +- Updated the ThreadedConections, .5x faster! +- Both multi:uManager(settings) and multi:mainloop(settings) provide about the same performance! Though uManager is slightly slower due to function overhead, but still really close. +- Revamped pausing mulit-objects they now take up less memory when being used + +Removed: +--- +- require("multi.all") — We are going into a new version of the library so this is nolonger needed +- require("multi.compat.backwards[1,5,0]") — This is really old and is no longer supported going forward +- multi:Do_Order() +- multi:enablePriority() +- multi:enablePriority2() +- multi:protect() +- multi:unProtect() +- multi:protectedMainloop() +- multi:unprotectedMainloop() +- multi:prioritizedMainloop1() +- multi:prioritizedMainloop2() +- Removed Tasks +- multi:oneTime(func,...) — never seen use of this, plus multi-functions can do this by pausing the function after the first use, and is much faster anyway +- multi:reboot() — removed due to having no real use +- multi:hold() — removed due to threads being able to do the same thing and way better too +- multi:waitFor() — the thread variant does something completely different +- multi.resurrect() — removed due to being useless + +The new settings table makes all of these possible and removes a lot of function overhead that was going on before. + +```lua +multi:mainloop{ + priority = 1, -- 1 or 2 + protect = true, -- Should I use pcall to ignore errors? + preLoop = function(self) -- a function that is called before the mainloop does its thing + multi:newTLoop(function() + print("Hello whats up!") + error(":P") + end,1) + multi.OnError(function(obj,err) + print(err) + obj:Destroy() + end) + end, +} +``` +# Update: 1.11.1 - Small Clarification on Love +Love2d change: +I didn't fully understand how the new love.run() function worked. +So, it works by returning a function that allows updating the mainloop. So, this means that we can do something like this: + +```lua +multi:newLoop(love.run()) -- Run the mainloop here, cannot use thread.* when using this object + +-- or + +multi:newThread("MainLoop",love.run()) -- allows you to use the thread.* + +--And you'll need to throw this in at the end +multi:mainloop() +``` + +The priority management system should be quite useful with this change. +NOTE: **multiobj:hold() will be removed in the next version!** This is something I feel should be changed, since threads(coroutines) do the job great, and way better than my holding method that I throw together 5 years ago. I doubt this is being used by many anyway. Version 1.11.2 or version 2.0.0 will have this change. The next update may be either, bug fixes if any or network parallelism. + +TODO: Add auto priority adjustments when working with priority and stuff... If the system is under heavy load it will dial some things deemed as less important down and raise the core processes. + +# Update: 1.11.0 +Added: +- SystemThreadedConsole(name) — Allow each thread to print without the sync issues that make prints merge and hard to read. + +```lua +-- MainThread: +console = multi:newSystemThreadedConsole("console"):init() +-- Thread: +console = THREAD.waitFor("console"):init() + +-- using the console +console:print(...) +console:write(...) -- kind of useless for formatting code though. other threads can eaisly mess this up. +``` + +Fixed/Updated: +- Love2d 11.1 support is now here! Will now require these lines in your main.lua file + +```lua +function love.update(dt) + multi:uManager(dt) -- runs the main loop of the multitasking library +end +function love.draw() + multi.dManager() -- If using my guimanager, if not omit this +end +``` + + +# Update: 1.10.0 +**Note:** The library is now considered to be stable! +**Upcoming:** Network parallelism is on the way. It is in the works and should be released soon + +Added: +--- +- isMainThread true/nil +- multi:newSystemThreadedConnection(name,protect) — Works like normal connections, but are able to trigger events across threads + +Example of threaded connections +```lua +local GLOBAL,THREAD=require("multi.integration.lanesManager").init() +multi:newSystemThread("Test_Thread_1",function() + connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + connOut(function(arg) + print(THREAD.getName(),arg) + end) + multi:mainloop() +end) +multi:newSystemThread("Test_Thread_2",function() + connOut = THREAD.waitFor("ConnectionNAMEHERE"):init() + connOut(function(arg) + print(THREAD.getName(),arg) + end) + multi:mainloop() +end) +connOut = multi:newSystemThreadedConnection("ConnectionNAMEHERE"):init() +a=0 +connOut(function(arg) + print("Main",arg) +end) +multi:newTLoop(function() + a=a+1 + connOut:Fire("Test From Main Thread: "..a.."\n") +end,1) +``` + +Fixed: +--- +**loveManager** and **shared threading objects** +- sThread.waitFor() +- sThread.hold() +- some typos +- SystemThreadedTables (They now work on both lanes and love2d as expected) + +Example of threaded tables +```lua +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +multi:newSystemThread("Test_Thread_1",function() + require("multi") + test = sThread.waitFor("testthing"):init() + multi:newTLoop(function() + print("------") + for i,v in pairs(test.tab) do + print("T1",i,v) + end + end,1) + multi:mainloop() +end) +multi:newSystemThread("Test_Thread_1",function() + require("multi") + test = sThread.waitFor("testthing"):init() + multi:newTLoop(function() + print("------") + for i,v in pairs(test.tab) do + print("T2",i,v) + end + end,1) + multi:mainloop() +end) +test = multi:newSystemThreadedTable("testthing"):init() +multi:newTLoop(function() + local a,b = multi.randomString(8),multi.randomString(4) + print(">",a,b) + test[a]=b +end,1) +multi:mainloop() +``` + +# Update: 1.9.2 +Added: +--- +- (THREAD).kill() kills a thread. Note: THREAD is based on what you name it +- newTimeStamper() Part of the persistent systems... Useful for when you are running this library for a long amount of time... like months and years! Though daily, hourly, minute events do also exist. +Allows one to hook to timed events such as whenever the clock strikes midnight or when the day turns to Monday. The event is only done once though. so as soon as Monday is set it would trigger then not trigger again until next Monday +works for seconds, minutes, days, months, year. +```lua +stamper = multi:newTimeStamper() +stamper:OnTime(int hour,int minute,int second,func) or stamper:OnTime(string time,func) time as 00:00:00 +stamper:OnHour(int hour,func) +stamper:OnMinute(int minute,func) +stamper:OnSecond(int second,func) +stamper:OnDay(int day,func) or stamper:OnDay(string day,func) Mon, Tues, Wed, etc... +stamper:OnMonth(int month,func) +stamper:OnYear(int year,func) +``` +Improved: +--- +- LoadBalancing, well better load balancing than existed before. This one allowed for multiple processes to have their own load reading. Calling this on the multi object will return the total load for the entire multi environment... loads of other processes are indeed affected by what other processes are doing. However, if you combine propriety to the mix of things then you will get differing results... these results however will most likely be higher than normal... different priorities will have different default thresholds of performance. + +Fixed: +--- +- Thread.getName() should now work on lanes and love2d, haven't tested it much with the luvit side of things... +- A bug with the lovemanager table.remove arguments were backwards +- The queue object in the love2d threading has been fixed! It now supports sending all objects (even functions if no upvalues are present!) + +Changed: +--- +- SystemThreadedJobQueues now have built in load management so they are not constantly at 100% CPU usage. +- SystemThreadedJobQueues pushJob now returns an id of that job which will match the same one that OnJobCompleted returns + + +# Update: 1.9.1 - Threads can now argue +Added: +--- +- Integration "multi.integration.luvitManager" +- Limited... Only the basic multi:newSystemThread(...) will work +- Not even data passing will work other than arguments... If using the bin library, you can pass tables and function... Even full objects if inner recursion is not present. + +Updated: +--- +- multi:newSystemThread(name,func,...) +- It will now pass the ... to the func(). Do not know why this wasn't done in the first place +- Also multi:getPlatform(will now return "luvit" if using luvit... Though Idk if module creators would use the multi library when inside the luvit environment + +# Update: 1.9.0 +Added: +--- +- multiobj:ToString() — returns a string representing the object +- multi:newFromString(str) — creates an object from a string + +Works on threads and regular objects. Requires the latest bin library to work! +```lua +talarm=multi:newThreadedAlarm("AlarmTest",5) +talarm:OnRing(function() + print("Ring!") +end) +bin.new(talarm:ToString()):tofile("test.dat") +-- multi:newFromString(bin.load("test.dat")) +``` +A more seamless way to use this will be made in the form of state saving. +This is still a WIP +processes, timers, timemasters, watchers, and queuers have not been worked on yet + +# Update: 1.8.7 +Added: +--- +- multi.timer(func,...) + +```lua +function test(a,b,c) + print("Running...") + a=0 + for i=1,1000000000 do + a=a+1 + end + return a,b+c +end +print(multi.timer(test,1,2,3)) +print(multi.timer(test,1,2,3)) +-- multi.timer returns the time taken then the arguments from the function... Uses unpack so careful of nil values! +``` + +# Update: 1.8.6 +Added: +- jobQueue:doToAll(function) +- jobQueue:start() is now required Call this after all calls to registerJob()'s. Calling it afterwards will not guarantee your next push job with that job will work. Not calling this will make pushing jobs impossible! +- Fixed a bug with love2d Threaded Queue +- Fixed some bugs +- Old versions of this library! It stems back from 2012 see rambling for more info... + +This will run said function in every thread. +```lua +-- Going to use love2d code this time, almost the same as last time... See ramblings +require("core.Library") +GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library +require("core.GuiManager") +gui.ff.Color=Color.Black +jQueue=multi:newSystemThreadedJobQueue() +jQueue:registerJob("TEST_JOB",function(a,s) + math.randomseed(s) + TEST_JOB2() + return math.random(0,255) +end) +jQueue:registerJob("TEST_JOB2",function() + print("Test Works!") +end) +-- 1.8.6 EXAMPLE Change +jQueue:start() -- This is now needed! +-- +jQueue:doToAll(function() + print("Doing this 2? times!") +end) +tableOfOrder={} +jQueue.OnJobCompleted(function(JOBID,n) + tableOfOrder[JOBID]=n + if #tableOfOrder==10 then + t.text="We got all of the pieces!" + end +end) +for i=1,10 do -- Job Name of registered function, ... varargs + jQueue:pushJob("TEST_JOB","This is a test!",math.random(1,1000000)) +end +t=gui:newTextLabel("no done yet!",0,0,300,100) +t:centerX() +t:centerY() +``` + +# Update: 1.8.5 +Added: +--- +- SystemThreadedExecute(cmd) + +Allows the execution of system calls without hold up. It is possible to do the same using io.popen()! You decide which works best for you! +```lua +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +cmd=multi:newSystemThreadedExecute("SystemThreadedExecuteTest.lua") -- This file is important! +cmd.OnCMDFinished(function(code) -- callback function to grab the exit code... Called when the command goes through + print("Got Code: "..code) +end) +multi:newTLoop(function() + print("...") -- lets show that we aren't being held up +end,1) +multi:mainloop() +``` + +# Update: 1.8.4 +Added: +--- +- multi:newSystemThreadedJobQueue() +- Improved stability of the library +- Fixed a bug that made the benchmark and getload commands non-thread(coroutine) safe +- Tweaked the loveManager to help improve idle CPU usage +- Minor tweaks to the coroutine scheduling + +Using multi:newSystemThreadedJobQueue() +--- +First you need to create the object +This works the same way as love2d as it does with lanes... It is getting harder to make both work the same way with speed in mind... Anyway... +```lua +-- Creating the object using lanes manager to show case this. Examples has the file for love2d +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +jQueue=multi:newSystemThreadedJobQueue(n) -- this internally creates System threads. By default it will use the # of processors on your system You can set this number though. +-- Only create 1 jobqueue! For now, making more than 1 is not supported. You only really need one though. Just register new functions if you want 1 queue to do more. The one reason though is keeping track of jobIDs. I have an idea that I will roll out in the ~~next update~~ eventually. +jQueue:registerJob("TEST_JOB",function(a,s) + math.randomseed(s) + -- We will push a random # + TEST_JOB2() -- You can call other registered functions as well! + return math.random(0,255) -- send the result to the main thread +end) +jQueue:registerJob("TEST_JOB2",function() + print("Test Works!") -- this is called from the job since it is registered on the same queue +end) +tableOfOrder={} -- This is how we will keep order of our completed jobs. There is no guarantee that the order will be correct +jQueue.OnJobCompleted(function(JOBID,n) -- whenever a job is completed you hook to the event that is called. This passes the JOBID filled by the returns of the job + -- JOBID is the completed job, starts at 1 and counts up by 1. + -- Threads finish at different times so jobIDs may be passed out of order! Be sure to have a way to order them + tableOfOrder[JOBID]=n -- we order ours by putting them into a table + if #tableOfOrder==10 then + print("We got all of the pieces!") + end +end) +-- Lets push the jobs now +for i=1,10 do -- Job Name of registered function, ... varargs + jQueue:pushJob("TEST_JOB","This is a test!",math.random(1,1000000)) +end +print("I pushed all of the jobs :)") +multi:mainloop() -- Start the main loop :D +``` + +That’s it from this version! + +# Update: 1.8.3 - Mainloop recieves some needed overhauling +Added: +--- +**New Mainloop functions** Below you can see the slight differences... Function overhead is not too bad in lua but has a real difference. multi:mainloop() and multi:unprotectedMainloop() use the same algorithm yet the dedicated unprotected one is slightly faster due to having less function overhead. +- multi:mainloop()\* — Bench: 16830003 Steps in 3 second(s)! +- multi:protectedMainloop() — Bench: 16699308 Steps in 3 second(s)! +- multi:unprotectedMainloop() — Bench: 16976627 Steps in 3 second(s)! +- multi:prioritizedMainloop1() — Bench: 15007133 Steps in 3 second(s)! +- multi:prioritizedMainloop2() — Bench: 15526248 Steps in 3 second(s)! + +\* The OG mainloop function remains the same and old methods to achieve what we have with the new ones still exist + +These new methods help by removing function overhead that is caused through the original mainloop function. The one downside is that you no longer have the flexibility to change the processing during runtime. + +However there is a work around! You can use processes to run multiobjs as well and use the other methods on them. + +I may make a full comparison between each method and which is faster, but for now trust that the dedicated ones with less function overhead are infect faster. Not by much but still faster. + +# Update: 1.8.2 +Added: +--- +- multi:newsystemThreadedTable(name) NOTE: Metatables are not supported in transfers. However there is a work around obj:init() does this. Look in the multi/integration/shared/shared.lua files to see how I did it! +- Modified the GLOBAL metatable to sync before doing its tests +- multi._VERSION was multi.Version, felt it would be more consistent this way... I left the old way of getting the version just in case someone has used that way. It will eventually be gone. Also multi:getVersion() will do the job just as well and keep your code nice and update related bug free! +- Also everything that is included in the: multi/integration/shared/shared.lua (Which is loaded automatically) works in both lanes and love2d environments! + +The threaded table is setup just like the threaded queue.
+It provids GLOBAL like features without having to write to GLOBAL!
+This is useful for module creators who want to keep their data private, but also use GLOBAL like coding.
+It has a few features that makes it a bit better than plain ol GLOBAL (For now...) +(ThreadedTable - TT for short) This was modified by a recent version that removed the need for a sync command +- TT:waitFor(name) +- TT:sync() +- TT["var"]=value +- print(TT["var"]) + +we also have the "sync" method, this one was made for love2d because we do a syncing trick to get data in a table format. The lanes side has a sync method as well so no worries. Using indexing calls sync once and may grab your variable. This allows you to have the lanes indexing 'like' syntax when doing regular indexing in love2d side of the module. As of right now both sides work flawlessly! And this effect is now the GLOBAL as well
+ +On GLOBALS sync is a internal method for keeping the GLOBAL table in order. You can still use sThread.waitFor(name) to wait for variables that may or may not yet exist! + +Time for some examples: +Using multi:newSystemThreadedTable(name) +--- +```lua +-- lanes Desktop lua! NOTE: this is in lanesintergratetest6.lua in the examples folder +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +test=multi:newSystemThreadedTable("YO"):init() +test["test1"]="lol" +multi:newSystemThread("test",function() + tab=sThread.waitFor("YO"):init() + print(tab:has("test1")) + sThread.sleep(3) + tab["test2"]="Whats so funny?" +end) +multi:newThread("test2",function() + print(test:waitFor("test2")) +end) +multi:mainloop() +``` + +```lua +-- love2d lua! NOTE: this is in main4.lua in the love2d examples +require("core.Library") +GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library +require("core.GuiManager") +gui.ff.Color=Color.Black +test=multi:newSystemThreadedTable("YO"):init() +test["test1"]="lol" +multi:newSystemThread("test",function() + tab=sThread.waitFor("YO"):init() + print(tab["test1"]) + sThread.sleep(3) + tab["test2"]="Whats so funny?" +end) +multi:newThread("test2",function() + print(test:waitFor("test2")) + t.text="DONE!" +end) +t=gui:newTextLabel("not done yet!",0,0,300,100) +t:centerX() +t:centerY() +``` + +# Update: 1.8.1 +No real change!
+Changed the structure of the library. Combined the coroutine based threads into the core!
+Only compat and integrations are not part of the core and never will be by nature.
+This should make the library more convent to use.
+I left multi/all.lua file so if anyone had libraries/projects that used that it will still work!
+Updated from 1.7.6 to 1.8.0
(How much thread could a thread htread if a thread could thread thread?) +Added:
+- multi:newSystemThreadedQueue() +- multi:systemThreadedBenchmark() +- More example files +- multi:canSystemThread() — true if an integration was added false otherwise (For module creation) +- Fixed a few bugs in the loveManager + +Using multi:systemThreadedBenchmark() +--- +```lua +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +multi:systemThreadedBenchmark(3):OnBench(function(self,count) + print("First Bench: "..count) + multi:systemThreadedBenchmark(3,"All Threads: ") +end) +multi:mainloop() +``` + +Using multi:newSystemThreadedQueue() +--- +Quick Note: queues shared across multiple objects will be pulling from the same "queue" keep this in mind when coding! ~~Also the queue respects direction a push on the thread side cannot be popped on the thread side... Same goes for the mainthread!
~~ Turns out I was wrong about this... +```lua +-- in love2d, this file will be in the same example folder as before, but is named main2.lua +require("core.Library") +GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager and requires the entire multi library +--IMPORTANT +-- Do not make the above local, this is the one difference that the lanesManager does not have +-- If these are local the functions will have the upvalues put into them that do not exist on the threaded side +-- You will need to ensure that the function does not refer to any upvalues in its code. It will print an error if it does though +-- Also, each thread has a .1 second delay! This is used to generate a random value for each thread! +require("core.GuiManager") +gui.ff.Color=Color.Black +queue=multi:newSystemThreadedQueue("QUEUE"):init() +queue:push("This is a test") +queue:push("This is a test2") +queue:push("This is a test3") +queue:push("This is a test4") +multi:newSystemThread("test2",function() + queue=sThread.waitFor("QUEUE"):init() + data=queue:pop() + while data do + print(data) + data=queue:pop() + end + queue:push("DONE!") +end) +multi:newThread("test!",function() + thread.hold(function() return queue:pop() end) + t.text="Done!" +end) +t=gui:newTextLabel("no done yet!",0,0,300,100) +t:centerX() +t:centerY() +``` +In Lanes +--- +```lua +-- The code is compatible with each other, I just wanted to show different things you can do in both examples +-- This file can be found in the examples folder as lanesintegrationtest4.lua +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +queue=multi:newSystemThreadedQueue("QUEUE"):init() +queue:push("This is a test") +queue:push("This is a test2") +queue:push("This is a test3") +queue:push("This is a test4") +multi:newSystemThread("test2",function() + queue=sThread.waitFor("QUEUE"):init() + data=queue:pop() + while data do + print(data) + data=queue:pop() + end + queue:push("This is a test5") + queue:push("This is a test6") + queue:push("This is a test7") + queue:push("This is a test8") +end) +multi:newThread("test!",function() -- this is a lua thread + thread.sleep(.1) + data=queue:pop() + while data do + print(data) + data=queue:pop() + end +end) +multi:mainloop() +``` + +# Update: 1.7.6 +Fixed: +--- +Typos like always +Added: +--- +- multi:getPlatform() — returns "love2d" if using the love2d platform or returns "lanes" if using lanes for threading +- examples files +- In Events added method setTask(func) --The old way still works and is more convent to be honest, but I felt a method to do this was needed for completeness. + +Improved: +--- +- Some example files to reflect changes to the core. Changes allow for less typing
+loveManager to require the compat if used so you don't need 2 require line to retrieve the library
+ +# Update: 1.7.5 +Fixed some typos in the readme... (I am sure there are more there are always more)
+Added more features for module support
+TODO:
+Work on performance of the library... I see 3 places where I can make this thing run quicker
+ +I'll show case some old versions of the multitasking library eventually so you can see its changes in days past!
+ +# Update: 1.7.4 +Added: the example folder which will be populated with more examples in the near future!
+The loveManager integration that mimics the lanesManager integration almost exactly to keep coding in both environments as close to possible. This is done mostly for library creation support!
+An example of the loveManager in action using almost the same code as the lanesintergreationtest2.lua
+NOTE: This code has only been tested to work on love2d version 1.10.2 though it should work version 0.9.0 +```lua +require("core.Library") -- Didn't add this to a repo yet! Will do eventually... Allows for injections and other cool things +require("multi.compat.love2d") -- allows for multitasking and binds my libraries to the love2d engine that i am using +GLOBAL,sThread=require("multi.integration.loveManager").init() -- load the love2d version of the lanesManager +--IMPORTANT +-- Do not make the above local, this is the one difference that the lanesManager does not have +-- If these are local the functions will have the upvalues put into them that do not exist on the threaded side +-- You will need to ensure that the function does not refer to any upvalues in its code. It will print an error if it does though +-- Also each thread has a .1 second delay! This is used to generate a random values for each thread! +require("core.GuiManager") -- allows the use of graphics in the program. +gui.ff.Color=Color.Black +function comma_value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then + break + end + end + return formatted +end +multi:newSystemThread("test1",function() -- Another difference is that the multi library is already loaded in the threaded enviroment as well as a call to multi:mainloop() + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 1"):OnBench(function(self,c) GLOBAL["T1"]=c multi:Stop() end) +end) +multi:newSystemThread("test2",function() -- spawns a thread in another lua process + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 2"):OnBench(function(self,c) GLOBAL["T2"]=c multi:Stop() end) +end) +multi:newSystemThread("test3",function() -- spawns a thread in another lua process + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 3"):OnBench(function(self,c) GLOBAL["T3"]=c multi:Stop() end) +end) +multi:newSystemThread("test4",function() -- spawns a thread in another lua process + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 4"):OnBench(function(self,c) GLOBAL["T4"]=c multi:Stop() end) +end) +multi:newSystemThread("test5",function() -- spawns a thread in another lua process + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 5"):OnBench(function(self,c) GLOBAL["T5"]=c multi:Stop() end) +end) +multi:newSystemThread("test6",function() -- spawns a thread in another lua process + multi:benchMark(sThread.waitFor("Bench"),nil,"Thread 6"):OnBench(function(self,c) GLOBAL["T6"]=c multi:Stop() end) +end) +multi:newSystemThread("Combiner",function() -- spawns a thread in another lua process + function comma_value(amount) + local formatted = amount + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then + break + end + end + return formatted + end + local b=comma_value(tostring(sThread.waitFor("T1")+sThread.waitFor("T2")+sThread.waitFor("T3")+sThread.waitFor("T4")+sThread.waitFor("T5")+sThread.waitFor("T6"))) + GLOBAL["DONE"]=b +end) +multi:newThread("test0",function() + -- sThread.waitFor("DONE") -- lets hold the main thread completely so we don't eat up cpu + -- os.exit() + -- when the main thread is holding there is a chance that error handling on the system threads may not work! + -- instead we can do this + while true do + thread.skip(1) -- allow error handling to take place... Otherwise let’s keep the main thread running on the low + -- Before we held just because we could... But this is a game and we need to have logic continue + --sThreadM.sleep(.001) -- Sleeping for .001 is a great way to keep cpu usage down. Make sure if you aren't doing work to rest. Abuse the hell out of GLOBAL if you need to :P + if GLOBAL["DONE"] then + t.text="Bench: "..GLOBAL["DONE"] + end + end +end) +GLOBAL["Bench"]=3 +t=gui:newTextLabel("no done yet!",0,0,300,100) +t:centerX() +t:centerY() +``` + +# Update: 1.7.3 +Changed how requiring the library works! +`require("multi.all")` Will still work as expected; however, with the exception of threading, compat, and integrations everything else has been moved into the core of the library. +```lua +-- This means that these are no longer required and will cause an error if done so +require("multi.loop") +require("multi.alarm") +require("multi.updater") +require("multi.tloop") +require("multi.watcher") +require("multi.tstep") +require("multi.step") +require("multi.task") +-- ^ they are all part of the core now +``` + +# Update: 1.7.2 +Moved updaters, loops, and alarms into the init.lua file. I consider them core features and they are referenced in the init.lua file so they need to exist there. Threaded versions are still separate though. Added another example file + +# Update: 1.7.1 - Bug Fixes Only +¯\\_(ツ)_/¯ +--- +# Update: 1.7.0 - Threading the systems +Modified: multi.integration.lanesManager.lua +It is now in a stable and simple state works with the latest lanes version! Tested with version 3.11 I cannot promise that everything will work with earlier versions. Future versions are good though.
+Example Usage:
+sThread is a handle to a global interface for system threads to interact with themselves
+thread is the interface for multithreads as seen in the threading section
+ +GLOBAL a table that can be used throughout each and every thread + +sThreads have a few methods
+sThread.set(name,val) — you can use the GLOBAL table instead modifies the same table anyway
+sThread.get(name) — you can use the GLOBAL table instead modifies the same table anyway
+sThread.waitFor(name) — waits until a value exists, if it does it returns it
+sThread.getCores() — returns the number of cores on your cpu
+sThread.sleep(n) — sleeps for a bit stopping the entire thread from running
+sThread.hold(n) — sleeps until a condition is met
+```lua +local GLOBAL,sThread=require("multi.integration.lanesManager").init() +require("multi.all") +multi:newAlarm(2):OnRing(function(self) + GLOBAL["NumOfCores"]=sThread.getCores() +end) +multi:newAlarm(7):OnRing(function(self) + GLOBAL["AnotherTest"]=true +end) +multi:newAlarm(13):OnRing(function(self) + GLOBAL["FinalTest"]=true +end) +multi:newSystemThread("test",function() -- spawns a thread in another lua process + require("multi.all") -- now you can do all of your coding with the multi library! You could even spawn more threads from here with the integration. You would need to require the interaction again though + print("Waiting for variable: NumOfCores") + print("Got it: ",sThread.waitFor("NumOfCores")) + sThread.hold(function() + return GLOBAL["AnotherTest"] -- note this would hold the entire systemthread. Spawn a coroutine thread using multi:newThread() or multi:newThreaded... + end) + print("Holding works!") + multi:newThread("tests",function() + thread.hold(function() + return GLOBAL["FinalTest"] -- note this will not hold the entire systemthread. As seen with the TLoop constantly going! + end) + print("Final test works!") + os.exit() + end) + local a=0 + multi:newTLoop(function() + a=a+1 + print(a) + end,.5) + multi:mainloop() +end) +multi:mainloop() +``` + +# Update: 1.6.0 +Changed: +--- +- steps +- loops +```lua +-- Was +step:OnStep(function(pos,self) -- same goes for tsteps as well + print(pos) +end) +multi:newLoop(function(dt,self) + print(dt) +end) +-- Is now +step:OnStep(function(self,pos) -- same goes for tsteps as wellc + print(pos) +end) +multi:newLoop(function(self,dt) + print(dt) +end) +``` +Reasoning I wanted to keep objects consistent, but a lot of my older libraries use the old way of doing things. Therefore, I added a backwards module + +**Note from the future:** That module has been canned. To be honest most features this low in the changelog are outdated and probably do not work. +```lua +require("multi.all") +require("multi.compat.backwards[1,5,0]") -- allows for the use of features that were scrapped/changed in 1.6.0+ +``` +# Update: 1.5.0 +Added: +--- +- An easy way to manage timeouts +- Small bug fixes + +# Update: 1.4.1 (4/10/2017) - First Public release of the library +Added: +- SystemThreading very infancy + +Change: +--- +- small change to the hold method to make it a bit more lightweight + - Using a timer instead of an alarm object! +- Limits to hold: + - cannot hold more than 1 object at a time, and doing so could cause a deadlock! + +**Note:** Wow you looked back this far. Nice, while your at it take a look at the old versions to view the code how it was before my first initial release + +Upcomming: +--- +- Threaded objects wrapped in coroutines, so you can hold/sleep without problems! + +# Update: 1.4.0 (3/20/2017) +Added: +--- +- multiobj:reallocate(ProcessObj) — changes the parent process of an object +- ProcessObj:getController() — returns the mThread so you can opperate on it like a multiobj +Example 1 +--- +```lua +require("multimanager") -- require the library +int1=multi:newProcess() -- create a process +int1.NAME="int1" -- give it a name for example purposes +int2=multi:newProcess() -- create another process to reallocate +int2.NAME="int2" -- name this a different name +step=int1:newTStep(1,10) -- create a TStep so we can slowly see what is going on +step:OnStep(function(p,s) -- connect to the onstep event + print(p,s.Parent.NAME) -- print the position and process name +end) +step:OnEnd(function(s) -- when the step ends lets reallocate it to the other process + if s.Parent.NAME=="int1" then -- lets only do this if it is in the int1 process + s:reallocate(int2) -- send it to int2 + s:Reset() -- reset the object + else + print("We are done!") + os.exit() -- end the program when int2 did its thing + end +end) +int1:Start() -- start process 1 +int2:Start() -- start process 2 +multi:mainloop() -- start the main loop +``` +Fixed/Updated: +--- +- queuer=multi:newQueuer([string: file]) +- Alarms now preform as they should on a queuer +Example 2 +--- +```lua +int=multi:newQueuer() +step=int:newTStep(1,10,1,.5) +alarm=int:newAlarm(2) +step2=int:newTStep(1,5,1,.5) +step:OnStep(function(p,s) + print(p) +end) +step2:OnStep(function(p,s) + print(p,"!") +end) +alarm:OnRing(function(a) + print("Ring1!!!") +end) +int:OnQueueCompleted(function(s) + s:Pause() + print("Done!") + os.exit() +end) +int:Start() +multi:mainloop() +``` + +# Update: 1.3.0 (1/29/2017) +Added: +--- +- Load detection! + - multi.threshold — minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 + - multi.threstimed — amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 + - multi:setThreshold(n) — method used to set multi.threshold + - multi:setThrestimed(n) — method used to set multi.threstimed + - multi:getLoad() — returns a number between 0 and 100 + +# Update: 1.2.0 (12.31.2016) +Added: +--- +- connectionobj.getConnection(name) — returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) if you can organize data before hand you can route info to certain connections thus saving a lot of cpu time. + +**NOTE:** Only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + +Changed: +--- +- Started keeping track of dates +- obj=multi:newConnection() + - obj:connect(func,name) and obj(func,name) + - Added the name argument to allow indexing specific connection objects... Useful when creating an async library + +# Update: 1.1.0 +Changed: +--- +- multi:newConnection(protect) — Changed the way you are able to interact with it by adding the __call metamethod +Old Usage: +```lua +OnUpdate=multi:newConnection() +OnUpdate:connect(function(...) + print("Updating",...) +end) +OnUpdate:Fire(1,2,3) +``` +New Usage: +```lua +OnUpdate=multi:newConnection() +OnUpdate(function(...) + print("Updating",...) +end) +OnUpdate:Fire(1,2,3) +``` + +# Update: 1.0.0 +Added: +--- +- Start of official changelog (internal) + +# Update: 0.6.3 +**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work + +# Update: 0.6.2 +**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work + +# Update: 0.6.1-6 +**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work + +# Update: 0.5.1-6 +**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work + +# Update: 0.4.1 +**Note:** No official changelog was made for versions this old. Doing code comparsions is way too much work + +# Update: 0.3.0 - The update that started it all +Changed: +--- +- Renamed the library to multi from EventManager and a ton of resturcturing +- Started the version numbering over + +# Update: EventManager 2.0.0 +Changed: +--- +- Everything, complete restructuring of the library from function based to object based. Resembles the modern version of the library + +Added: +--- +- Love2d support basic +- event:benchMark() +- event:lManager() — like uManager, but for love2d +- event:uManager(dt) — you could pass the change in time to the method +- event:cManager() +- event:manager() — old version of mainloop() +- @onClose(func) — modifidable +- event:onDraw(func) +- event:onUpdate(func) +- @event:onStart() — modifidable +- event:stop() +- event:createTrigger(func) +- event:createTStep(start,reset,timer,count) +- event:createStep(start,reset,count,skip) +- event:createLoop() +- event:newTask(func) +- event:newAlarm(set,func,start) +- event:hold(task) +- event:oneETime(func) — One time function call +- event:oneTime(func) +# Update: EventManager 1.2.0 +Changed: +--- +- Made Alarms have their own load order, before alarms were grouped with events +- events started becoming more object like +- event:setAlarm => event:newAlarm() returns alarm object +Note: Weird period where both fuction based and object based features worked! + +Added: +--- +- event:Hold(task) +- event:destroyEvent(tag) — destroys an event + +# Update: EventManager 1.1.0 +Added: +--- +- LoadOrder — allow you to change what was processed first events, steps, or updates +- event:setLoadOrder(str) — ESU,EUS,USE,UES,SEU,SUE +- event:createTStep(tag,reset,timer,endc) — works like steps but sleeps between loops +# Update: EventManager 1.0.0 - Error checking +Changed: +--- +- Added error checking which would alert you when something was done wrong + +# Version: EventManager 0.0.1 - In The Beginning things were very different +Usage: +--- +```lua +require("BasicCommands") +require("EventManager") +event:setAlarm("test",1) +a=0 +b=0 +function Alarm_test(tag) + print("Alarm: "..tag) + a = 1 + b=b+1 + event:updateAlarm("test",1) +end +event:setEvent("test()","a == 1") +event:setEvent("test2()","b == 5") +function Event_test() + print("Event! A=1 which means the alarm rang") + a = 0 +end +function Event_test2() + event:Stop() +end +-- this might feel somewhat familiar +step = event:createStep("test",10,true) +function Step_test(pos) + print(pos,self) +end +function Step_test_End(tag) + step:Remove() +end +function event:OnCreate() + print(event:stepExist("test")) + print(event:eventExist("test")) + print(event:alarmExist("test")) +end +function event:OnUpdate() + -- Called every cycle +end +function event:OnClose() + print("Manager was stopped!") +end +event:Manager() +--event:CManager() +--event:UManager() -- One of the few things that lived in name and spirit the u just became lowercase haha ``` \ No newline at end of file diff --git a/init.lua b/init.lua index f92111e..b6f322e 100644 --- a/init.lua +++ b/init.lua @@ -1,2856 +1,2857 @@ ---[[ -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 = {} -local mainloopActive = false -local isRunning = false -local clock = os.clock -local thread = {} -local processes = {} -local find_optimization = false -local threadManager -local __CurrentConnectionThread - -multi.unpack = table.unpack or unpack -multi.pack = table.pack or function(...) return {...} end - -if table.unpack then unpack = table.unpack end - --- Types - -multi.DestroyedObj = { - Type = "DESTROYED", -} - -local function uni() - return multi.DestroyedObj -end - -local function uniN() end -function multi.setType(obj,t) - if t == multi.DestroyedObj then - for i,v in pairs(obj) do - obj[i] = nil - end - setmetatable(obj, { - __index = function(t,k) - return setmetatable({},{__index = uni,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni}) - end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni - }) - end -end - -setmetatable(multi.DestroyedObj, { - __index = function(t,k) - return setmetatable({},{__index = uni,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni}) - end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni -}) - -multi.DESTROYED = multi.DestroyedObj - --- I don't like modifying the global namespace, so I prepend a "$" -if not _G["$multi"] then - _G["$multi"] = {multi = multi, thread = thread} -end - -local types = {} -function multi.registerType(typ, p) - if multi["$"..typ:upper():gsub("_","")] then return typ end - multi["$"..typ:upper():gsub("_","")] = typ - table.insert(types, {typ, p or typ}) - return typ -end - -function multi.hasType(typ) - if multi["$"..typ:upper():gsub("_","")] then - return multi["$"..typ:upper():gsub("_","")] - end -end - -function multi.getTypes() - return types -end - -multi.Version = "16.3.0" -multi.Name = "root" -multi.NIL = {Type="NIL"} -local NIL = multi.NIL -multi.Mainloop = {} -multi.Children = {} -multi.Active = true -multi.Type = multi.registerType("rootprocess") -multi.LinkedPath = multi -multi.TIMEOUT = multi.registerType("TIMEOUT", "timeouts") -multi.TID = 0 -multi.defaultSettings = {} - -multi.Priority_Core = 1 -multi.Priority_Very_High = 4 -multi.Priority_High = 16 -multi.Priority_Above_Normal = 64 -multi.Priority_Normal = 256 -multi.Priority_Below_Normal = 1024 -multi.Priority_Low = 4096 -multi.Priority_Very_Low = 16384 -multi.Priority_Idle = 65536 - -multi.PriorityResolve = { - [1] = "Core", - [4] = "Very High", - [16] = "High", - [64] = "Above Normal", - [256] = "Normal", - [1024] = "Below Normal", - [4096] = "Low", - [16384] = "Very Low", - [65536] = "Idle", -} - -local PList = {multi.Priority_Core,multi.Priority_Very_High,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Very_Low,multi.Priority_Idle} -multi.PriorityTick=1 -multi.Priority=multi.Priority_High -multi.threshold=256 -multi.threstimed=.001 - --- System -function multi.Stop() - isRunning = false - mainloopActive = false -end - -local pack = multi.pack - ---Processor -local priorityTable = {[false]="Disabled",[true]="Enabled"} -local ProcessName = {"SubProcessor","MainProcessor"} -local globalThreads = {} - -function multi:getProcessors() - return processes -end - -function multi:isType(type) - return self.Type == type -end - -function multi:getStats() - local stats = { - [multi.Name] = { - threads = multi:getThreads(), - tasks = multi:getTasks() - } - } - local procs = multi:getProcessors() - for i = 1, #procs do - local proc = procs[i] - stats[proc:getFullName()] = { - threads = proc:getThreads(), - tasks = proc:getTasks() - } - end - return stats -end - ---Helpers - -function multi.setClock(c) - clock = c -end - -function multi.ForEach(tab,func) - for i=1,#tab do func(tab[i]) end -end - -function multi.randomString(n) - local str = '' - local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} - for i=1,n do - str = str..''..strings[math.random(1,#strings)] - end - return str -end - -function multi.isMulitObj(obj) - if type(obj)=="table" then - if obj.Type ~= nil then - return multi.hasType(obj.Type) ~= nil - end - end - return false -end - -function multi.forwardConnection(src, dest) - if multi.isMulitObj(src) and multi.isMulitObj(dest) then - src(function(...) - dest:Fire(...) - end) - else - multi.error("Cannot forward non-connection objects") - end -end - -local optimization_stats = {} -local ignoreconn = true -local empty_func = function() end - -function multi:newConnection(protect,func,kill) - local processor = self - local c={} - local lock = false - local fast = {} - c.__connectionAdded = function() end - c.rawadd = false - c.Parent = self - - setmetatable(c,{ - __call=function(self,...) - local t = ... - if type(t)=="table" then - for i,v in pairs(t) do - if v==self then - local ref = self:Connect(select(2,...)) - if ref then - ref.root_link = select(1,...) - return ref - end - return self - end - end - return self:Connect(...) - else - return self:Connect(...) - end - end, - __unm = function(obj) -- -obj Reverses the order of connected events - local conns = obj:Bind({}) - for i = #conns, 1, -1 do - obj.rawadd = true - obj(conns[i]) - obj.rawadd = false - end - return obj - end, - __mod = function(obj1, obj2) -- % - local cn = self:newConnection() - if type(obj1) == "function" and type(obj2) == "table" then - obj2(function(...) - cn:Fire(obj1(...)) - end) - elseif type(obj1) == "table" and type(obj2) == "function" then - local conns = obj1:Bind({}) - for i = 1,#conns do - obj1(function(...) - conns[i](obj2(...)) - end) - end - obj1.__connectionAdded = function(conn, func) - obj1:Unconnect(conn) - obj1.rawadd = true - obj1:Connect(function(...) - func(obj2(...)) - end) - obj1.rawadd = false - end - return obj1 - else - error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") - end - return cn - end, - __div = function(obj1, obj2) -- / - local cn = self:newConnection() - local ref - if type(obj1) == "function" and type(obj2) == "table" then - obj2(function(...) - local args = {obj1(...)} - if args[1] then - cn:Fire(multi.unpack(args)) - end - end) - else - multi.error("Invalid divide!", type(obj1), type(obj2),"Expected function/connection(table)") - end - return cn - end, - __concat = function(obj1, obj2) -- .. - local cn = self:newConnection() - local ref - if type(obj1) == "function" and type(obj2) == "table" then - cn(function(...) - if obj1(...) == true then - obj2:Fire(...) - end - end) - cn.__connectionAdded = function(conn, func) - cn:Unconnect(conn) - obj2:Connect(func) - end - elseif type(obj1) == "table" and type(obj2) == "function" then - ref = cn(function(...) - obj1:Fire(...) - obj2(...) - end) - cn.__connectionAdded = function() - cn.rawadd = true - cn:Unconnect(ref) - ref = cn(function(...) - if obj2(...) then - obj1:Fire(...) - end - end) - end - return cn - elseif type(obj1) == "table" and type(obj2) == "table" then - -- - else - error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") - end - return cn - end, - __add = function(c1,c2) -- Or - local cn = self:newConnection() - c1(function(...) - cn:Fire(...) - end) - c2(function(...) - cn:Fire(...) - end) - return cn - end, - __mul = function(c1,c2) -- And - local cn = self:newConnection() - local ref1, ref2 - if c1.__hasInstances == nil then - cn.__hasInstances = {2} - cn.__count = {0} - else - cn.__hasInstances = c1.__hasInstances - cn.__hasInstances[1] = cn.__hasInstances[1] + 1 - cn.__count = c1.__count - end - - ref1 = c1(function(...) - cn.__count[1] = cn.__count[1] + 1 - c1:Lock(ref1) - if cn.__count[1] == cn.__hasInstances[1] then - cn:Fire(...) - cn.__count[1] = 0 - c1:Unlock(ref1) - c2:Unlock(ref2) - end - end) - - ref2 = c2(function(...) - cn.__count[1] = cn.__count[1] + 1 - c2:Lock(ref2) - if cn.__count[1] == cn.__hasInstances[1] then - cn:Fire(...) - cn.__count[1] = 0 - end - end) - return cn - end}) - - c.Type=multi.registerType("connector", "connections") - c.func={} - c.ID=0 - local protect=protect or false - local connections={} - c.FC=0 - - function c:hasConnections() - return #fast~=0 - end - - function c:Lock(conn) - if conn and not conn.lock then - conn.lock = function() end - for i = 1, #fast do - if fast[conn.ref] == fast[i] then - fast[i] = conn.lock - return self - end - end - return self - end - lock = true - return self - end - - function c:Unlock(conn) - if conn and conn.lock then - for i = 1, #fast do - if conn.lock == fast[i] then - fast[i] = fast[conn.ref] - return self - end - end - return self - end - lock = false - return self - end - - if protect then - function c:Fire(...) - if lock then return end - local kills = {} - for i=1,#fast do - local suc, err = pcall(fast[i], ...) - if not suc then - multi.error(err) - end - if kill then - table.insert(kills,i) - processor:newTask(function() - for _, k in pairs(kills) do - table.remove(kills, _) - table.remove(fast, k) - end - end) - end - end - end - end - - function c:getConnections() - return fast - end - - function c:getConnection(name, ignore) - return fast[name] or function() multi:warning("") end - end - - function c:Unconnect(conn) - for i = 1, #fast do - if fast[conn.ref] == fast[i] then - table.remove(self) - return table.remove(fast, i), i - end - end - end - - function c:fastMode() return self end - - if kill then - local kills = {} - function c:Fire(...) - if lock then return end - for i=1,#fast do - fast[i](...) - if kill then - table.insert(kills,i) - processor:newTask(function() - for _, k in pairs(kills) do - table.remove(kills, _) - table.remove(fast, k) - end - end) - end - end - end - else - function c:Fire(...) - if lock then return end - for i=1,#fast do - fast[i](...) - end - end - end - - function c:Connect(func, name) - local th - if thread.getRunningThread then - th = thread.getRunningThread() - end - if th then - local fref = func - func = function(...) - __CurrentConnectionThread = th - fref(...) - __CurrentConnectionThread = nil - end - end - table.insert(fast, func) - if name then - fast[name] = func - else - fast["Conn_"..multi.randomString(12)] = func - end - local temp = {fast = true} - setmetatable(temp,{ - __call=function(s,...) - return self:Connect(...) - end, - __index = function(t,k) - if rawget(t,"root_link") then - return t["root_link"][k] - end - return nil - end, - __newindex = function(t,k,v) - if rawget(t,"root_link") then - t["root_link"][k] = v - end - rawset(t,k,v) - end, - }) - temp.ref = multi.randomString(24) - fast[temp.ref] = func - temp.name = name - if self.rawadd then - self.rawadd = false - else - table.insert(self,true) - self.__connectionAdded(temp, func) - end - return temp - end - - function c:Bind(t) - local temp = fast - fast=t - return temp - end - - function c:Get() - return fast - end - - function c:Remove() - local temp = fast - fast={} - return temp - end - - function c:Hold() - local rets = {multi.hold(self)} - return unpack(rets) - end - - c.connect=c.Connect - c.GetConnection=c.getConnection - c.HasConnections = c.hasConnections - c.GetConnection = c.getConnection - - if func then - c = c .. func - end - - if not(ignoreconn) then - if not self then return c end - self:create(c) - end - - return c -end - --- Used with ISO Threads -local function isolateFunction(func, env) - if setfenv then - return setfenv(func, env) - else - local env = env or {} - local dmp = string.dump(func) - return load(dmp,"IsolatedThread_PesudoThreading", "bt", env) - end -end - -multi.isolateFunction = isolateFunction - -function multi:Break() - self:Pause() - self.Active=nil - self.OnBreak:Fire(self) -end - -function multi:isPaused() - return not(self.Active) -end - -function multi:isActive() - return self.Active -end - -function multi:getType() - return self.Type -end - --- Advance Timer stuff -function multi:SetTime(n) - if not n then n=3 end - local c,err=self:newBase(multi.registerType("timemaster")) - c.timer=self:newTimer() - c.timer:Start() - c.set=n - c.link=self - c.OnTimedOut = self:newConnection() - c.OnTimerResolved = self:newConnection() - self._timer=c.timer - function c:Act() - if self.timer:Get()>=self.set then - self.link:Pause() - self.OnTimedOut:Fire(self.link) - self:Destroy() - return true - end - end - return self,err -end - -function multi:ResolveTimer(...) - self._timer:Pause() - self.OnTimerResolved:Fire(self,...) - self:Pause() - return self -end - --- Timer stuff done -multi.PausedObjects = {} -function multi:Pause() - if self.Type==multi.registerType("rootprocess") then - multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - self._Act = self.Act - self.Act = empty_func - end - return self -end - -function multi:Resume() - if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self.Active==false then - self.Act = self._Act - self.Active=true - end - end - return self -end - -function multi:Destroy() - if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i], self) - c[i]:Destroy() - end - local new = {} - for th,proc in pairs(globalThreads) do - if proc == self then - th:Destroy() - table.remove(globalThreads,th) - else - new[th]=proc - end - end - globalThreads = new - multi.setType(self,multi.DestroyedObj) - else - for i=#self.Parent.Mainloop,1,-1 do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self, self.Parent) - table.remove(self.Parent.Mainloop,i) - self.Destroyed = true - break - end - end - self.Act = function() end - end - return self -end - -function multi:Reset(n) - self:Resume() - return self -end - -function multi:isDone() - return self.Active~=true -end - -function multi:create(ref) - ref.UID = multi.generate_uuid7() - self.OnObjectCreated:Fire(ref, self) - return self -end - -function multi:GetCreationTimestamp() - return multi.extract_uuid7_timestamp(self.UID).iso8601 -end - -function multi:setName(name) - self.Name = name - return self -end - ---Constructors [CORE] -local _tid = 0 -function multi:newBase(tp,ins,callback) - if not(self.Type==multi.registerType("rootprocess") or self.Type==multi.registerType("process", "processes")) then multi.error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type==multi.registerType("process", "processes") then - setmetatable(c, {__index = multi}) - else - setmetatable(c, {__index = multi}) - end - c.Active=true - c.func={} - c.funcTM={} - c.funcTMR={} - c.OnBreak = self:newConnection() - c.OnPriorityChanged = self:newConnection() - c.TID = _tid - c.Act=function() end - c.Parent=self - c.creationTime = clock() - c.Type = tp - - function c:Pause() - c.Parent.Pause(self) - return self - end - - function c:Resume() - c.Parent.Resume(self) - return self - end - - _tid = _tid + 1 -- Even if the task isn't scheduled, we want to increment this - - if type(callback) == "function" then - local res, err = callback(tp) - if not res then - return c, err - end - end - - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end - -function multi:newTimeout(timeout) - local c={} - c.Type = multi.registerType(multi.TIMEOUT, "timeouts") - return function(self) self:Destroy() return c end % self:newAlarm(timeout).OnRing -end - -function multi:newTimer() - local c={} - c.Type=multi.registerType("timer", "timers") - local time=0 - local count=0 - local paused=false - function c:Start() - time=clock() - return self - end - function c:Get() - if self:isPaused() then return time end - return (clock()-time)+count - end - function c:isPaused() - return paused - end - c.Reset=c.Start - function c:Pause() - time=self:Get() - paused=true - return self - end - function c:Resume() - paused=false - time=clock()-time - return self - end - self:create(c) - return c -end - ---Core Actors -function multi:newEvent(task, func) - local c,err=self:newBase(multi.registerType("event", "events")) - local task = task or function() end - function c:Act() - local t = task(self) - if t then - self:Pause() - self.returns = t - self.OnEvent:Fire(self) - return true - end - end - function c:SetTask(func) - task=func - return self - end - c.OnEvent = self:newConnection() - if func then - c.OnEvent(func) - end - self:setPriority("core") - c:setName(c.Type) - self:create(c) - return c,err -end - -function multi:newUpdater(skip, func) - local c,err=self:newBase(multi.registerType("updater", "updaters")) - local pos = 1 - local skip = skip or 1 - function c:Act() - if pos >= skip then - pos = 0 - self.OnUpdate:Fire(self) - return true - end - pos = pos+1 - end - function c:SetSkip(n) - skip=n - return self - end - c.OnUpdate = self:newConnection() - c:setName(c.Type) - if func then - c.OnUpdate(func) - end - self:create(c) - return c,err -end - -function multi:newAlarm(set, func) - local c,err=self:newBase(multi.registerType("alarm", "alarms")) - c:setPriority("Low") - c.set=set or 0 - local count = 0 - local t = clock() - function c:Act() - if clock()-t>=self.set then - self:Pause() - self.Active=false - self.OnRing:Fire(self) - t = clock() - return true - end - end - function c:Resume() - self.Parent.Resume(self) - t = count + t - return self - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - t = clock() - return self - end - c.OnRing = self:newConnection() - function c:Pause() - count = clock() - self.Parent.Pause(self) - return self - end - if func then - c.OnRing(func) - end - c:setName(c.Type) - self:create(c) - return c,err -end - -function multi:newLoop(func, notime) - local c,err=self:newBase(multi.registerType("loop", "loops")) - local start=clock() - if notime then - function c:Act() - self.OnLoop:Fire(self) - return true - end - else - function c:Act() - self.OnLoop:Fire(self,clock()-start) - return true - end - end - - c.OnLoop = self:newConnection() - - if func then - c.OnLoop(func) - end - - self:create(c) - c:setName(c.Type) - return c,err -end - -function multi:newStep(start,reset,count,skip) - local c,err=self:newBase(multi.registerType("step", "steps")) - think=1 - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - self.OnStart:Fire(self) - end - self.OnStep:Fire(self,self.pos) - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - self.OnEnd:Fire(self) - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - return true - end - c.Reset=c.Resume - c.OnStart = self:newConnection() - c.OnStep = self:newConnection() - c.OnEnd = self:newConnection() - function c:Break() - self.Active=nil - return self - end - function c:Count(count) - self.count = count - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - return self - end - c:setName(c.Type) - self:create(c) - return c,err -end - -function multi:newTLoop(func, set) - local c,err=self:newBase(multi.registerType("tloop", "tloops")) - c.set=set or 0 - c.timer=self:newTimer() - c.life=0 - c:setPriority("Low") - - function c:Act() - if self.timer:Get() >= self.set then - self.life=self.life+1 - self.timer:Reset() - self.OnLoop:Fire(self, self.life) - return true - end - end - - function c:Set(set) - self.set = set - end - - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - return self - end - - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - return self - end - - c.OnLoop = self:newConnection() - - if func then - c.OnLoop(func) - end - - c:setName(c.Type) - - self:create(c) - - return c,err -end - -function multi:setTimeout(func, t) - thread:newThread("TimeoutThread", function() thread.sleep(t) func() end) -end - -function multi:newTStep(start,reset,count,set) - local c=self:newStep(start,reset,count) - c.Type=multi.registerType("tstep", "tsteps") - c:setPriority("Low") - local reset = reset or math.huge - c.timer=clock() - c.set=set or 1 - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=clock() - self:Resume() - return self - end - function c:Act() - if clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - self.OnStart:Fire(self) - end - self.OnStep:Fire(self,self.pos) - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - self.OnEnd:Fire(self) - self.pos=self.start - end - return true - end - end - function c:Set(set) - self.set = set - end - function c:Reset(n) - if n then self.set=n end - self.timer=clock() - self:Resume() - return self - end - c:setName(c.Type) - self:create(c) - return c -end - -multi.tasks = {} - -function multi:newTask(func) - self.tasks[#self.tasks + 1] = func -end - -local scheduledjobs = {} -local sthread - -function multi:scheduleJob(time,func) - if not sthread then - sthread = thread:newThread("JobScheduler",function() - local time = os.date("*t", os.time()) - local ready = false - while true do - thread.sleep(1) -- Every second we do some tests - time = os.date("*t", os.time()) - for j,job in pairs(scheduledjobs) do - ready = true - for k,v in pairs(job[1]) do - if not (v == time[k]) then - ready = false - end - end - if ready and not job[3] then - job[2]() - job[3] = true - elseif not ready and job[3] then - job[3] = false - end - end - end - end) - end - table.insert(scheduledjobs,{time, func,false}) -end - -local __CurrentProcess = multi -local __CurrentTask - -function multi.getCurrentProcess() - return __CurrentProcess -end - -function multi.getCurrentTask() - return __CurrentTask -end - -function multi:setCurrentProcess() - __CurrentProcess = self -end - -function multi:setCurrentTask() - __CurrentTask = self -end - -function multi:getName() - return self.Name -end - -function multi:getFullName() - return self.Name -end - -local sandcount = 1 - -function multi:newProcessor(name, opts, priority) - local nothread, attach - if type(opts) ~= "table" then - attach = not opts - nothread = opts -- support old params - end - local c = {} - c.Status = {} - setmetatable(c,{__index = multi}) - local name = name or "Processor_" .. sandcount - local maxThreads = -1 - local maxObjects = -1 - local taskhandler = true - - rootBase = multi.newBase - - local function setStatus(status) - table.insert(c.Status,status) - return status - end - - local callback = function(tp) - if maxObjects <0 or #c.Mainloop < maxObjects then - return true - end - return false, setStatus(string.format("Unable to create [%s]: MAX_OBJECTS: '%d' current scheduled objects: '%d'", tp, maxObjects, #c.Mainloop)) - end - - function c:newBase(tp,ins) - return rootBase(self,tp,ins,callback) - end - - sandcount = sandcount + 1 - c.Mainloop = {} - c.Type = multi.registerType("process", "processes") - local Active = nothread or false - local task_delay = 0 - c.Name = name or "" - c.tasks = {} - c.threads = {} - c.startme = {} - c.parent = self - c.OnObjectCreated = self:newConnection() - - local boost = 1 - local handler - - if type(opts) == "table" then - priority = opts.Priority or false - Active = opts.Start or false - maxThreads = opts.MaxThreads or -1 - maxObjects = opts.MaxObjects or -1 - task_delay = opts.TaskDelay or 0 - attach = opts.Attach or false - if opts.TaskHandler == false then -- The default was true - taskhandler = false - end - end - - if priority then - handler = c:createPriorityHandler(c) - else - handler = c:createHandler(c) - end - - if attach then -- Don't create a loop if we are triggering this manually - c.process = self:newLoop(function() - if Active then - c:uManager(true) - handler() - end - end) - c.process.__ignore = true - c.process.isProcessThread = true - c.process.PID = sandcount - c.OnError = c.process.OnError - else - c.OnError = self:newConnection() - end - - c.OnError(multi.error) - - function c:getHandler() - return handler - end - - function c:getThreads() - return c.threads - end - - function c:getFullName() - return c.parent:getFullName() .. "." .. c.Name - end - - function c:getName() - return self.Name - end - - function c:newThread(name, func,...) - if maxThreads < 0 or (#c.threads+#c.startme < maxThreads) then - return thread.newThread(c, name, func, ...) - end - return nil, setStatus(string.format("Unable to create [thread]: '%s' MAX_THREADS: '%d' current scheduled threads: '%d'",name,maxThreads,#c.threads+#c.startme)) - end - - function c:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return c:newThread("Process Threaded Function Handler", func, ...) - end, holdme)() - end - - function c:getMaxThreads() - return maxThreads - end - - function c:setMaxThreads(n) - maxThreads = n - return c - end - - function c:getMaxObjects() - return maxObjects - end - - function c:setMaxObjects(n) - maxObjects = n - return c - end - - function c:boost(count) - boost = count or 1 - if boost > 1 then - self.run = function() - if not Active then return end - for i=1,boost do - c:uManager(true) - handler() - end - return c - end - else - self.run = function() - if not Active then return end - c:uManager(true) - handler() - return c - end - end - end - - function c.run() - if not Active then return end - c:uManager(true) - handler() - return c - end - - function c.isActive() - return Active - end - - function c.Start() - Active = true - return c - end - - function c.Stop() - Active = false - return c - end - - function c:Destroy() - Active = false - c.process:Destroy() - end - - function c:setTaskDelay(delay) - if type(delay) == "function" then - task_delay = delay - else - task_delay = tonumber(delay) or 0 - end - end - - if taskhandler then - c:newThread("Task Handler", function() - local self = multi:getCurrentProcess() - local function task_holder() - return #self.tasks > 0 - end - while true do - if #self.tasks > 0 then - table.remove(self.tasks,1)() - else - thread.hold(task_holder) - end - if task_delay~=0 then - thread.hold(task_delay) - end - end - end).OnError(multi.error) - end - - table.insert(processes,c) - self:create(c) - return c -end - -function multi.hold(func,opt) - if thread.isThread() then return thread.hold(func, opt) end - local proc = multi.getCurrentTask() - if proc then - proc:Pause() - end - local rets - thread:newThread("Hold_func",function() - rets = {thread.hold(func,opt)} - end) - while rets == nil do - multi:uManager() - end - if proc then - proc:Resume() - end - return multi.unpack(rets) -end - --- Threading stuff -local threadCount = 0 -local threadid = 0 -thread.__threads = {} -local threads = thread.__threads -multi.GlobalVariables={} -local dFunc = function() return true end -thread.requests = {} -local CMD = {} -- We will compare this special local -local interval -local resume, status, create, yield, running = coroutine.resume, coroutine.status, coroutine.create, coroutine.yield, coroutine.running - -local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 - -function multi:getThreads() - return threads -end - -function multi:getRunners() - local tasks = {} - for i,v in pairs(self.Mainloop) do - if not v.__ignore then - tasks[#tasks+1] = v - end - end - return tasks -end - -function thread.request(t,cmd,...) - thread.requests[t.thread] = {cmd, multi.pack(...)} -end - -function thread.defer(func) - local th = thread.getRunningThread() - local conn = (th.OnError + th.OnDeath) - conn(function() - func(th) - end) -end - -function thread.getRunningThread() - local threads = globalThreads - local t = coroutine.running() - if t then - for th,process in pairs(threads) do - if t==th.thread then - return th - end - end - end -end - -function thread._Requests() - local t = thread.requests[running()] - if t then - thread.requests[running()] = nil - local cmd,args = t[1],t[2] - thread[cmd](multi.unpack(args)) - end -end - -function thread.exec(func) - func() -end - -function thread.sleep(n) - thread._Requests() - thread.getRunningThread().lastSleep = clock() - return yield(CMD, t_sleep, n or 1) -end - -local function conn_test(conn) - local ready = false - local args - local func = function(...) - ready = true - args = multi.pack(...) - end - - local ref = conn(func) - return function() - if ready then - conn:Unconnect(ref) - if #args==0 then - return multi.NIL - else - return multi.unpack(args) - end - end - end -end - -function thread.chain(...) - local args = select("#",...) - for i=1,args do - thread.hold(select(i,...)) - end -end - -function thread.hold(n, opt) - thread._Requests() - local opt = opt or {} - if type(opt)=="table" and type(n) == "function" then - interval = opt.interval - if opt.cycles then - return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) - elseif opt.sleep then - return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) - elseif opt.skip then - return yield(CMD, t_skip, opt.skip or 1, nil, interval) - end - end - - if type(n) == "number" then - thread.getRunningThread().lastSleep = clock() - return yield(CMD, t_sleep, n or 0, nil, interval) - elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then - return yield(CMD, t_hold, conn_test(n), nil, interval) - elseif type(n) == "table" and n.Hold ~= nil then - return n:Hold(opt) - elseif type(n) == "function" then - return yield(CMD, t_hold, n, nil, interval) - else - multi.error("Invalid argument passed to thread.hold(...) ".. type(n) .. "!") - end -end - -function thread.holdFor(sec,n) - thread._Requests() - return yield(CMD, t_holdF, sec, n or dFunc) -end - -function thread.holdWithin(skip,n) - thread._Requests() - return yield(CMD, t_holdW, skip or 1, n or dFunc) -end - -function thread.skip(n) - thread._Requests() - return yield(CMD, t_skip, n or 1) -end - -function thread.kill(msg) - msg = msg or "thread killed!" - multi.error(msg) -end - -function thread.yield() - thread._Requests() - return yield(CMD, t_yield) -end - -function thread.isThread() - local a,b = running() - if b then - -- We are dealing with luajit compat or 5.2+ - return not(b) - else - return a~=nil - end -end - -function thread.getCores() - return thread.__CORES -end - -function thread.set(name,val) - multi.GlobalVariables[name]=val - return true -end - -function thread.get(name) - return multi.GlobalVariables[name] -end - -function thread.waitFor(name) - thread.hold(function() return thread.get(name)~=nil end) - return thread.get(name) -end - -local function cleanReturns(...) - local returns = multi.pack(...) - local rets = {} - local ind = 0 - for i=#returns,1,-1 do - if returns[i] then - ind = i - break - end - end - return multi.unpack(returns,1,ind) -end - -function thread.pushStatus(...) - local t = __CurrentConnectionThread or thread.getRunningThread() - t.statusconnector:Fire(...) -end - -function thread:newFunctionBase(generator, holdme, TYPE) - return function() - local UID = multi.generate_uuid7() - local tfunc = { - GetCreationTimestamp = function() return multi.extract_uuid7_timestamp(UID).iso8601 end, - } - tfunc.Active = true - function tfunc:Pause() - self.Active = false - end - function tfunc:Resume() - self.Active = true - end - function tfunc:holdMe(b) - holdme = b - end - local function noWait() - return nil, "Function is paused" - end - local rets, err - local function wait() - if thread.isThread() then - return thread.hold(function() - if err then - return multi.NIL, err - elseif rets then - local g = rets - rets = nil - return cleanReturns((g[1] or multi.NIL),g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) - end - end) - else - while not rets and not err do - multi:uManager() - end - local g = rets - rets = nil - if err then - return nil,err - end - return cleanReturns(g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) - end - end - tfunc.__call = function(th,...) - if th.Active == false then - if holdme then - return nil, "Function is paused" - end - return { - isTFunc = true, - wait = noWait, - connect = function(f) - f(nil,"Function is paused") - end - } - end - local t = generator(...) - t.OnDeath(function(...) rets = multi.pack(...) end) - t.OnError(function(self,e) err = e end) - if holdme then - return wait() - end - local temp = { - OnStatus = multi:getCurrentProcess():newConnection(true), - OnError = multi:getCurrentProcess():newConnection(true), - OnReturn = multi:getCurrentProcess():newConnection(true), - isTFunc = true, - wait = wait, - getReturns = function() - return multi.unpack(rets) - end, - connect = function(f) - local tempConn = multi:getCurrentProcess():newConnection(true) - t.OnDeath(function(...) if f then f(...) else tempConn:Fire(...) end end) - t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) - return tempConn - end - } - t.OnDeath(function(...) temp.OnReturn:Fire(...) end) - t.OnError(function(self,err) temp.OnError:Fire(err) temp.OnError(multi.error) end) - t.linkedFunction = temp - t.statusconnector = temp.OnStatus - return temp - end - setmetatable(tfunc, tfunc) - tfunc.Type = TYPE or multi.registerType("function", "functions") - return tfunc - end -end - -function thread:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return thread:newThread("Free Threaded Function Handler", func, ...) - end, holdme)() -end - -function thread:newProcessor(name, nothread, priority) - -- Inactive proxy proc - local process = multi:getCurrentProcess() - local proc = process:newProcessor(name, true) - local thread_proc = process:newProcessor(name).Start() - local Active = true - - local handler - if priority then - handler = thread_proc:createPriorityHandler(c) - else - handler = thread_proc:createHandler(c) - end - - function proc:getThreads() - return thread_proc.threads - end - - function proc:getFullName() - return thread_proc.parent:getFullName() .. "." .. self.Name - end - - function proc:getName() - return thread_proc.Name - end - - function proc:isActive() - return Active - end - - function proc:newThread(name, func, ...) - return thread.newThread(thread_proc, name, func, ...) - end - - function proc:newFunction(func, holdme) - return thread:newFunctionBase(function(...) - return thread_proc:newThread("TProc Threaded Function Handler", func, ...) - end, holdme)() - end - - function proc.Start() - Active = true - return proc - end - - function proc.Stop() - Active = false - return proc - end - - function proc:Destroy() - Active = false - thread_proc:Destroy() - end - - proc.OnObjectCreated(function(obj) - if not obj.Act then return end - multi.print("Converting "..obj.Type.." to thread!") - thread_proc:newThread(function() - obj.reallocate = empty_func - while true do - thread.hold(function() return Active end) - obj:Act() - end - end) - end) - - process:create(proc) - - return proc -end - --- A cross version way to set enviroments, not the same as fenv though -function multi.setEnv(func,env) - local f = string.dump(func) - local chunk = load(f, "env", "bt", env) - return chunk -end - -function thread:newThread(name, func, ...) - multi.OnLoad:Fire() -- This was done incase a threaded function was called before mainloop/uManager was called - if type(name) == "function" then - func = name - name = "UnnamedThread_"..multi.randomString(16) - end - local c={nil,nil,nil,nil,nil,nil,nil} - c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} - c.startArgs = multi.pack(...) - c.ref={} - c.Name=name - c.thread=create(func) - c.sleep=1 - c.Type = multi.registerType("thread", "threads") - c.TID = threadid - c.firstRunDone=false - c._isPaused = false - c.returns = {} - c.isError = false - - if self.Type == multi.registerType("process", "processes") then - c.OnError = self:newConnection(true,nil,true) - c.OnDeath = self:newConnection(true,nil,true) - else - c.OnError = threadManager:newConnection(true,nil,true) - c.OnDeath = threadManager:newConnection(true,nil,true) - end - - c.OnError(multi.error) - - function c:getName() - return c.Name - end - - function c:isPaused() - return self._isPaused - end - - function c:GetCreationTimestamp() - return multi.extract_uuid7_timestamp(self.UID).iso8601 - end - - local resumed = false - function c:Pause() - if not self._isPaused then - thread.request(self, "exec", function() - thread.hold(function() - return resumed - end) - resumed = false - self._isPaused = false - end) - self._isPaused = true - end - return self - end - - function c:Resume() - resumed = true - return self - end - - function c:Kill() - thread.request(self, "kill") - return self - end - - function c:Sleep(n) - thread.request(self, "exec", function() - thread.sleep(n) - resumed = false - end) - return self - end - - function c:Hold(n,opt) - thread.request(self, "exec", function() - thread.hold(n, opt) - resumed = false - end) - return self - end - - c.Destroy = c.Kill - if thread.isThread() then - if self.Type == multi.registerType("process", "processes") then - table.insert(self.startme, c) - else - table.insert(threadManager.startme, c) - end - else - if self.Type == multi.registerType("process", "processes") then - table.insert(self.startme, c) - else - table.insert(threadManager.startme, c) - end - end - - globalThreads[c] = multi - threadid = threadid + 1 - if self.Type == multi.registerType("process", "processes") then - self:create(c) - else - threadManager:create(c) - end - c.creationTime = clock() - return c -end - -function thread:newISOThread(name, func, env, ...) - local func = func or name - local env = env or {} - if not env.thread then - env.thread = thread - end - if not env.multi then - env.multi = multi - end - if type(name) == "function" then - name = "Thread#"..threadCount - end - local func = isolateFunction(func, env) - return thread:newThread(name, func, ...) -end - -multi.newThread = thread.newThread -multi.newISOThread = thread.newISOThread - -local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 -local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 -local ret,_ -local task, thd, ref, ready -local switch = { - function(th,co)--hold - if clock() - th.intervalR>=th.interval then - t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() - if t0 then - if t0==NIL then t0 = nil end - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--sleep - if clock() - th.time>=th.sec then - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - end, - function(th,co)--holdf - if clock() - th.intervalR>=th.interval then - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() - if t0 then - if t0 then - if t0==NIL then t0 = nil end - th.task = t_none - end - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - elseif clock() - th.time>=th.sec then - th.task = t_none - t0 = nil - t1 = multi.TIMEOUT - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--skip - th.pos = th.pos + 1 - if th.count==th.pos then - th.task = t_none - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - end, - function(th,co)--holdw - if clock() - th.intervalR>=th.interval then - th.pos = th.pos + 1 - t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() - if t0 then - if t0 then - if t0==NIL then t0 = nil end - th.task = t_none - end - th.task = "" - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - elseif th.count==th.pos then - th.task = t_none - t0 = nil - t1 = multi.TIMEOUT - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end - th.intervalR = clock() - end - end, - function(th,co)--yield - _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) - end, - function() end--none -} - -setmetatable(switch,{__index=function() return function() end end}) - -local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order - function(th,arg1,arg2,arg3) - th.func = arg1 - th.task = t_hold - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function(th,arg1,arg2,arg3) - th.sec = arg1 - th.time = clock() - th.task = t_sleep - end, - function(th,arg1,arg2,arg3) - th.sec = arg1 - th.func = arg2 - th.task = t_holdF - th.time = clock() - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function(th,arg1,arg2,arg3) - th.count = arg1 - th.pos = 0 - th.task = t_skip - end, - function(th,arg1,arg2,arg3) - th.count = arg1 - th.pos = 0 - th.func = arg2 - th.task = t_holdW - th.time = clock() - th.interval = arg3 or 0 - th.intervalR = clock() - end, - function(th,arg1,arg2,arg3) - th.task = t_yield - end, - function() end -} -setmetatable(cmds,{__index=function() return function() end end}) -local co_status -co_status = { - ["suspended"] = function(thd,ref,task,i,th) - switch[task](ref,thd) - cmds[r1](ref,r2,r3,r4,r5) - if ret ~= CMD and _ ~= nil then -- The rework makes this necessary - co_status["dead"](thd,ref,task,i,th) - end - r1=nil r2=nil r3=nil r4=nil r5=nil - end, - ["normal"] = function(thd,ref) end, - ["running"] = function(thd,ref) end, - ["dead"] = function(thd,ref,task,i,th) - if ref.__processed then table.remove(th,i) return end - if _ then - ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - else - ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) - multi.error(ref, ret) - end - if i then - table.remove(th,i) - else - for i,v in pairs(th) do - if v.thread==thd then - table.remove(th,i) - break - end - end - end - _=nil r1=nil r2=nil r3=nil r4=nil r5=nil r6=nil r7=nil r8=nil r9=nil r10=nil r11=nil r12=nil r13=nil r14=nil r15=nil r16=nil - ref.__processed = true - end, -} - -function multi:createHandler() - local threads, startme = self.threads, self.startme - return coroutine.wrap(function() - local temp_start - while true do - while #startme>0 do - temp_start = table.remove(startme) - _, ret, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = resume(temp_start.thread, multi.unpack(temp_start.startArgs)) - co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) - table.insert(threads, temp_start) - yield() - end - for i=#threads,1,-1 do - ref = threads[i] - if ref then - task = ref.task - thd = ref.thread - ready = ref.__ready - co_status[status(thd)](thd, ref, task, i, threads) - end - yield() - end - yield() - end - end) -end - -function multi:createPriorityHandler() - local threads, startme = self.threads, self.startme - return coroutine.wrap(function() - local temp_start - while true do - while #startme>0 do - temp_start = table.remove(startme) - _, ret, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = resume(temp_start.thread, multi.unpack(temp_start.startArgs)) - co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) - table.insert(threads, temp_start) - end - for i=#threads,1,-1 do - ref = threads[i] - if ref then - task = ref.task - thd = ref.thread - ready = ref.__ready - co_status[status(thd)](thd, ref, task, i, threads) - end - end - yield() - end - end) -end - -function multi:newService(func) -- Priority managed threads - local c = {} - c.Type = multi.registerType("service", "services") - c.OnStopped = self:newConnection() - c.OnStarted = self:newConnection() - local Service_Data = {} - local active - local time - local p = multi.Priority_Normal - local ap - local task = thread.sleep - local scheme = 1 - - function c.Start() - if not active then - time = self:newTimer() - time:Start() - active = true - c:OnStarted(c, Service_Data) - end - return c - end - - local function process() - thread.hold(function() - return active - end) - func(c, Service_Data) - task(ap) - return c - end - - local th = thread:newThread("Service_Handler",function() - while true do - process() - end - end) - - th.OnError = c.OnError -- use the threads onerror as our own - th.OnError(multi.error) - - function c.Destroy() - th:kill() - c.Stop() - multi.setType(c,multi.DestroyedObj) - return c - end - - function c:SetScheme(n) - if type(self)=="number" then n = self end - scheme = n - if math.abs(n)==1 then - ap = (p^(1/3))/10 - if ap==.1 then task = thread.yield end - task = thread.sleep - elseif math.abs(n)==2 then - ap = math.abs(p-1)*32+1 - task = thread.skip - elseif math.abs(n)==3 then - -- This is a time based pirority manager. Things that take long to run get - end - return c - end - - function c.Stop() - if active then - c:OnStopped(c) - Service_Data = {} - time:Reset() - time:Pause() - time = nil - active = false - end - return c - end - - function c.Pause() - if active then - time:Pause() - active = false - end - return c - end - - function c.Resume() - if not active then - time:Resume() - active = true - end - return c - end - - function c.GetUpTime() - return time:Get() - end - - function c:SetPriority(pri) - if type(self)=="number" then pri = self end - p = pri - c.SetScheme(scheme) - return c - end - - self:create(c) - - return c -end - --- Multi runners -function multi:mainloopRef() - __CurrentProcess = self - multi.OnPreLoad:Fire() - self.uManager = self.uManagerRef - if not isRunning then - isRunning = true - mainloopActive = true - local Loop=self.Mainloop - local ctask - multi.OnLoad:Fire() - while mainloopActive do - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - ctask = __CurrentTask - if ctask then ctask:Act() end - __CurrentProcess = self - end - end - else - return nil, "Already Running!" - end -end - -multi.mainloop = multi.mainloopRef - -function multi:p_mainloop() - __CurrentProcess = self - multi.OnPreLoad:Fire() - self.uManager = self.uManagerRefP1 - if not isRunning then - isRunning=true - mainloopActive = true - local Loop = self.Mainloop - local ctask - multi.OnLoad:Fire() - while mainloopActive do - for task=#Loop,1,-1 do - __CurrentTask = Loop[task] - ctask = __CurrentTask - for i=1,9 do - if PList[i]%ctask.Priority == 0 then - ctask:Act() - __CurrentProcess = self - end - end - end - end - else - return nil, "Already Running!" - end -end - -local function doOpt() - function thread.hold(n,opt) - thread._Requests() - local opt = opt or {} - if type(opt)=="table" then - interval = opt.interval - if opt.cycles then - return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) - elseif opt.sleep then - return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) - elseif opt.skip then - return yield(CMD, t_skip, opt.skip or 1, nil, interval) - end - end - if type(n) == "number" then - thread.getRunningThread().lastSleep = clock() - return yield(CMD, t_sleep, n or 0, nil, interval) - elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then - local rdy = function() - return false - end - n(function(a1,a2,a3,a4,a5,a6) - rdy = function() - if a1==nil then - return NIL,a2,a3,a4,a5,a6 - end - return a1,a2,a3,a4,a5,a6 - end - end) - return yield(CMD, t_hold, function() - return rdy() - end, nil, interval) - elseif type(n) == "function" then - local cache = string.dump(n) - local f_str = tostring(n) - local good = true - for i=1,#func_cache do - if func_cache[i][1] == cache and func_cache[i][2] ~= f_str and not func_cache[i][3] then - multi:getOptimizationConnection():Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) - func_cache[i][3] = true - good = false - end - end - if good then - table.insert(func_cache, {cache, f_str}) - end - return yield(CMD, t_hold, n or dFunc, nil, interval) - else - multi.error("Invalid argument passed to thread.hold(...)!") - end - end -end - -local init = false -multi.settingsHook = multi:newConnection() -function multi.init(settings, realsettings) - if settings == multi then settings = realsettings end - - if type(settings)=="table" then - - multi.defaultSettings = settings - - if settings.priority then - multi.mainloop = multi.p_mainloop - else - multi.mainloop = multi.mainloopRef - end - - if not init then - - if settings.findopt then - find_optimization = true - doOpt() - multi.enableOptimization:Fire(multi, thread) - end - - if settings.debugging then - require("multi.integration.debugManager") - end - - multi.settingsHook:Fire(settings) - end - end - init = true - return _G["$multi"].multi,_G["$multi"].thread -end - -function multi:uManager() - if self.Active then - __CurrentProcess = self - multi.OnPreLoad:Fire() - self.uManager=self.uManagerRef - multi.OnLoad:Fire() - end -end - -function multi:uManagerRefP1() - if self.Active then - __CurrentProcess = self - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - for P=1,9 do - if PList[P]%__CurrentTask.Priority==0 then - __CurrentTask:Act() - __CurrentProcess = self - end - end - end - end -end - -function multi:uManagerRef() - if self.Active then - __CurrentProcess = self - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - __CurrentTask = Loop[_D] - __CurrentTask:Act() - __CurrentProcess = self - end - end -end - --------- --- UTILS --------- - -function multi.isTimeout(res) - if type(res) == "table" then - return res.Type == multi.TIMEOUT - end - return res == multi.TIMEOUT -end - -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end - -math.randomseed(os.time()) - -function multi:enableLoadDetection() - if multi.maxSpd then return end - -- here we are going to run a quick benchMark solo - local temp = self:newProcessor() - local t = clock() - local stop = false - temp:benchMark(.01):OnBench(function(time,steps) - stop = steps - end) - while not stop do - temp:uManager() - end - temp:Destroy() - multi.maxSpd = stop -end - -local lastVal = 0 -local last_step = 0 - -function multi:getLoad() - if not multi.maxSpd then multi:enableLoadDetection() end - local val = nil - local bench - local bb - self:benchMark(.01).OnBench(function(time,steps) - bench = steps - bb = steps - end) - _,timeout = multi.hold(function() - return bench - end,{sleep=.012}) - if timeout or not bench then - bench = 0 - bb = 0 - end - bench = bench^1.5 - val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) - if val<0 then val = 0 end - if val > 100 then val = 100 end - lastVal = val - last_step = bb*100 - return val,last_step -end - -function multi:setPriority(s) - if not self:IsAnActor() or self.Type == multi.registerType("process", "processes") then return end - if type(s)=="number" then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=="very high" or s:lower()=="vh" then - self.Priority=self.Priority_Very_High - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='a' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='b' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=="very low" or s:lower()=="vl" then - self.Priority=self.Priority_Very_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - self.OnPriorityChanged:Fire(self, self.Priority) - end - if not self.PrioritySet then - self.defPriority = self.Priority - self.PrioritySet = true - end - return self -end - -function multi:ResetPriority() - self.Priority = self.defPriority - return self -end - -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end - -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end - -function multi:getChildren() - return self.Mainloop -end - -function multi:getVersion() - return multi.Version -end - -function multi:getPlatform() - if love then - if love.thread then - return "love2d" - end - else - return "lanes" - end -end - -function multi:canSystemThread() - return false -end - -function multi:benchMark(sec,p,pt) - local c = 0 - local temp=self:newLoop(function(self,t) - if t>sec then - if pt then - multi.print(pt.." "..c.." Steps in "..sec.." second(s)!") - end - self.OnBench:Fire(sec,c) - self:Destroy() - else - c=c+1 - end - end) - temp.OnBench = self:newConnection() - temp:setPriority(p or 1) - return temp -end - -function multi.Round(num, numDecimalPlaces) - local mult = 10^(numDecimalPlaces or 0) - return math.floor(num * mult + 0.5) / mult -end - -function multi.AlignTable(tab) - local longest = {} - local columns = #tab[1] - local rows = #tab - for i=1, columns do - longest[i] = -math.huge - end - for i = 1,rows do - for j = 1,columns do - tab[i][j] = tostring(tab[i][j]) - if #tab[i][j]>longest[j] then - longest[j] = #tab[i][j] - end - end - end - for i = 1,rows do - for j = 1,columns do - if tab[i][j]~=nil and #tab[i][j]= "Lua 5.2" or jit then - setmetatable(multi.m, {__gc = multi.m.onexit}) -else - multi.m.sentinel = newproxy(true) - getmetatable(multi.m.sentinel).__gc = multi.m.onexit -end - -threadManager = multi:newProcessor("Global_Thread_Manager", nil, true).Start() -threadManager.tasks = multi.tasks -- The main multi interface is a bit different. - -function multi:setTaskDelay(delay) - threadManager:setTaskDelay(delay) -end - -function multi:getThreadManagerProcess() - return threadManager -end - -function multi:getHandler() - return threadManager:getHandler() -end - +--[[ +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 = {} +local mainloopActive = false +local isRunning = false +local clock = os.clock +local thread = {} +local processes = {} +local find_optimization = false +local threadManager +local __CurrentConnectionThread + +multi.unpack = table.unpack or unpack +multi.pack = table.pack or function(...) return {...} end + +if table.unpack then unpack = table.unpack end + +-- Types + +multi.DestroyedObj = { + Type = "DESTROYED", +} + +local function uni() + return multi.DestroyedObj +end + +local function uniN() end +function multi.setType(obj,t) + if t == multi.DestroyedObj then + for i,v in pairs(obj) do + obj[i] = nil + end + setmetatable(obj, { + __index = function(t,k) + return setmetatable({},{__index = uni,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni}) + end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni + }) + end +end + +setmetatable(multi.DestroyedObj, { + __index = function(t,k) + return setmetatable({},{__index = uni,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni}) + end,__newindex = uni,__call = uni,__metatable = multi.DestroyedObj,__tostring = function() return "destroyed" end,__unm = uni,__add = uni,__sub = uni,__mul = uni,__div = uni,__mod = uni,__pow = uni,__concat = uni +}) + +multi.DESTROYED = multi.DestroyedObj + +-- I don't like modifying the global namespace, so I prepend a "$" +if not _G["$multi"] then + _G["$multi"] = {multi = multi, thread = thread} +end + +local types = {} +function multi.registerType(typ, p) + if multi["$"..typ:upper():gsub("_","")] then return typ end + multi["$"..typ:upper():gsub("_","")] = typ + table.insert(types, {typ, p or typ}) + return typ +end + +function multi.hasType(typ) + if multi["$"..typ:upper():gsub("_","")] then + return multi["$"..typ:upper():gsub("_","")] + end +end + +function multi.getTypes() + return types +end + +multi.Version = "16.3.0" +multi.Name = "root" +multi.NIL = {Type="NIL"} +local NIL = multi.NIL +multi.Mainloop = {} +multi.Children = {} +multi.Active = true +multi.Type = multi.registerType("rootprocess") +multi.LinkedPath = multi +multi.TIMEOUT = multi.registerType("TIMEOUT", "timeouts") +multi.TID = 0 +multi.defaultSettings = {} + +multi.Priority_Core = 1 +multi.Priority_Very_High = 4 +multi.Priority_High = 16 +multi.Priority_Above_Normal = 64 +multi.Priority_Normal = 256 +multi.Priority_Below_Normal = 1024 +multi.Priority_Low = 4096 +multi.Priority_Very_Low = 16384 +multi.Priority_Idle = 65536 + +multi.PriorityResolve = { + [1] = "Core", + [4] = "Very High", + [16] = "High", + [64] = "Above Normal", + [256] = "Normal", + [1024] = "Below Normal", + [4096] = "Low", + [16384] = "Very Low", + [65536] = "Idle", +} + +local PList = {multi.Priority_Core,multi.Priority_Very_High,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Very_Low,multi.Priority_Idle} +multi.PriorityTick=1 +multi.Priority=multi.Priority_High +multi.threshold=256 +multi.threstimed=.001 + +-- System +function multi.Stop() + isRunning = false + mainloopActive = false +end + +local pack = multi.pack + +--Processor +local priorityTable = {[false]="Disabled",[true]="Enabled"} +local ProcessName = {"SubProcessor","MainProcessor"} +local globalThreads = {} + +function multi:getProcessors() + return processes +end + +function multi:isType(type) + return self.Type == type +end + +function multi:getStats() + local stats = { + [multi.Name] = { + threads = multi:getThreads(), + tasks = multi.Mainloop + } + } + local procs = multi:getProcessors() + for i = 1, #procs do + local proc = procs[i] + stats[proc:getFullName()] = { + threads = proc:getThreads(), + tasks = proc.Mainloop + } + end + return stats +end + +--Helpers + +function multi.setClock(c) + clock = c +end + +function multi.ForEach(tab,func) + for i=1,#tab do func(tab[i]) end +end + +function multi.randomString(n) + local str = '' + local strings = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} + for i=1,n do + str = str..''..strings[math.random(1,#strings)] + end + return str +end + +function multi.isMulitObj(obj) + if type(obj)=="table" then + if obj.Type ~= nil then + return multi.hasType(obj.Type) ~= nil + end + end + return false +end + +function multi.forwardConnection(src, dest) + if multi.isMulitObj(src) and multi.isMulitObj(dest) then + src(function(...) + dest:Fire(...) + end) + else + multi.error("Cannot forward non-connection objects") + end +end + +local optimization_stats = {} +local ignoreconn = true +local empty_func = function() end + +function multi:newConnection(protect,func,kill) + local processor = self + local c={} + local lock = false + local fast = {} + c.__connectionAdded = function() end + c.rawadd = false + c.Parent = self + + setmetatable(c,{ + __call=function(self,...) + local t = ... + if type(t)=="table" then + for i,v in pairs(t) do + if v==self then + local ref = self:Connect(select(2,...)) + if ref then + ref.root_link = select(1,...) + return ref + end + return self + end + end + return self:Connect(...) + else + return self:Connect(...) + end + end, + __unm = function(obj) -- -obj Reverses the order of connected events + local conns = obj:Bind({}) + for i = #conns, 1, -1 do + obj.rawadd = true + obj(conns[i]) + obj.rawadd = false + end + return obj + end, + __mod = function(obj1, obj2) -- % + local cn = self:newConnection() + if (type(obj1) == "function" or type(obj1) == "table" and obj1.Type == multi.registerType("function", "functions")) and type(obj2) == "table" then + obj2(function(...) + cn:Fire(obj1(...)) + end) + elseif type(obj1) == "table" and (type(obj2) == "function" or type(obj2) == "table" and obj2.Type == multi.registerType("function", "functions")) then + local conns = obj1:Bind({}) + for i = 1,#conns do + obj1(function(...) + conns[i](obj2(...)) + end) + end + obj1.__connectionAdded = function(conn, func) + obj1:Unconnect(conn) + obj1.rawadd = true + obj1:Connect(function(...) + func(obj2(...)) + end) + obj1.rawadd = false + end + return obj1 + else + multi.error("Invalid mod!", type(obj1), type(obj2),"Expected function, connection(table)") + end + return cn + end, + __div = function(obj1, obj2) -- / + local cn = self:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + obj2(function(...) + local args = {obj1(...)} + if args[1] then + table.remove(args,1) + cn:Fire(multi.unpack(args)) + end + end) + else + multi.error("Invalid divide!", type(obj1), type(obj2),"Expected function/connection(table)") + end + return cn + end, + __concat = function(obj1, obj2) -- .. + local cn = self:newConnection() + local ref + if type(obj1) == "function" and type(obj2) == "table" then + cn(function(...) + if obj1(...) == true then + obj2:Fire(...) + end + end) + cn.__connectionAdded = function(conn, func) + cn:Unconnect(conn) + obj2:Connect(func) + end + elseif type(obj1) == "table" and type(obj2) == "function" then + ref = cn(function(...) + obj1:Fire(...) + obj2(...) + end) + cn.__connectionAdded = function() + cn.rawadd = true + cn:Unconnect(ref) + ref = cn(function(...) + if obj2(...) then + obj1:Fire(...) + end + end) + end + return cn + elseif type(obj1) == "table" and type(obj2) == "table" then + -- + else + error("Invalid concat!", type(obj1), type(obj2),"Expected function/connection(table), connection(table)/function") + end + return cn + end, + __add = function(c1,c2) -- Or + local cn = self:newConnection() + c1(function(...) + cn:Fire(...) + end) + c2(function(...) + cn:Fire(...) + end) + return cn + end, + __mul = function(c1,c2) -- And + local cn = self:newConnection() + local ref1, ref2 + if c1.__hasInstances == nil then + cn.__hasInstances = {2} + cn.__count = {0} + else + cn.__hasInstances = c1.__hasInstances + cn.__hasInstances[1] = cn.__hasInstances[1] + 1 + cn.__count = c1.__count + end + + ref1 = c1(function(...) + cn.__count[1] = cn.__count[1] + 1 + c1:Lock(ref1) + if cn.__count[1] == cn.__hasInstances[1] then + cn:Fire(...) + cn.__count[1] = 0 + c1:Unlock(ref1) + c2:Unlock(ref2) + end + end) + + ref2 = c2(function(...) + cn.__count[1] = cn.__count[1] + 1 + c2:Lock(ref2) + if cn.__count[1] == cn.__hasInstances[1] then + cn:Fire(...) + cn.__count[1] = 0 + end + end) + return cn + end}) + + c.Type=multi.registerType("connector", "connections") + c.func={} + c.ID=0 + local protect=protect or false + local connections={} + c.FC=0 + + function c:hasConnections() + return #fast~=0 + end + + function c:Lock(conn) + if conn and not conn.lock then + conn.lock = function() end + for i = 1, #fast do + if fast[conn.ref] == fast[i] then + fast[i] = conn.lock + return self + end + end + return self + end + lock = true + return self + end + + function c:Unlock(conn) + if conn and conn.lock then + for i = 1, #fast do + if conn.lock == fast[i] then + fast[i] = fast[conn.ref] + return self + end + end + return self + end + lock = false + return self + end + + if protect then + function c:Fire(...) + if lock then return end + local kills = {} + for i=1,#fast do + local suc, err = pcall(fast[i], ...) + if not suc then + multi.error(err) + end + if kill then + table.insert(kills,i) + processor:newTask(function() + for _, k in pairs(kills) do + table.remove(kills, _) + table.remove(fast, k) + end + end) + end + end + end + end + + function c:getConnections() + return fast + end + + function c:getConnection(name, ignore) + return fast[name] or function() multi:warning("") end + end + + function c:Unconnect(conn) + for i = 1, #fast do + if fast[conn.ref] == fast[i] then + table.remove(self) + return table.remove(fast, i), i + end + end + end + + function c:fastMode() return self end + + if kill then + local kills = {} + function c:Fire(...) + if lock then return end + for i=1,#fast do + fast[i](...) + if kill then + table.insert(kills,i) + processor:newTask(function() + for _, k in pairs(kills) do + table.remove(kills, _) + table.remove(fast, k) + end + end) + end + end + end + else + function c:Fire(...) + if lock then return end + for i=1,#fast do + fast[i](...) + end + end + end + + function c:Connect(func, name) + local th + if thread.getRunningThread then + th = thread.getRunningThread() + end + if th then + local fref = func + func = function(...) + __CurrentConnectionThread = th + fref(...) + __CurrentConnectionThread = nil + end + end + table.insert(fast, func) + if name then + fast[name] = func + else + fast["Conn_"..multi.randomString(12)] = func + end + local temp = {fast = true} + setmetatable(temp,{ + __call=function(s,...) + return self:Connect(...) + end, + __index = function(t,k) + if rawget(t,"root_link") then + return t["root_link"][k] + end + return nil + end, + __newindex = function(t,k,v) + if rawget(t,"root_link") then + t["root_link"][k] = v + end + rawset(t,k,v) + end, + }) + temp.ref = multi.randomString(24) + fast[temp.ref] = func + temp.name = name + if self.rawadd then + self.rawadd = false + else + table.insert(self,true) + self.__connectionAdded(temp, func) + end + return temp + end + + function c:Bind(t) + local temp = fast + fast=t + return temp + end + + function c:Get() + return fast + end + + function c:Remove() + local temp = fast + fast={} + return temp + end + + function c:Hold() + local rets = {multi.hold(self)} + return unpack(rets) + end + + c.connect=c.Connect + c.GetConnection=c.getConnection + c.HasConnections = c.hasConnections + c.GetConnection = c.getConnection + + if func then + c = c .. func + end + + if not(ignoreconn) then + if not self then return c end + self:create(c) + end + + return c +end + +-- Used with ISO Threads +local function isolateFunction(func, env) + if setfenv then + return setfenv(func, env) + else + local env = env or {} + local dmp = string.dump(func) + return load(dmp,"IsolatedThread_PesudoThreading", "bt", env) + end +end + +multi.isolateFunction = isolateFunction + +function multi:Break() + self:Pause() + self.Active=nil + self.OnBreak:Fire(self) +end + +function multi:isPaused() + return not(self.Active) +end + +function multi:isActive() + return self.Active +end + +function multi:getType() + return self.Type +end + +-- Advance Timer stuff +function multi:SetTime(n) + if not n then n=3 end + local c,err=self:newBase(multi.registerType("timemaster")) + c.timer=self:newTimer() + c.timer:Start() + c.set=n + c.link=self + c.OnTimedOut = self:newConnection() + c.OnTimerResolved = self:newConnection() + self._timer=c.timer + function c:Act(dt) + if self.timer:Get()>=self.set then + self.link:Pause() + self.OnTimedOut:Fire(self.link,dt) + self:Destroy() + return true + end + end + return self,err +end + +function multi:ResolveTimer(...) + self._timer:Pause() + self.OnTimerResolved:Fire(self,...) + self:Pause() + return self +end + +-- Timer stuff done +multi.PausedObjects = {} +function multi:Pause() + if self.Type==multi.registerType("rootprocess") then + multi.print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + self._Act = self.Act + self.Act = empty_func + end + return self +end + +function multi:Resume() + if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self.Active==false then + self.Act = self._Act + self.Active=true + end + end + return self +end + +function multi:Destroy() + if self.Type==multi.registerType("process", "processes") or self.Type==multi.registerType("rootprocess") then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i], self) + c[i]:Destroy() + end + local new = {} + for th,proc in pairs(globalThreads) do + if proc == self then + th:Destroy() + table.remove(globalThreads,th) + else + new[th]=proc + end + end + globalThreads = new + multi.setType(self,multi.DestroyedObj) + else + for i=#self.Parent.Mainloop,1,-1 do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self, self.Parent) + table.remove(self.Parent.Mainloop,i) + self.Destroyed = true + break + end + end + self.Act = function() end + end + return self +end + +function multi:Reset(n) + self:Resume() + return self +end + +function multi:isDone() + return self.Active~=true +end + +function multi:create(ref) + ref.UID = multi.generate_uuid7() + self.OnObjectCreated:Fire(ref, self) + return self +end + +function multi:GetCreationTimestamp() + return multi.extract_uuid7_timestamp(self.UID).iso8601 +end + +function multi:setName(name) + self.Name = name + return self +end + +--Constructors [CORE] +local _tid = 0 +function multi:newBase(tp,ins,callback) + if not(self.Type==multi.registerType("rootprocess") or self.Type==multi.registerType("process", "processes")) then multi.error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type==multi.registerType("process", "processes") then + setmetatable(c, {__index = multi}) + else + setmetatable(c, {__index = multi}) + end + c.Active=true + c.func={} + c.funcTM={} + c.funcTMR={} + c.OnBreak = self:newConnection() + c.OnPriorityChanged = self:newConnection() + c.TID = _tid + c.Act=function() end + c.Parent=self + c.creationTime = clock() + c.Type = tp + + function c:Pause() + c.Parent.Pause(self) + return self + end + + function c:Resume() + c.Parent.Resume(self) + return self + end + + _tid = _tid + 1 -- Even if the task isn't scheduled, we want to increment this + + if type(callback) == "function" then + local res, err = callback(tp) + if not res then + return c, err + end + end + + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end + +function multi:newTimeout(timeout) + local c={} + c.Type = multi.registerType(multi.TIMEOUT, "timeouts") + return function(self) self:Destroy() return c end % self:newAlarm(timeout).OnRing +end + +function multi:newTimer() + local c={} + c.Type=multi.registerType("timer", "timers") + local time=0 + local count=0 + local paused=false + function c:Start() + time=clock() + return self + end + function c:Get() + if self:isPaused() then return time end + return (clock()-time)+count + end + function c:isPaused() + return paused + end + c.Reset=c.Start + function c:Pause() + time=self:Get() + paused=true + return self + end + function c:Resume() + paused=false + time=clock()-time + return self + end + self:create(c) + return c +end + +--Core Actors +function multi:newEvent(task, func) + local c,err=self:newBase(multi.registerType("event", "events")) + local task = task or function() end + function c:Act(dt) + local t = task(self) + if t then + self:Pause() + self.returns = t + self.OnEvent:Fire(self,dt) + return true + end + end + function c:SetTask(func) + task=func + return self + end + c.OnEvent = self:newConnection() + if func then + c.OnEvent(func) + end + self:setPriority("core") + c:setName(c.Type) + self:create(c) + return c,err +end + +function multi:newUpdater(skip, func) + local c,err=self:newBase(multi.registerType("updater", "updaters")) + local pos = 1 + local skip = skip or 1 + function c:Act(dt) + if pos >= skip then + pos = 0 + self.OnUpdate:Fire(self,dt) + return true + end + pos = pos+1 + end + function c:SetSkip(n) + skip=n + return self + end + c.OnUpdate = self:newConnection() + c:setName(c.Type) + if func then + c.OnUpdate(func) + end + self:create(c) + return c,err +end + +function multi:newAlarm(set, func) + local c,err=self:newBase(multi.registerType("alarm", "alarms")) + c:setPriority("Low") + c.set=set or 0 + local count = 0 + local t = clock() + function c:Act(dt) + if clock()-t>=self.set then + self:Pause() + self.Active=false + self.OnRing:Fire(self,dt) + t = clock() + return true + end + end + function c:Resume() + self.Parent.Resume(self) + t = count + t + return self + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + t = clock() + return self + end + c.OnRing = self:newConnection() + function c:Pause() + count = clock() + self.Parent.Pause(self) + return self + end + if func then + c.OnRing(func) + end + c:setName(c.Type) + self:create(c) + return c,err +end + +function multi:newLoop(func, notime) + local c,err=self:newBase(multi.registerType("loop", "loops")) + local start=clock() + if notime then + function c:Act(dt) + self.OnLoop:Fire(self,nil,dt) + return true + end + else + function c:Act(dt) + self.OnLoop:Fire(self,clock()-start,dt) + return true + end + end + + c.OnLoop = self:newConnection() + + if func then + c.OnLoop(func) + end + + self:create(c) + c:setName(c.Type) + return c,err +end + +function multi:newStep(start,reset,count,skip) + local c,err=self:newBase(multi.registerType("step", "steps")) + think=1 + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:Act(dt) + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + self.OnStart:Fire(self) + end + self.OnStep:Fire(self,self.pos,dt) + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + self.OnEnd:Fire(self) + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + return true + end + c.Reset=c.Resume + c.OnStart = self:newConnection() + c.OnStep = self:newConnection() + c.OnEnd = self:newConnection() + function c:Break() + self.Active=nil + return self + end + function c:Count(count) + self.count = count + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + return self + end + c:setName(c.Type) + self:create(c) + return c,err +end + +function multi:newTLoop(func, set) + local c,err=self:newBase(multi.registerType("tloop", "tloops")) + c.set=set or 0 + c.timer=self:newTimer() + c.life=0 + c:setPriority("Low") + + function c:Act(dt) + if self.timer:Get() >= self.set then + self.life=self.life+1 + self.timer:Reset() + self.OnLoop:Fire(self, self.life,dt) + return true + end + end + + function c:Set(set) + self.set = set + end + + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + return self + end + + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + return self + end + + c.OnLoop = self:newConnection() + + if func then + c.OnLoop(func) + end + + c:setName(c.Type) + + self:create(c) + + return c,err +end + +function multi:setTimeout(func, t) + thread:newThread("TimeoutThread", function() thread.sleep(t) func() end) +end + +function multi:newTStep(start,reset,count,set) + local c=self:newStep(start,reset,count) + c.Type=multi.registerType("tstep", "tsteps") + c:setPriority("Low") + local reset = reset or math.huge + c.timer=clock() + c.set=set or 1 + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=clock() + self:Resume() + return self + end + function c:Act(dt) + if clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + self.OnStart:Fire(self) + end + self.OnStep:Fire(self,self.pos,dt) + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + self.OnEnd:Fire(self) + self.pos=self.start + end + return true + end + end + function c:Set(set) + self.set = set + end + function c:Reset(n) + if n then self.set=n end + self.timer=clock() + self:Resume() + return self + end + c:setName(c.Type) + self:create(c) + return c +end + +multi.tasks = {} + +function multi:newTask(func) + self.tasks[#self.tasks + 1] = func +end + +local scheduledjobs = {} +local sthread + +function multi:scheduleJob(time,func) + if not sthread then + sthread = thread:newThread("JobScheduler",function() + local time = os.date("*t", os.time()) + local ready = false + while true do + thread.sleep(1) -- Every second we do some tests + time = os.date("*t", os.time()) + for j,job in pairs(scheduledjobs) do + ready = true + for k,v in pairs(job[1]) do + if not (v == time[k]) then + ready = false + end + end + if ready and not job[3] then + job[2]() + job[3] = true + elseif not ready and job[3] then + job[3] = false + end + end + end + end) + end + table.insert(scheduledjobs,{time, func,false}) +end + +local __CurrentProcess = multi +local __CurrentTask + +function multi.getCurrentProcess() + return __CurrentProcess +end + +function multi.getCurrentTask() + return __CurrentTask +end + +function multi:setCurrentProcess() + __CurrentProcess = self +end + +function multi:setCurrentTask() + __CurrentTask = self +end + +function multi:getName() + return self.Name +end + +function multi:getFullName() + return self.Name +end + +local sandcount = 1 + +function multi:newProcessor(name, opts, priority) + local nothread, attach + if type(opts) ~= "table" then + attach = not opts + nothread = opts -- support old params + end + local c = {} + c.Status = {} + setmetatable(c,{__index = multi}) + local name = name or "Processor_" .. sandcount + local maxThreads = -1 + local maxObjects = -1 + local taskhandler = true + + rootBase = multi.newBase + + local function setStatus(status) + table.insert(c.Status,status) + return status + end + + local callback = function(tp) + if maxObjects <0 or #c.Mainloop < maxObjects then + return true + end + return false, setStatus(string.format("Unable to create [%s]: MAX_OBJECTS: '%d' current scheduled objects: '%d'", tp, maxObjects, #c.Mainloop)) + end + + function c:newBase(tp,ins) + return rootBase(self,tp,ins,callback) + end + + sandcount = sandcount + 1 + c.Mainloop = {} + c.Type = multi.registerType("process", "processes") + local Active = nothread or false + local task_delay = 0 + c.Name = name or "" + c.tasks = {} + c.threads = {} + c.startme = {} + c.parent = self + c.OnObjectCreated = self:newConnection() + + local boost = 1 + local handler + + if type(opts) == "table" then + priority = opts.Priority or false + Active = opts.Start or false + maxThreads = opts.MaxThreads or -1 + maxObjects = opts.MaxObjects or -1 + task_delay = opts.TaskDelay or 0 + attach = opts.Attach or false + if opts.TaskHandler == false then -- The default was true + taskhandler = false + end + end + + if priority then + handler = c:createPriorityHandler(c) + else + handler = c:createHandler(c) + end + + if attach then -- Don't create a loop if we are triggering this manually + c.process = self:newLoop(function() + if Active then + c:uManager(true) + handler() + end + end) + c.process.__ignore = true + c.process.isProcessThread = true + c.process.PID = sandcount + c.OnError = c.process.OnError + else + c.OnError = self:newConnection() + end + + c.OnError(multi.error) + + function c:getHandler() + return handler + end + + function c:getThreads() + return c.threads + end + + function c:getFullName() + return c.parent:getFullName() .. "." .. c.Name + end + + function c:getName() + return self.Name + end + + function c:newThread(name, func,...) + if maxThreads < 0 or (#c.threads+#c.startme < maxThreads) then + return thread.newThread(c, name, func, ...) + end + return nil, setStatus(string.format("Unable to create [thread]: '%s' MAX_THREADS: '%d' current scheduled threads: '%d'",name,maxThreads,#c.threads+#c.startme)) + end + + function c:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return c:newThread("Process Threaded Function Handler", func, ...) + end, holdme)() + end + + function c:getMaxThreads() + return maxThreads + end + + function c:setMaxThreads(n) + maxThreads = n + return c + end + + function c:getMaxObjects() + return maxObjects + end + + function c:setMaxObjects(n) + maxObjects = n + return c + end + + function c:boost(count) + boost = count or 1 + if boost > 1 then + self.run = function(dt) + if not Active then return end + for i=1,boost do + c:uManager(dt) + handler() + end + return c + end + else + self.run = function(dt) + if not Active then return end + c:uManager(dt) + handler() + return c + end + end + end + + function c.run(dt) + if not Active then return end + c:uManager(dt) + handler() + return c + end + + function c.isActive() + return Active + end + + function c.Start() + Active = true + return c + end + + function c.Stop() + Active = false + return c + end + + function c:Destroy() + Active = false + c.process:Destroy() + end + + function c:setTaskDelay(delay) + if type(delay) == "function" then + task_delay = delay + else + task_delay = tonumber(delay) or 0 + end + end + + if taskhandler then + c:newThread("Task Handler", function() + local self = multi:getCurrentProcess() + local function task_holder() + return #self.tasks > 0 + end + while true do + if #self.tasks > 0 then + table.remove(self.tasks,1)() + else + thread.hold(task_holder) + end + if task_delay~=0 then + thread.hold(task_delay) + end + end + end).OnError(multi.error) + end + + table.insert(processes,c) + self:create(c) + return c +end + +function multi.hold(func,opt) + if thread.isThread() then return thread.hold(func, opt) end + local proc = multi.getCurrentTask() + if proc then + proc:Pause() + end + local rets + thread:newThread("Hold_func",function() + rets = {thread.hold(func,opt)} + end) + while rets == nil do + multi:uManager() + end + if proc then + proc:Resume() + end + return multi.unpack(rets) +end + +-- Threading stuff +local threadCount = 0 +local threadid = 0 +thread.__threads = {} +local threads = thread.__threads +multi.GlobalVariables={} +local dFunc = function() return true end +thread.requests = {} +local CMD = {} -- We will compare this special local +local interval +local resume, status, create, yield, running = coroutine.resume, coroutine.status, coroutine.create, coroutine.yield, coroutine.running + +local t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none = 1, 2, 3, 4, 5, 6, 7 + +function multi:getThreads() + return threads +end + +function multi:getRunners() + local tasks = {} + for i,v in pairs(self.Mainloop) do + if not v.__ignore then + tasks[#tasks+1] = v + end + end + return tasks +end + +function thread.request(t,cmd,...) + thread.requests[t.thread] = {cmd, multi.pack(...)} +end + +function thread.defer(func) + local th = thread.getRunningThread() + local conn = (th.OnError + th.OnDeath) + conn(function() + func(th) + end) +end + +function thread.getRunningThread() + local threads = globalThreads + local t = coroutine.running() + if t then + for th,process in pairs(threads) do + if t==th.thread then + return th + end + end + end +end + +function thread._Requests() + local t = thread.requests[running()] + if t then + thread.requests[running()] = nil + local cmd,args = t[1],t[2] + thread[cmd](multi.unpack(args)) + end +end + +function thread.exec(func) + func() +end + +function thread.sleep(n) + thread._Requests() + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 1) +end + +local function conn_test(conn) + local ready = false + local args + local func = function(...) + ready = true + args = multi.pack(...) + end + + local ref = conn(func) + return function() + if ready then + conn:Unconnect(ref) + if #args==0 then + return multi.NIL + else + return multi.unpack(args) + end + end + end +end + +function thread.chain(...) + local args = select("#",...) + for i=1,args do + thread.hold(select(i,...)) + end +end + +function thread.hold(n, opt) + thread._Requests() + local opt = opt or {} + if type(opt)=="table" and type(n) == "function" then + interval = opt.interval + if opt.cycles then + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then + return yield(CMD, t_hold, conn_test(n), nil, interval) + elseif type(n) == "table" and n.Hold ~= nil then + return n:Hold(opt) + elseif type(n) == "function" then + return yield(CMD, t_hold, n, nil, interval) + else + multi.error("Invalid argument passed to thread.hold(...) ".. type(n) .. "!") + end +end + +function thread.holdFor(sec,n) + thread._Requests() + return yield(CMD, t_holdF, sec, n or dFunc) +end + +function thread.holdWithin(skip,n) + thread._Requests() + return yield(CMD, t_holdW, skip or 1, n or dFunc) +end + +function thread.skip(n) + thread._Requests() + return yield(CMD, t_skip, n or 1) +end + +function thread.kill(msg) + msg = msg or "thread killed!" + multi.error(msg) +end + +function thread.yield() + thread._Requests() + return yield(CMD, t_yield) +end + +function thread.isThread() + local a,b = running() + if b then + -- We are dealing with luajit compat or 5.2+ + return not(b) + else + return a~=nil + end +end + +function thread.getCores() + return thread.__CORES +end + +function thread.set(name,val) + multi.GlobalVariables[name]=val + return true +end + +function thread.get(name) + return multi.GlobalVariables[name] +end + +function thread.waitFor(name) + thread.hold(function() return thread.get(name)~=nil end) + return thread.get(name) +end + +local function cleanReturns(...) + local returns = multi.pack(...) + local rets = {} + local ind = 0 + for i=#returns,1,-1 do + if returns[i] then + ind = i + break + end + end + return multi.unpack(returns,1,ind) +end + +function thread.pushStatus(...) + local t = __CurrentConnectionThread or thread.getRunningThread() + t.statusconnector:Fire(...) +end + +function thread:newFunctionBase(generator, holdme, TYPE) + return function() + local UID = multi.generate_uuid7() + local tfunc = { + GetCreationTimestamp = function() return multi.extract_uuid7_timestamp(UID).iso8601 end, + } + tfunc.Active = true + function tfunc:Pause() + self.Active = false + end + function tfunc:Resume() + self.Active = true + end + function tfunc:holdMe(b) + holdme = b + end + local function noWait() + return nil, "Function is paused" + end + local rets, err + local function wait() + if thread.isThread() then + return thread.hold(function() + if err then + return multi.NIL, err + elseif rets then + local g = rets + rets = nil + return cleanReturns((g[1] or multi.NIL),g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) + end + end) + else + while not rets and not err do + multi:uManager() + end + local g = rets + rets = nil + if err then + return nil,err + end + return cleanReturns(g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15],g[16]) + end + end + tfunc.__call = function(th,...) + if th.Active == false then + if holdme then + return nil, "Function is paused" + end + return { + isTFunc = true, + wait = noWait, + connect = function(f) + f(nil,"Function is paused") + end + } + end + local t = generator(...) + t.OnDeath(function(...) rets = multi.pack(...) end) + t.OnError(function(self,e) err = e end) + if holdme then + return wait() + end + local temp = { + OnStatus = multi:getCurrentProcess():newConnection(true), + OnError = multi:getCurrentProcess():newConnection(true), + OnReturn = multi:getCurrentProcess():newConnection(true), + isTFunc = true, + wait = wait, + getReturns = function() + return multi.unpack(rets) + end, + connect = function(f) + local tempConn = multi:getCurrentProcess():newConnection(true) + t.OnDeath(function(...) if f then f(...) else tempConn:Fire(...) end end) + t.OnError(function(self,err) if f then f(nil,err) else tempConn:Fire(nil,err) end end) + return tempConn + end + } + t.OnDeath(function(...) temp.OnReturn:Fire(...) end) + t.OnError(function(self,err) temp.OnError:Fire(err) temp.OnError(multi.error) end) + t.linkedFunction = temp + t.statusconnector = temp.OnStatus + return temp + end + setmetatable(tfunc, tfunc) + tfunc.Type = TYPE or multi.registerType("function", "functions") + return tfunc + end +end + +function thread:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return thread:newThread("Free Threaded Function Handler", func, ...) + end, holdme)() +end + +function thread:newProcessor(name, nothread, priority) + -- Inactive proxy proc + local process = multi:getCurrentProcess() + local proc = process:newProcessor(name, true) + local thread_proc = process:newProcessor(name).Start() + local Active = true + + local handler + if priority then + handler = thread_proc:createPriorityHandler(c) + else + handler = thread_proc:createHandler(c) + end + + function proc:getThreads() + return thread_proc.threads + end + + function proc:getFullName() + return thread_proc.parent:getFullName() .. "." .. self.Name + end + + function proc:getName() + return thread_proc.Name + end + + function proc:isActive() + return Active + end + + function proc:newThread(name, func, ...) + return thread.newThread(thread_proc, name, func, ...) + end + + function proc:newFunction(func, holdme) + return thread:newFunctionBase(function(...) + return thread_proc:newThread("TProc Threaded Function Handler", func, ...) + end, holdme)() + end + + function proc.Start() + Active = true + return proc + end + + function proc.Stop() + Active = false + return proc + end + + function proc:Destroy() + Active = false + thread_proc:Destroy() + end + + proc.OnObjectCreated(function(obj) + if not obj.Act then return end + multi.print("Converting "..obj.Type.." to thread!") + thread_proc:newThread(function() + obj.reallocate = empty_func + while true do + thread.hold(function() return Active end) + obj:Act() + end + end) + end) + + process:create(proc) + + return proc +end + +-- A cross version way to set enviroments, not the same as fenv though +function multi.setEnv(func,env) + local f = string.dump(func) + local chunk = load(f, "env", "bt", env) + return chunk +end + +function thread:newThread(name, func, ...) + multi.OnLoad:Fire() -- This was done incase a threaded function was called before mainloop/uManager was called + if type(name) == "function" then + func = name + name = "UnnamedThread_"..multi.randomString(16) + end + local c={nil,nil,nil,nil,nil,nil,nil} + c.TempRets = {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil} + c.startArgs = multi.pack(...) + c.ref={} + c.Name=name + c.thread=create(func) + c.sleep=1 + c.Type = multi.registerType("thread", "threads") + c.TID = threadid + c.firstRunDone=false + c._isPaused = false + c.returns = {} + c.isError = false + + if self.Type == multi.registerType("process", "processes") then + c.OnError = self:newConnection(true,nil,true) + c.OnDeath = self:newConnection(true,nil,true) + else + c.OnError = threadManager:newConnection(true,nil,true) + c.OnDeath = threadManager:newConnection(true,nil,true) + end + + c.OnError(multi.error) + + function c:getName() + return c.Name + end + + function c:isPaused() + return self._isPaused + end + + function c:GetCreationTimestamp() + return multi.extract_uuid7_timestamp(self.UID).iso8601 + end + + local resumed = false + function c:Pause() + if not self._isPaused then + thread.request(self, "exec", function() + thread.hold(function() + return resumed + end) + resumed = false + self._isPaused = false + end) + self._isPaused = true + end + return self + end + + function c:Resume() + resumed = true + return self + end + + function c:Kill() + thread.request(self, "kill") + return self + end + + function c:Sleep(n) + thread.request(self, "exec", function() + thread.sleep(n) + resumed = false + end) + return self + end + + function c:Hold(n,opt) + thread.request(self, "exec", function() + thread.hold(n, opt) + resumed = false + end) + return self + end + + c.Destroy = c.Kill + if thread.isThread() then + if self.Type == multi.registerType("process", "processes") then + table.insert(self.startme, c) + else + table.insert(threadManager.startme, c) + end + else + if self.Type == multi.registerType("process", "processes") then + table.insert(self.startme, c) + else + table.insert(threadManager.startme, c) + end + end + + globalThreads[c] = multi + threadid = threadid + 1 + if self.Type == multi.registerType("process", "processes") then + self:create(c) + else + threadManager:create(c) + end + c.creationTime = clock() + return c +end + +function thread:newISOThread(name, func, env, ...) + local func = func or name + local env = env or {} + if not env.thread then + env.thread = thread + end + if not env.multi then + env.multi = multi + end + if type(name) == "function" then + name = "Thread#"..threadCount + end + local func = isolateFunction(func, env) + return thread:newThread(name, func, ...) +end + +multi.newThread = thread.newThread +multi.newISOThread = thread.newISOThread + +local t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 +local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 +local ret,_ +local task, thd, ref, ready +local switch = { + function(th,co)--hold + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16 = th.func() + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--sleep + if clock() - th.time>=th.sec then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdf + if clock() - th.intervalR>=th.interval then + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif clock() - th.time>=th.sec then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--skip + th.pos = th.pos + 1 + if th.count==th.pos then + th.task = t_none + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + end, + function(th,co)--holdw + if clock() - th.intervalR>=th.interval then + th.pos = th.pos + 1 + t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15 = th.func() + if t0 then + if t0 then + if t0==NIL then t0 = nil end + th.task = t_none + end + th.task = "" + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + elseif th.count==th.pos then + th.task = t_none + t0 = nil + t1 = multi.TIMEOUT + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end + th.intervalR = clock() + end + end, + function(th,co)--yield + _,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16=resume(co,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15) + end, + function() end--none +} + +setmetatable(switch,{__index=function() return function() end end}) + +local cmds = {-- ipart: t_hold, t_sleep, t_holdF, t_skip, t_holdW, t_yield, t_none <-- Order + function(th,arg1,arg2,arg3) + th.func = arg1 + th.task = t_hold + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.time = clock() + th.task = t_sleep + end, + function(th,arg1,arg2,arg3) + th.sec = arg1 + th.func = arg2 + th.task = t_holdF + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.task = t_skip + end, + function(th,arg1,arg2,arg3) + th.count = arg1 + th.pos = 0 + th.func = arg2 + th.task = t_holdW + th.time = clock() + th.interval = arg3 or 0 + th.intervalR = clock() + end, + function(th,arg1,arg2,arg3) + th.task = t_yield + end, + function() end +} +setmetatable(cmds,{__index=function() return function() end end}) +local co_status +co_status = { + ["suspended"] = function(thd,ref,task,i,th) + switch[task](ref,thd) + cmds[r1](ref,r2,r3,r4,r5) + if ret ~= CMD and _ ~= nil then -- The rework makes this necessary + co_status["dead"](thd,ref,task,i,th) + end + r1=nil r2=nil r3=nil r4=nil r5=nil + end, + ["normal"] = function(thd,ref) end, + ["running"] = function(thd,ref) end, + ["dead"] = function(thd,ref,task,i,th) + if ref.__processed then table.remove(th,i) return end + if _ then + ref.OnDeath:Fire(ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + else + ref.OnError:Fire(ref,ret,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16) + multi.error(ref, ret) + end + if i then + table.remove(th,i) + else + for i,v in pairs(th) do + if v.thread==thd then + table.remove(th,i) + break + end + end + end + _=nil r1=nil r2=nil r3=nil r4=nil r5=nil r6=nil r7=nil r8=nil r9=nil r10=nil r11=nil r12=nil r13=nil r14=nil r15=nil r16=nil + ref.__processed = true + end, +} + +function multi:createHandler() + local threads, startme = self.threads, self.startme + return coroutine.wrap(function() + local temp_start + while true do + while #startme>0 do + temp_start = table.remove(startme) + _, ret, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = resume(temp_start.thread, multi.unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) + table.insert(threads, temp_start) + yield() + end + for i=#threads,1,-1 do + ref = threads[i] + if ref then + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd, ref, task, i, threads) + end + yield() + end + yield() + end + end) +end + +function multi:createPriorityHandler() + local threads, startme = self.threads, self.startme + return coroutine.wrap(function() + local temp_start + while true do + while #startme>0 do + temp_start = table.remove(startme) + _, ret, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16 = resume(temp_start.thread, multi.unpack(temp_start.startArgs)) + co_status[status(temp_start.thread)](temp_start.thread, temp_start, t_none, nil, threads) + table.insert(threads, temp_start) + end + for i=#threads,1,-1 do + ref = threads[i] + if ref then + task = ref.task + thd = ref.thread + ready = ref.__ready + co_status[status(thd)](thd, ref, task, i, threads) + end + end + yield() + end + end) +end + +function multi:newService(func) -- Priority managed threads + local c = {} + c.Type = multi.registerType("service", "services") + c.OnStopped = self:newConnection() + c.OnStarted = self:newConnection() + local Service_Data = {} + local active + local time + local p = multi.Priority_Normal + local ap + local task = thread.sleep + local scheme = 1 + + function c.Start() + if not active then + time = self:newTimer() + time:Start() + active = true + c:OnStarted(c, Service_Data) + end + return c + end + + local function process() + thread.hold(function() + return active + end) + func(c, Service_Data) + task(ap) + return c + end + + local th = thread:newThread("Service_Handler",function() + while true do + process() + end + end) + + th.OnError = c.OnError -- use the threads onerror as our own + th.OnError(multi.error) + + function c.Destroy() + th:kill() + c.Stop() + multi.setType(c,multi.DestroyedObj) + return c + end + + function c:SetScheme(n) + if type(self)=="number" then n = self end + scheme = n + if math.abs(n)==1 then + ap = (p^(1/3))/10 + if ap==.1 then task = thread.yield end + task = thread.sleep + elseif math.abs(n)==2 then + ap = math.abs(p-1)*32+1 + task = thread.skip + elseif math.abs(n)==3 then + -- This is a time based pirority manager. Things that take long to run get + end + return c + end + + function c.Stop() + if active then + c:OnStopped(c) + Service_Data = {} + time:Reset() + time:Pause() + time = nil + active = false + end + return c + end + + function c.Pause() + if active then + time:Pause() + active = false + end + return c + end + + function c.Resume() + if not active then + time:Resume() + active = true + end + return c + end + + function c.GetUpTime() + return time:Get() + end + + function c:SetPriority(pri) + if type(self)=="number" then pri = self end + p = pri + c.SetScheme(scheme) + return c + end + + self:create(c) + + return c +end + +-- Multi runners +function multi:mainloopRef() + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager = self.uManagerRef + if not isRunning then + isRunning = true + mainloopActive = true + local Loop=self.Mainloop + local ctask + multi.OnLoad:Fire() + while mainloopActive do + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + ctask = __CurrentTask + if ctask then ctask:Act() end + __CurrentProcess = self + end + end + else + return nil, "Already Running!" + end +end + +multi.mainloop = multi.mainloopRef + +function multi:p_mainloop() + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager = self.uManagerRefP1 + if not isRunning then + isRunning=true + mainloopActive = true + local Loop = self.Mainloop + local ctask + multi.OnLoad:Fire() + while mainloopActive do + for task=#Loop,1,-1 do + __CurrentTask = Loop[task] + ctask = __CurrentTask + for i=1,9 do + if PList[i]%ctask.Priority == 0 then + ctask:Act() + __CurrentProcess = self + end + end + end + end + else + return nil, "Already Running!" + end +end + +local function doOpt() + function thread.hold(n,opt) + thread._Requests() + local opt = opt or {} + if type(opt)=="table" then + interval = opt.interval + if opt.cycles then + return yield(CMD, t_holdW, opt.cycles or 1, n or dFunc, interval) + elseif opt.sleep then + return yield(CMD, t_holdF, opt.sleep, n or dFunc, interval) + elseif opt.skip then + return yield(CMD, t_skip, opt.skip or 1, nil, interval) + end + end + if type(n) == "number" then + thread.getRunningThread().lastSleep = clock() + return yield(CMD, t_sleep, n or 0, nil, interval) + elseif type(n) == "table" and n.Type == multi.registerType("connector", "connections") then + local rdy = function() + return false + end + n(function(a1,a2,a3,a4,a5,a6) + rdy = function() + if a1==nil then + return NIL,a2,a3,a4,a5,a6 + end + return a1,a2,a3,a4,a5,a6 + end + end) + return yield(CMD, t_hold, function() + return rdy() + end, nil, interval) + elseif type(n) == "function" then + local cache = string.dump(n) + local f_str = tostring(n) + local good = true + for i=1,#func_cache do + if func_cache[i][1] == cache and func_cache[i][2] ~= f_str and not func_cache[i][3] then + multi:getOptimizationConnection():Fire("It's better to store a function to a variable than to use an anonymous function within the hold method!\n" .. debug.traceback()) + func_cache[i][3] = true + good = false + end + end + if good then + table.insert(func_cache, {cache, f_str}) + end + return yield(CMD, t_hold, n or dFunc, nil, interval) + else + multi.error("Invalid argument passed to thread.hold(...)!") + end + end +end + +local init = false +multi.settingsHook = multi:newConnection() +function multi.init(settings, realsettings) + if settings == multi then settings = realsettings end + + if type(settings)=="table" then + + multi.defaultSettings = settings + + if settings.priority then + multi.mainloop = multi.p_mainloop + else + multi.mainloop = multi.mainloopRef + end + + if not init then + + if settings.findopt then + find_optimization = true + doOpt() + multi.enableOptimization:Fire(multi, thread) + end + + if settings.debugging then + require("multi.integration.debugManager") + end + + multi.settingsHook:Fire(settings) + end + end + init = true + return _G["$multi"].multi,_G["$multi"].thread +end + +function multi:uManager(dt) + if self.Active then + __CurrentProcess = self + multi.OnPreLoad:Fire() + self.uManager=self.uManagerRef + multi.OnLoad:Fire() + end +end + +function multi:uManagerRefP1(dt) + if self.Active then + __CurrentProcess = self + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + for P=1,9 do + if PList[P]%__CurrentTask.Priority==0 then + __CurrentTask:Act(dt) + __CurrentProcess = self + end + end + end + end +end + +function multi:uManagerRef(dt) + if self.Active then + __CurrentProcess = self + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + __CurrentTask = Loop[_D] + __CurrentTask:Act(dt) + __CurrentProcess = self + end + end +end + +-------- +-- UTILS +-------- + +function multi.isTimeout(res) + if type(res) == "table" then + return res.Type == multi.TIMEOUT + end + return res == multi.TIMEOUT +end + +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end + +math.randomseed(os.time()) + +function multi:enableLoadDetection() + if multi.maxSpd then return end + -- here we are going to run a quick benchMark solo + local temp = self:newProcessor() + local t = clock() + local stop = false + temp:benchMark(.01):OnBench(function(time,steps) + stop = steps + end) + while not stop do + temp:uManager() + end + temp:Destroy() + multi.maxSpd = stop +end + +local lastVal = 0 +local last_step = 0 + +function multi:getLoad() + if not multi.maxSpd then multi:enableLoadDetection() end + local val = nil + local bench + local bb + self:benchMark(.01).OnBench(function(time,steps) + bench = steps + bb = steps + end) + _,timeout = multi.hold(function() + return bench + end,{sleep=.012}) + if timeout or not bench then + bench = 0 + bb = 0 + end + bench = bench^1.5 + val = math.ceil((1-(bench/(multi.maxSpd/2.2)))*100) + if val<0 then val = 0 end + if val > 100 then val = 100 end + lastVal = val + last_step = bb*100 + return val,last_step +end + +function multi:setPriority(s) + if not self:IsAnActor() or self.Type == multi.registerType("process", "processes") then return end + if type(s)=="number" then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=="very high" or s:lower()=="vh" then + self.Priority=self.Priority_Very_High + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='a' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='b' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=="very low" or s:lower()=="vl" then + self.Priority=self.Priority_Very_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + self.OnPriorityChanged:Fire(self, self.Priority) + end + if not self.PrioritySet then + self.defPriority = self.Priority + self.PrioritySet = true + end + return self +end + +function multi:ResetPriority() + self.Priority = self.defPriority + return self +end + +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end + +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end + +function multi:getChildren() + return self.Mainloop +end + +function multi:getVersion() + return multi.Version +end + +function multi:getPlatform() + if love then + if love.thread then + return "love2d" + end + else + return "lanes" + end +end + +function multi:canSystemThread() + return false +end + +function multi:benchMark(sec,p,pt) + local c = 0 + local temp=self:newLoop(function(self,t) + if t>sec then + if pt then + multi.print(pt.." "..c.." Steps in "..sec.." second(s)!") + end + self.OnBench:Fire(sec,c) + self:Destroy() + else + c=c+1 + end + end) + temp.OnBench = self:newConnection() + temp:setPriority(p or 1) + return temp +end + +function multi.Round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function multi.AlignTable(tab) + local longest = {} + local columns = #tab[1] + local rows = #tab + for i=1, columns do + longest[i] = -math.huge + end + for i = 1,rows do + for j = 1,columns do + tab[i][j] = tostring(tab[i][j]) + if #tab[i][j]>longest[j] then + longest[j] = #tab[i][j] + end + end + end + for i = 1,rows do + for j = 1,columns do + if tab[i][j]~=nil and #tab[i][j]= "Lua 5.2" or jit then + setmetatable(multi.m, {__gc = multi.m.onexit}) +else + multi.m.sentinel = newproxy(true) + getmetatable(multi.m.sentinel).__gc = multi.m.onexit +end + +threadManager = multi:newProcessor("Global_Thread_Manager", nil, true).Start() +threadManager.tasks = multi.tasks -- The main multi interface is a bit different. + +function multi:setTaskDelay(delay) + threadManager:setTaskDelay(delay) +end + +function multi:getThreadManagerProcess() + return threadManager +end + +function multi:getHandler() + return threadManager:getHandler() +end + return multi \ No newline at end of file diff --git a/integration/debugManager/init.lua b/integration/debugManager/init.lua index c467547..f5e015a 100644 --- a/integration/debugManager/init.lua +++ b/integration/debugManager/init.lua @@ -1,106 +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 +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 diff --git a/integration/effilManager/init.lua b/integration/effilManager/init.lua index f5c1277..e1aac7a 100644 --- a/integration/effilManager/init.lua +++ b/integration/effilManager/init.lua @@ -1,46 +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 +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 } \ No newline at end of file diff --git a/integration/lanesManager/extensions.lua b/integration/lanesManager/extensions.lua index 618762f..6a1db82 100644 --- a/integration/lanesManager/extensions.lua +++ b/integration/lanesManager/extensions.lua @@ -1,393 +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 +--[[ +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") \ No newline at end of file diff --git a/integration/lanesManager/init.lua b/integration/lanesManager/init.lua index f9d0848..8d6ad62 100644 --- a/integration/lanesManager/init.lua +++ b/integration/lanesManager/init.lua @@ -1,205 +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 -} +--[[ +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 +} diff --git a/integration/lanesManager/threads.lua b/integration/lanesManager/threads.lua index 42433df..b7cf0bb 100644 --- a/integration/lanesManager/threads.lua +++ b/integration/lanesManager/threads.lua @@ -1,151 +1,151 @@ ---[[ -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(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) - local THREAD = {} - 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) - __GlobalLinda:set(name, val) - end - - function THREAD.get(name) - return __GlobalLinda:get(name) - end - - 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 - function c.print(...) - c.queue:push("Q", table.concat(multi.pack(...), "\t")) - end - function c.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!\1") - end - - function THREAD.sync() - -- Maybe do something... - end - - 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()) - __SleepingLinda:receive(.001, "__non_existing_variable") - end - repeat - wait() - until n() - end - - local GLOBAL = {} - setmetatable(GLOBAL, { - __index = function(t, k) - return __GlobalLinda:get(k) - end, - __newindex = function(t, k, v) - __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,st,c,onexit) - return INIT(g,s,st,c,onexit) +--[[ +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(__GlobalLinda, __SleepingLinda, __StatusLinda, __Console) + local THREAD = {} + 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) + __GlobalLinda:set(name, val) + end + + function THREAD.get(name) + return __GlobalLinda:get(name) + end + + 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 + function c.print(...) + c.queue:push("Q", table.concat(multi.pack(...), "\t")) + end + function c.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!\1") + end + + function THREAD.sync() + -- Maybe do something... + end + + 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()) + __SleepingLinda:receive(.001, "__non_existing_variable") + end + repeat + wait() + until n() + end + + local GLOBAL = {} + setmetatable(GLOBAL, { + __index = function(t, k) + return __GlobalLinda:get(k) + end, + __newindex = function(t, k, v) + __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,st,c,onexit) + return INIT(g,s,st,c,onexit) end} \ No newline at end of file diff --git a/integration/loveManager/extensions.lua b/integration/loveManager/extensions.lua index ee29baf..ce0777d 100644 --- a/integration/loveManager/extensions.lua +++ b/integration/loveManager/extensions.lua @@ -1,244 +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 +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 \ No newline at end of file diff --git a/integration/loveManager/init.lua b/integration/loveManager/init.lua index 44ec815..5294646 100644 --- a/integration/loveManager/init.lua +++ b/integration/loveManager/init.lua @@ -1,137 +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 -} +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 +} diff --git a/integration/loveManager/threads.lua b/integration/loveManager/threads.lua index 4ad7474..17f5bd5 100644 --- a/integration/loveManager/threads.lua +++ b/integration/loveManager/threads.lua @@ -1,228 +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:" where is the Lua stringified version of the function -function tableToFunctionString(t) - if type(t) == "nil" then return "\1\2:nil:" end - if type(t) == "function" then return "\1\2:func:"..string.dump(t) end - if type(t) ~= "table" then return t end - local newtable = {} - for k, v in pairs(t) do - if type(v) == "function" then - newtable[k] = "\1\2:func:"..string.dump(v) - elseif type(v) == "table" then - newtable[k] = tableToFunctionString(v) - elseif isLoveObject(v) then - newtable[k] = v - elseif type(v) == "userdata" then - newtable[k] = tostring(v) - else - newtable[k] = v - end - end - return newtable -end - --- Converts strings with the value "\1\2:func:" back to functions -function functionStringToTable(t) - if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end - if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end - if type(t) ~= "table" then return t end - for k, v in pairs(t) do - if type(v) == "string" then - if v:sub(1, 8) == "\1\2:func:" then - t[k] = loadstring(v:sub(9, -1)) - else - t[k] = v - end - elseif type(v) == "table" then - t[k] = functionStringToTable(v) - else - t[k] = v - end - end - if t.init then - t:init() - end - return t -end - -local function packValue(t) - return tableToFunctionString(t) -end - -local function unpackValue(t) - return functionStringToTable(t) -end - -local function createTable(n) - 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 +--[[ +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:" where is the Lua stringified version of the function +function tableToFunctionString(t) + if type(t) == "nil" then return "\1\2:nil:" end + if type(t) == "function" then return "\1\2:func:"..string.dump(t) end + if type(t) ~= "table" then return t end + local newtable = {} + for k, v in pairs(t) do + if type(v) == "function" then + newtable[k] = "\1\2:func:"..string.dump(v) + elseif type(v) == "table" then + newtable[k] = tableToFunctionString(v) + elseif isLoveObject(v) then + newtable[k] = v + elseif type(v) == "userdata" then + newtable[k] = tostring(v) + else + newtable[k] = v + end + end + return newtable +end + +-- Converts strings with the value "\1\2:func:" back to functions +function functionStringToTable(t) + if type(t) == "string" and t:sub(1, 8) == "\1\2:func:" then return loadstring(t:sub(9, -1)) end + if type(t) == "string" and t:sub(1, 7) == "\1\2:nil:" then return nil end + if type(t) ~= "table" then return t end + for k, v in pairs(t) do + if type(v) == "string" then + if v:sub(1, 8) == "\1\2:func:" then + t[k] = loadstring(v:sub(9, -1)) + else + t[k] = v + end + elseif type(v) == "table" then + t[k] = functionStringToTable(v) + else + t[k] = v + end + end + if t.init then + t:init() + end + return t +end + +local function packValue(t) + return tableToFunctionString(t) +end + +local function unpackValue(t) + return functionStringToTable(t) +end + +local function createTable(n) + 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 } \ No newline at end of file diff --git a/integration/lovrManager/extensions.lua b/integration/lovrManager/extensions.lua index ddc66c6..7624057 100644 --- a/integration/lovrManager/extensions.lua +++ b/integration/lovrManager/extensions.lua @@ -1,204 +1,204 @@ ---[[ -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() -GLOBAL = multi.integration.GLOBAL -THREAD = multi.integration.THREAD -function multi:newSystemThreadedQueue(name) - local c = {} - c.Name = name - local fRef = {"func",nil} - function c:init() - local q = {} - q.chan = lovr.thread.getChannel(self.Name) - function q:push(dat) - if type(dat) == "function" then - fRef[2] = THREAD.dump(dat) - self.chan:push(fRef) - return - else - self.chan:push(dat) - end - end - function q:pop() - local dat = self.chan:pop() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - function q:peek() - local dat = self.chan:peek() - if type(dat)=="table" and dat[1]=="func" then - return THREAD.loadDump(dat[2]) - else - return dat - end - end - return q - end - THREAD.package(name,c) - return c -end -function multi:newSystemThreadedTable(name) - local c = {} - c.name = name - function c:init() - return THREAD.createTable(self.name) - end - THREAD.package(name,c) - return c -end -local jqc = 1 -function multi:newSystemThreadedJobQueue(n) - local c = {} - c.cores = n or THREAD.getCores() - c.registerQueue = {} - c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - 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 - function c:doToAll(func) - local f = THREAD.dump(func) - for i = 1, self.cores do - self.queueAll:push({allfunc,f}) - end - allfunc = allfunc + 1 - end - function c:registerFunction(name,func) - if self.funcs[name] then - error("A function by the name "..name.." has already been registered!") - end - self.funcs[name] = func - end - function c:pushJob(name,...) - self.id = self.id + 1 - self.queue:push{name,self.id,...} - return self.id - end - function c:isEmpty() - return queueJob:peek()==nil - end - local nFunc = 0 - function c:newFunction(name,func,holup) -- This registers with the queue - if type(name)=="function" then - holup = func - func = name - name = "JQ_Function_"..nFunc - end - nFunc = nFunc + 1 - c:registerFunction(name,func) - return thread:newFunction(function(...) - local id = c:pushJob(name,...) - local link - local rets - link = c.OnJobCompleted(function(jid,...) - if id==jid then - rets = multi.pack(...) - link:Destroy() - 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("lovr.timer") - local function atomic(channel) - return channel:pop() - end - local clock = os.clock - local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") - local queue = lovr.thread.getChannel("__JobQueue_"..jqc.."_queue") - local queueReturn = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") - local lastProc = clock() - local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") - local registry = {} - _G["__QR"] = queueReturn - setmetatable(_G,{__index = funcs}) - thread:newThread("startUp",function() - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - end - end) - thread:newThread("runner",function() - thread.sleep(.1) - while true do - thread.yield() - local all = queueAll:peek() - if all and not registry[all[1]] then - lastProc = os.clock() - THREAD.loadDump(queueAll:pop()[2])() - end - local dat = queue:performAtomic(atomic) - if dat then - lastProc = os.clock() - local name = table.remove(dat,1) - local id = table.remove(dat,1) - local tab = {funcs[name](multi.unpack(dat))} - table.insert(tab,1,id) - queueReturn:push(tab) - end - end - end):OnError(function(...) - error(...) - end) - thread:newThread("Idler",function() - while true do - thread.yield() - if clock()-lastProc> 2 then - THREAD.sleep(.05) - else - THREAD.sleep(.001) - end - end - end) - multi:mainloop() - end,jqc) - end - jqc = jqc + 1 - return c +--[[ +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() +GLOBAL = multi.integration.GLOBAL +THREAD = multi.integration.THREAD +function multi:newSystemThreadedQueue(name) + local c = {} + c.Name = name + local fRef = {"func",nil} + function c:init() + local q = {} + q.chan = lovr.thread.getChannel(self.Name) + function q:push(dat) + if type(dat) == "function" then + fRef[2] = THREAD.dump(dat) + self.chan:push(fRef) + return + else + self.chan:push(dat) + end + end + function q:pop() + local dat = self.chan:pop() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + function q:peek() + local dat = self.chan:peek() + if type(dat)=="table" and dat[1]=="func" then + return THREAD.loadDump(dat[2]) + else + return dat + end + end + return q + end + THREAD.package(name,c) + return c +end +function multi:newSystemThreadedTable(name) + local c = {} + c.name = name + function c:init() + return THREAD.createTable(self.name) + end + THREAD.package(name,c) + return c +end +local jqc = 1 +function multi:newSystemThreadedJobQueue(n) + local c = {} + c.cores = n or THREAD.getCores() + c.registerQueue = {} + c.funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + 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 + function c:doToAll(func) + local f = THREAD.dump(func) + for i = 1, self.cores do + self.queueAll:push({allfunc,f}) + end + allfunc = allfunc + 1 + end + function c:registerFunction(name,func) + if self.funcs[name] then + error("A function by the name "..name.." has already been registered!") + end + self.funcs[name] = func + end + function c:pushJob(name,...) + self.id = self.id + 1 + self.queue:push{name,self.id,...} + return self.id + end + function c:isEmpty() + return queueJob:peek()==nil + end + local nFunc = 0 + function c:newFunction(name,func,holup) -- This registers with the queue + if type(name)=="function" then + holup = func + func = name + name = "JQ_Function_"..nFunc + end + nFunc = nFunc + 1 + c:registerFunction(name,func) + return thread:newFunction(function(...) + local id = c:pushJob(name,...) + local link + local rets + link = c.OnJobCompleted(function(jid,...) + if id==jid then + rets = multi.pack(...) + link:Destroy() + 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("lovr.timer") + local function atomic(channel) + return channel:pop() + end + local clock = os.clock + local funcs = THREAD.createStaticTable("__JobQueue_"..jqc.."_table") + local queue = lovr.thread.getChannel("__JobQueue_"..jqc.."_queue") + local queueReturn = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueReturn") + local lastProc = clock() + local queueAll = lovr.thread.getChannel("__JobQueue_"..jqc.."_queueAll") + local registry = {} + _G["__QR"] = queueReturn + setmetatable(_G,{__index = funcs}) + thread:newThread("startUp",function() + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + end + end) + thread:newThread("runner",function() + thread.sleep(.1) + while true do + thread.yield() + local all = queueAll:peek() + if all and not registry[all[1]] then + lastProc = os.clock() + THREAD.loadDump(queueAll:pop()[2])() + end + local dat = queue:performAtomic(atomic) + if dat then + lastProc = os.clock() + local name = table.remove(dat,1) + local id = table.remove(dat,1) + local tab = {funcs[name](multi.unpack(dat))} + table.insert(tab,1,id) + queueReturn:push(tab) + end + end + end):OnError(function(...) + error(...) + end) + thread:newThread("Idler",function() + while true do + thread.yield() + if clock()-lastProc> 2 then + THREAD.sleep(.05) + else + THREAD.sleep(.001) + end + end + end) + multi:mainloop() + end,jqc) + end + jqc = jqc + 1 + return c end \ No newline at end of file diff --git a/integration/lovrManager/init.lua b/integration/lovrManager/init.lua index 3d6943d..72cb0ff 100644 --- a/integration/lovrManager/init.lua +++ b/integration/lovrManager/init.lua @@ -1,98 +1,98 @@ ---[[ -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. -]] --- TODO make compatible with lovr -if ISTHREAD then - error("You cannot require the lovrManager from within a thread!") -end -local ThreadFileData = [[ -ISTHREAD = true -THREAD = require("multi.integration.lovrManager.threads") -- order is important! -sThread = THREAD -__IMPORTS = {...} -__FUNC__=table.remove(__IMPORTS,1) -__THREADID__=table.remove(__IMPORTS,1) -__THREADNAME__=table.remove(__IMPORTS,1) -stab = THREAD.createStaticTable(__THREADNAME__) -GLOBAL = THREAD.getGlobal() -multi, thread = require("multi").init() -stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} -]] -local multi, thread = require("multi.compat.lovr2d"):init() -local THREAD = {} -__THREADID__ = 0 -__THREADNAME__ = "MainThread" -_G.THREAD_NAME = "MAIN_THREAD" -_G.THREAD_ID = 0 -multi.integration={} -multi.integration.lovr2d={} -local THREAD = require("multi.integration.lovrManager.threads") -local GLOBAL = THREAD.getGlobal() -local THREAD_ID = 1 -local OBJECT_ID = 0 -local stf = 0 -function THREAD:newFunction(func,holup) - stf = stf + 1 - return function(...) - local t = multi:newSystemThread("STF"..stf,func,...) - return thread:newFunction(function() - return thread.hold(function() - if t.stab["returns"] then - local dat = t.stab.returns - t.stab.returns = nil - return multi.unpack(dat) - end - end) - end,holup)() - end -end -function multi:newSystemThread(name,func,...) - local c = {} - c.name = name - c.ID=THREAD_ID - 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 -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.lovrManager.extensions") -multi.print("Integrated lovr Threading!") -return {init=function() - return GLOBAL,THREAD +--[[ +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. +]] +-- TODO make compatible with lovr +if ISTHREAD then + error("You cannot require the lovrManager from within a thread!") +end +local ThreadFileData = [[ +ISTHREAD = true +THREAD = require("multi.integration.lovrManager.threads") -- order is important! +sThread = THREAD +__IMPORTS = {...} +__FUNC__=table.remove(__IMPORTS,1) +__THREADID__=table.remove(__IMPORTS,1) +__THREADNAME__=table.remove(__IMPORTS,1) +stab = THREAD.createStaticTable(__THREADNAME__) +GLOBAL = THREAD.getGlobal() +multi, thread = require("multi").init() +stab["returns"] = {THREAD.loadDump(__FUNC__)(multi.unpack(__IMPORTS))} +]] +local multi, thread = require("multi.compat.lovr2d"):init() +local THREAD = {} +__THREADID__ = 0 +__THREADNAME__ = "MainThread" +_G.THREAD_NAME = "MAIN_THREAD" +_G.THREAD_ID = 0 +multi.integration={} +multi.integration.lovr2d={} +local THREAD = require("multi.integration.lovrManager.threads") +local GLOBAL = THREAD.getGlobal() +local THREAD_ID = 1 +local OBJECT_ID = 0 +local stf = 0 +function THREAD:newFunction(func,holup) + stf = stf + 1 + return function(...) + local t = multi:newSystemThread("STF"..stf,func,...) + return thread:newFunction(function() + return thread.hold(function() + if t.stab["returns"] then + local dat = t.stab.returns + t.stab.returns = nil + return multi.unpack(dat) + end + end) + end,holup)() + end +end +function multi:newSystemThread(name,func,...) + local c = {} + c.name = name + c.ID=THREAD_ID + 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 +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.lovrManager.extensions") +multi.print("Integrated lovr Threading!") +return {init=function() + return GLOBAL,THREAD end} \ No newline at end of file diff --git a/integration/lovrManager/threads.lua b/integration/lovrManager/threads.lua index dc919ab..70544f9 100644 --- a/integration/lovrManager/threads.lua +++ b/integration/lovrManager/threads.lua @@ -1,222 +1,222 @@ ---[[ -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. -]] --- 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 = {} -function threads.loadDump(d) - return loadstring(d:getString()) -end -function threads.dump(func) - return lovr.data.newByteData(string.dump(func)) -end -local fRef = {"func",nil} -local function manage(channel, value) - channel:clear() - if type(value) == "function" then - fRef[2] = THREAD.dump(value) - channel:push(fRef) - return - else - channel:push(value) - end -end -local function RandomVariable(length) - local res = {} - math.randomseed(socket.gettime()*10000) - for i = 1, length do - res[#res+1] = string.char(math.random(97, 122)) - end - return table.concat(res) -end -local GNAME = "__GLOBAL_" -local proxy = {} -function threads.set(name,val) - 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] = 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]) - else - return dat - end -end -function threads.waitFor(name) - if thread.isThread() then - return thread.hold(function() - return threads.get(name) - end) - end - while threads.get(name)==nil do - lovr.timer.sleep(.001) - end - local dat = threads.get(name) - if type(dat) == "table" and dat.init then - dat.init = threads.loadDump(dat.init) - end - return dat -end -function threads.package(name,val) - local init = val.init - val.init=threads.dump(val.init) - GLOBAL[name]=val - val.init=init -end -function threads.getCores() - return lovr.system.getProcessorCount() -end -function threads.kill() - error("Thread Killed!") -end -function threads.getThreads() - local t = {} - for i=1,GLOBAL["__THREAD_COUNT"] do - t[#t+1]=GLOBAL["__THREAD_"..i] - end - return t -end -function threads.getThread(n) - return GLOBAL["__THREAD_"..n] -end -function threads.getName() - return __THREADNAME__ -end -function threads.getID() - return __THREADID__ -end -function threads.sleep(n) - lovr.timer.sleep(n) -end -function threads.getGlobal() - return setmetatable({}, - { - __index = function(t, k) - return THREAD.get(k) - end, - __newindex = function(t, k, v) - THREAD.set(k,v) - end - } - ) -end -function threads.createTable(n) - local _proxy = {} - local function set(name,val) - 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] = 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]) - else - return dat - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end -function threads.getConsole() - local c = {} - c.queue = lovr.thread.getChannel("__CONSOLE__") - function c.print(...) - c.queue:push(multi.pack(...)) - end - function c.error(err) - c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} - error(err) - end - return c -end -if not ISTHREAD then - local clock = os.clock - local lastproc = clock() - local queue = lovr.thread.getChannel("__CONSOLE__") - thread:newThread("consoleManager",function() - while true do - thread.yield() - dat = queue:pop() - if dat then - lastproc = clock() - print(multi.unpack(dat)) - end - if clock()-lastproc>2 then - thread.sleep(.1) - end - end - end) -end -function threads.createStaticTable(n) - local __proxy = {} - local function set(name,val) - if __proxy[name] then return end - local chan = 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 = lovr.thread.getChannel(n..name):peek() - if type(dat)=="table" and dat[1]=="func" then - __proxy[name] = THREAD.loadDump(dat[2]) - return __proxy[name] - else - __proxy[name] = dat - return __proxy[name] - end - end - return setmetatable({}, - { - __index = function(t, k) - return get(k) - end, - __newindex = function(t, k, v) - set(k,v) - end - } - ) -end -function threads.hold(n) - local dat - while not(dat) do - dat = n() - end -end +--[[ +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. +]] +-- 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 = {} +function threads.loadDump(d) + return loadstring(d:getString()) +end +function threads.dump(func) + return lovr.data.newByteData(string.dump(func)) +end +local fRef = {"func",nil} +local function manage(channel, value) + channel:clear() + if type(value) == "function" then + fRef[2] = THREAD.dump(value) + channel:push(fRef) + return + else + channel:push(value) + end +end +local function RandomVariable(length) + local res = {} + math.randomseed(socket.gettime()*10000) + for i = 1, length do + res[#res+1] = string.char(math.random(97, 122)) + end + return table.concat(res) +end +local GNAME = "__GLOBAL_" +local proxy = {} +function threads.set(name,val) + 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] = 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]) + else + return dat + end +end +function threads.waitFor(name) + if thread.isThread() then + return thread.hold(function() + return threads.get(name) + end) + end + while threads.get(name)==nil do + lovr.timer.sleep(.001) + end + local dat = threads.get(name) + if type(dat) == "table" and dat.init then + dat.init = threads.loadDump(dat.init) + end + return dat +end +function threads.package(name,val) + local init = val.init + val.init=threads.dump(val.init) + GLOBAL[name]=val + val.init=init +end +function threads.getCores() + return lovr.system.getProcessorCount() +end +function threads.kill() + error("Thread Killed!") +end +function threads.getThreads() + local t = {} + for i=1,GLOBAL["__THREAD_COUNT"] do + t[#t+1]=GLOBAL["__THREAD_"..i] + end + return t +end +function threads.getThread(n) + return GLOBAL["__THREAD_"..n] +end +function threads.getName() + return __THREADNAME__ +end +function threads.getID() + return __THREADID__ +end +function threads.sleep(n) + lovr.timer.sleep(n) +end +function threads.getGlobal() + return setmetatable({}, + { + __index = function(t, k) + return THREAD.get(k) + end, + __newindex = function(t, k, v) + THREAD.set(k,v) + end + } + ) +end +function threads.createTable(n) + local _proxy = {} + local function set(name,val) + 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] = 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]) + else + return dat + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +end +function threads.getConsole() + local c = {} + c.queue = lovr.thread.getChannel("__CONSOLE__") + function c.print(...) + c.queue:push(multi.pack(...)) + end + function c.error(err) + c.queue:push{"ERROR in <"..__THREADNAME__..">: "..err,__THREADID__} + error(err) + end + return c +end +if not ISTHREAD then + local clock = os.clock + local lastproc = clock() + local queue = lovr.thread.getChannel("__CONSOLE__") + thread:newThread("consoleManager",function() + while true do + thread.yield() + dat = queue:pop() + if dat then + lastproc = clock() + print(multi.unpack(dat)) + end + if clock()-lastproc>2 then + thread.sleep(.1) + end + end + end) +end +function threads.createStaticTable(n) + local __proxy = {} + local function set(name,val) + if __proxy[name] then return end + local chan = 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 = lovr.thread.getChannel(n..name):peek() + if type(dat)=="table" and dat[1]=="func" then + __proxy[name] = THREAD.loadDump(dat[2]) + return __proxy[name] + else + __proxy[name] = dat + return __proxy[name] + end + end + return setmetatable({}, + { + __index = function(t, k) + return get(k) + end, + __newindex = function(t, k, v) + set(k,v) + end + } + ) +end +function threads.hold(n) + local dat + while not(dat) do + dat = n() + end +end return threads \ No newline at end of file diff --git a/integration/luvitManager.lua b/integration/luvitManager.lua index 46a0dc2..938c83a 100644 --- a/integration/luvitManager.lua +++ b/integration/luvitManager.lua @@ -1,138 +1,138 @@ ---[[ -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. -]] - --- This module probably will not be maintained any longer! -package.path = "?/init.lua;?.lua;" .. package.path -local function _INIT(luvitThread, timer) - -- lots of this stuff should be able to stay the same - function os.getOS() - if package.config:sub(1, 1) == "\\" then - return "windows" - else - return "unix" - end - end - -- Step 1 get setup threads on luvit... Sigh how do i even... - local multi, thread = require("multi").init() - multi.isMainThread = true - function multi:canSystemThread() - return true - end - function multi:getPlatform() - return "luvit" - end - local multi = multi - -- Step 2 set up the Global table... is this possible? - local GLOBAL = {} - setmetatable( - GLOBAL, - { - __index = function(t, k) - --print("No Global table when using luvit integration!") - return nil - end, - __newindex = function(t, k, v) - --print("No Global table when using luvit integration!") - end - } - ) - local THREAD = {} - function THREAD.set(name, val) - --print("No Global table when using luvit integration!") - end - function THREAD.get(name) - --print("No Global table when using luvit integration!") - end - local function randomString(n) - local str = "" - local strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} - for i = 1, n do - str = str .. "" .. strings[math.random(1, #strings)] - end - return str - end - function THREAD.waitFor(name) - --print("No Global table when using luvit integration!") - end - function THREAD.testFor(name, val, sym) - --print("No Global table when using luvit integration!") - end - function THREAD.getCores() - return THREAD.__CORES - 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 thread destruction - error("Thread was Killed!") - end - -- hmmm if im cleaver I can get this to work... but since data passing isn't going to be a thing its probably not important - function THREAD.sleep(n) - --print("No Global table when using luvit integration!") - end - function THREAD.hold(n) - --print("No Global table when using luvit integration!") - end - -- Step 5 Basic Threads! - local function entry(path, name, func, ...) - local timer = require "timer" - local luvitThread = require "thread" - package.path = path - loadstring(func)(...) - end - function multi:newSystemThread(name, func, ...) - local c = {} - local __self = c - c.name = name - c.Type = multi.STHREAD - c.thread = {} - c.func = string.dump(func) - function c:kill() - -- print("No Global table when using luvit integration!") - end - 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 - multi.integration.THREAD = THREAD - require("multi.integration.shared") - -- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh... - local interval = - timer.setInterval( - 1, - function() - multi:uManager() - end - ) - return multi -end -return {init = function(threadHandle, timerHandle) - local multi = _INIT(threadHandle, timerHandle) - return GLOBAL, THREAD -end} +--[[ +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. +]] + +-- This module probably will not be maintained any longer! +package.path = "?/init.lua;?.lua;" .. package.path +local function _INIT(luvitThread, timer) + -- lots of this stuff should be able to stay the same + function os.getOS() + if package.config:sub(1, 1) == "\\" then + return "windows" + else + return "unix" + end + end + -- Step 1 get setup threads on luvit... Sigh how do i even... + local multi, thread = require("multi").init() + multi.isMainThread = true + function multi:canSystemThread() + return true + end + function multi:getPlatform() + return "luvit" + end + local multi = multi + -- Step 2 set up the Global table... is this possible? + local GLOBAL = {} + setmetatable( + GLOBAL, + { + __index = function(t, k) + --print("No Global table when using luvit integration!") + return nil + end, + __newindex = function(t, k, v) + --print("No Global table when using luvit integration!") + end + } + ) + local THREAD = {} + function THREAD.set(name, val) + --print("No Global table when using luvit integration!") + end + function THREAD.get(name) + --print("No Global table when using luvit integration!") + end + local function randomString(n) + local str = "" + local strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} + for i = 1, n do + str = str .. "" .. strings[math.random(1, #strings)] + end + return str + end + function THREAD.waitFor(name) + --print("No Global table when using luvit integration!") + end + function THREAD.testFor(name, val, sym) + --print("No Global table when using luvit integration!") + end + function THREAD.getCores() + return THREAD.__CORES + 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 thread destruction + error("Thread was Killed!") + end + -- hmmm if im cleaver I can get this to work... but since data passing isn't going to be a thing its probably not important + function THREAD.sleep(n) + --print("No Global table when using luvit integration!") + end + function THREAD.hold(n) + --print("No Global table when using luvit integration!") + end + -- Step 5 Basic Threads! + local function entry(path, name, func, ...) + local timer = require "timer" + local luvitThread = require "thread" + package.path = path + loadstring(func)(...) + end + function multi:newSystemThread(name, func, ...) + local c = {} + local __self = c + c.name = name + c.Type = multi.STHREAD + c.thread = {} + c.func = string.dump(func) + function c:kill() + -- print("No Global table when using luvit integration!") + end + 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 + multi.integration.THREAD = THREAD + require("multi.integration.shared") + -- Start the main mainloop... This allows you to process your multi objects, but the engine on the main thread will be limited to .001 or 1 millisecond sigh... + local interval = + timer.setInterval( + 1, + function() + multi:uManager() + end + ) + return multi +end +return {init = function(threadHandle, timerHandle) + local multi = _INIT(threadHandle, timerHandle) + return GLOBAL, THREAD +end} diff --git a/integration/networkManager/channel.lua b/integration/networkManager/channel.lua index 3083241..8af86b8 100644 --- a/integration/networkManager/channel.lua +++ b/integration/networkManager/channel.lua @@ -1,34 +1,34 @@ ---[[ -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. -]] -_G["__CHANNEL__"] = {} -local channel = {} -channel.__index = channel - --- Creates/Gets a channel of name -function channel:newChannel(name) - local chan = _G["__CHANNEL__"] - if chan then - - end +--[[ +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. +]] +_G["__CHANNEL__"] = {} +local channel = {} +channel.__index = channel + +-- Creates/Gets a channel of name +function channel:newChannel(name) + local chan = _G["__CHANNEL__"] + if chan then + + end end \ No newline at end of file diff --git a/integration/networkManager/childNode.lua b/integration/networkManager/childNode.lua index 43837d5..b1d27bd 100644 --- a/integration/networkManager/childNode.lua +++ b/integration/networkManager/childNode.lua @@ -1,46 +1,46 @@ ---[[ -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 cmd = require("multi.integration.networkManager.cmds") -local node = require("multi.integration.networkManager.node") -local net = require("net") -local bin = require("bin") -local child = {} -child.__index = child -function multi:newChildNode(cd) - local c = {} - setmetatable(c,child) - local name - if cd then - if cd.name then - name = cd.name - end - c.node = node:new(cd.nodePort or cmd.defaultPort,nil,name) - if cd.managerHost then - cd.managerPort = cd.managerPort or cmd.defaultManagerPort - c.node:registerWithManager(cd.managerHost,cd.managerPort) - end - end - return c +--[[ +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 cmd = require("multi.integration.networkManager.cmds") +local node = require("multi.integration.networkManager.node") +local net = require("net") +local bin = require("bin") +local child = {} +child.__index = child +function multi:newChildNode(cd) + local c = {} + setmetatable(c,child) + local name + if cd then + if cd.name then + name = cd.name + end + c.node = node:new(cd.nodePort or cmd.defaultPort,nil,name) + if cd.managerHost then + cd.managerPort = cd.managerPort or cmd.defaultManagerPort + c.node:registerWithManager(cd.managerHost,cd.managerPort) + end + end + return c end \ No newline at end of file diff --git a/integration/networkManager/clientSide.lua b/integration/networkManager/clientSide.lua index 9210840..bac633c 100644 --- a/integration/networkManager/clientSide.lua +++ b/integration/networkManager/clientSide.lua @@ -1,35 +1,35 @@ ---[[ -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. -]] -return function(self,data) - local cmd,data = data:match("!(.-)!(.*)") - --print(">",cmd,data) - if cmd == "PONG" then - self:send("!PONG!") - elseif cmd == "CHANNEL" then - -- - elseif cmd == "RETURNS" then - local rets = bin.new(data):getBlock("t") - self.node.master.OnDataReturned:Fire(rets) - end +--[[ +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. +]] +return function(self,data) + local cmd,data = data:match("!(.-)!(.*)") + --print(">",cmd,data) + if cmd == "PONG" then + self:send("!PONG!") + elseif cmd == "CHANNEL" then + -- + elseif cmd == "RETURNS" then + local rets = bin.new(data):getBlock("t") + self.node.master.OnDataReturned:Fire(rets) + end end \ No newline at end of file diff --git a/integration/networkManager/cmds.lua b/integration/networkManager/cmds.lua index ccb3c2f..d282b54 100644 --- a/integration/networkManager/cmds.lua +++ b/integration/networkManager/cmds.lua @@ -1,42 +1,42 @@ ---[[ -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 cmds = { - defaultManagerPort = 0XDE2, - defaultWait = 0X002, - defaultPort = 0X000, -- We will let the OS assign us one - standardSkip = 0X018, - ERROR = 0X000, - PING = 0X001, - PONG = 0X002, - QUEUE = 0X003, - TASK = 0X004, - INITNODE = 0X005, - INITMASTER = 0X006, - GLOBAL = 0X007, - LOAD = 0X008, - CALL = 0X009, - REG = 0X00A, - CONSOLE = 0X00B, -} +--[[ +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 cmds = { + defaultManagerPort = 0XDE2, + defaultWait = 0X002, + defaultPort = 0X000, -- We will let the OS assign us one + standardSkip = 0X018, + ERROR = 0X000, + PING = 0X001, + PONG = 0X002, + QUEUE = 0X003, + TASK = 0X004, + INITNODE = 0X005, + INITMASTER = 0X006, + GLOBAL = 0X007, + LOAD = 0X008, + CALL = 0X009, + REG = 0X00A, + CONSOLE = 0X00B, +} return cmds \ No newline at end of file diff --git a/integration/networkManager/extensions.lua b/integration/networkManager/extensions.lua index c4cac79..14513e3 100644 --- a/integration/networkManager/extensions.lua +++ b/integration/networkManager/extensions.lua @@ -1,23 +1,23 @@ ---[[ -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. +--[[ +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. ]] \ No newline at end of file diff --git a/integration/networkManager/init.lua b/integration/networkManager/init.lua index 78e10e2..2f0a32a 100644 --- a/integration/networkManager/init.lua +++ b/integration/networkManager/init.lua @@ -1,57 +1,57 @@ ---[[ -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 net = require("net") ---local bin = require("bin") -local char = string.char -local byte = string.byte -bin.setBitsInterface(infinabits) ---[[ ---[=[ Pre reqs: -- Network contains nodes -- Network can broadcast/has nodemanager/ is simple and can be scanned - -Outline: -- multi:newMasterNode(connectionDetails) --- master:setDefaultNode(nodeName) -- Set default node --- master:newNetworkThread(nodeName,func,...) -- Thread is ran on a random node or the default one if set if nodeName is set to nil --- master:newNetworkChannel(nodeName) --- master:sendTo(nodeName,data) -- multi:newNode(connectionDetails) -- multi:newNodeManager(connectionDetails) -- This will be incharge of a lot of data handling -]=] - -local nGLOBAL, nTHREAD = require("multi.integration.networkManager"):init() -local master = multi:newMasterNode() -master:newNetworkThread("simpleNode",function(a,b,c) - print(a,b,c) -end,1,2,3) -]] - --- The init file should provide the structure that all the other modules build off of -return { - init = function() - -- - end +--[[ +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 net = require("net") +--local bin = require("bin") +local char = string.char +local byte = string.byte +bin.setBitsInterface(infinabits) +--[[ +--[=[ Pre reqs: +- Network contains nodes +- Network can broadcast/has nodemanager/ is simple and can be scanned + +Outline: +- multi:newMasterNode(connectionDetails) +-- master:setDefaultNode(nodeName) -- Set default node +-- master:newNetworkThread(nodeName,func,...) -- Thread is ran on a random node or the default one if set if nodeName is set to nil +-- master:newNetworkChannel(nodeName) +-- master:sendTo(nodeName,data) +- multi:newNode(connectionDetails) +- multi:newNodeManager(connectionDetails) -- This will be incharge of a lot of data handling +]=] + +local nGLOBAL, nTHREAD = require("multi.integration.networkManager"):init() +local master = multi:newMasterNode() +master:newNetworkThread("simpleNode",function(a,b,c) + print(a,b,c) +end,1,2,3) +]] + +-- The init file should provide the structure that all the other modules build off of +return { + init = function() + -- + end } \ No newline at end of file diff --git a/integration/networkManager/masterNode.lua b/integration/networkManager/masterNode.lua index 25c2b07..458987e 100644 --- a/integration/networkManager/masterNode.lua +++ b/integration/networkManager/masterNode.lua @@ -1,163 +1,163 @@ ---[[ -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 cmd = require("multi.integration.networkManager.cmds") -local node = require("multi.integration.networkManager.node") -local net = require("net") -local bin = require("bin") -local master = {} -master.__index = master -function master:addNode(ip,port) - return node:new(ip,port) -end -function master:getNodesFromBroadcast() - net:newCastedClients("NODE_.+") - net.OnCastedClientInfo(function(client, n, ip, port) - self.nodes[n] = node:new(client) - end) -end -function master:getNodesFromManager(ip,port) - local mn = self.nodes - if not self.manager then - self.manager = net:newTCPClient(ip,port) - if not self.manager then - error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") - end - end - self.manager.OnDataRecieved(function(self,data,client) - local cmd = data:match("!(.+)!") - data = data:gsub("!"..cmd.."!","") - if cmd == "NODE" then - local n,h,p = data:match("(.-)|(.-)|(.+)") - mn[n] = node:new(h,tonumber(p)) - end - end) - self.manager:send("!NODES!") -end -function master:setDefaultNode(nodeName) - if self:nodeExists(nodeName) then - self.defaultNode = nodeName - end -end -function master:getRandomNode() - local t = {} - for i,v in pairs(self.nodes) do t[#t+1] = i end - return t[math.random(1,#t)] -end -local netID = 0 -function master:newNetworkThread(nodeName,func,...) - local args = {...} - local dat = bin.new() - local ret - local nID = netID - local conn = multi:newConnection() - thread:newthread(function() - dat:addBlock{ - args = args, - func = func, - id = netID - } - netID = netID + 1 - if type(nodeName) == "function" then - func = nodeName - nodeName = self.defaultNode or self:getRandomNode() - if not func then - error("You must provide a function!") - end - end - self:sendTo(nodeName,"!N_THREAD!"..dat.data) - self.OnDataReturned(function(rets) - if rets.ID == nID then - conn:Fire(unpack(rets.rets)) - end - end) - end) - return conn -end -function master:newNetworkChannel(nodeName) - -- -end -function master:sendTo(nodeName,data) - self:queue("send",nodeName,data) -end -function master:demandNodeExistance(nodeName) - if self.nodes[nodeName] then - return multi.hold(self.nodes[nodeName]:ping().pong) - else - return false - end -end -function master:queue(c,...) - table.insert(self._queue,{c,{...}}) -end -function multi:newMasterNode(cd) - local c = {} - setmetatable(c, master) - c.OnNodeDiscovered = multi:newConnection() - c.OnNodeRemoved = multi:newConnection() - c.OnDataRecieved = multi:newConnection() - c.OnDataReturned = multi:newConnection() - c.defaultNode = "" - c.nodes = {} - setmetatable(c.nodes, - {__newindex = function(t,k,v) - rawset(t,k,v) - v.master = c - c.OnNodeDiscovered:Fire(k,v) - end}) - c._queue = {} - if cd then - if cd.nodeHost then - cd.nodePort = cd.nodePort or cmd.defaultPort - local n,no = c:addNode(cd.nodeHost,cd.nodePort) - if n then - c.nodes[n] = no - end - elseif cd.managerHost then - cd.managerPort = cd.managerPort or cmd.defaultManagerPort - c:getNodesFromManager(cd.managerHost,cd.managerPort) - else - c:getNodesFromBroadcast() - end - else - c:getNodesFromBroadcast() - end - thread:newthread("CMDQueueProcessor",function() - while true do - thread.skip(128) - local data = table.remove(c._queue,1) - if data then - local cmd = data[1] - if cmd == "send" then - local nodeName = data[2][1] - local dat = data[2][2] - c.nodes[nodeName]:send(dat) - end - end - end - end):OnError(function(...) - print(...) - end) - return c +--[[ +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 cmd = require("multi.integration.networkManager.cmds") +local node = require("multi.integration.networkManager.node") +local net = require("net") +local bin = require("bin") +local master = {} +master.__index = master +function master:addNode(ip,port) + return node:new(ip,port) +end +function master:getNodesFromBroadcast() + net:newCastedClients("NODE_.+") + net.OnCastedClientInfo(function(client, n, ip, port) + self.nodes[n] = node:new(client) + end) +end +function master:getNodesFromManager(ip,port) + local mn = self.nodes + if not self.manager then + self.manager = net:newTCPClient(ip,port) + if not self.manager then + error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") + end + end + self.manager.OnDataRecieved(function(self,data,client) + local cmd = data:match("!(.+)!") + data = data:gsub("!"..cmd.."!","") + if cmd == "NODE" then + local n,h,p = data:match("(.-)|(.-)|(.+)") + mn[n] = node:new(h,tonumber(p)) + end + end) + self.manager:send("!NODES!") +end +function master:setDefaultNode(nodeName) + if self:nodeExists(nodeName) then + self.defaultNode = nodeName + end +end +function master:getRandomNode() + local t = {} + for i,v in pairs(self.nodes) do t[#t+1] = i end + return t[math.random(1,#t)] +end +local netID = 0 +function master:newNetworkThread(nodeName,func,...) + local args = {...} + local dat = bin.new() + local ret + local nID = netID + local conn = multi:newConnection() + thread:newthread(function() + dat:addBlock{ + args = args, + func = func, + id = netID + } + netID = netID + 1 + if type(nodeName) == "function" then + func = nodeName + nodeName = self.defaultNode or self:getRandomNode() + if not func then + error("You must provide a function!") + end + end + self:sendTo(nodeName,"!N_THREAD!"..dat.data) + self.OnDataReturned(function(rets) + if rets.ID == nID then + conn:Fire(unpack(rets.rets)) + end + end) + end) + return conn +end +function master:newNetworkChannel(nodeName) + -- +end +function master:sendTo(nodeName,data) + self:queue("send",nodeName,data) +end +function master:demandNodeExistance(nodeName) + if self.nodes[nodeName] then + return multi.hold(self.nodes[nodeName]:ping().pong) + else + return false + end +end +function master:queue(c,...) + table.insert(self._queue,{c,{...}}) +end +function multi:newMasterNode(cd) + local c = {} + setmetatable(c, master) + c.OnNodeDiscovered = multi:newConnection() + c.OnNodeRemoved = multi:newConnection() + c.OnDataRecieved = multi:newConnection() + c.OnDataReturned = multi:newConnection() + c.defaultNode = "" + c.nodes = {} + setmetatable(c.nodes, + {__newindex = function(t,k,v) + rawset(t,k,v) + v.master = c + c.OnNodeDiscovered:Fire(k,v) + end}) + c._queue = {} + if cd then + if cd.nodeHost then + cd.nodePort = cd.nodePort or cmd.defaultPort + local n,no = c:addNode(cd.nodeHost,cd.nodePort) + if n then + c.nodes[n] = no + end + elseif cd.managerHost then + cd.managerPort = cd.managerPort or cmd.defaultManagerPort + c:getNodesFromManager(cd.managerHost,cd.managerPort) + else + c:getNodesFromBroadcast() + end + else + c:getNodesFromBroadcast() + end + thread:newthread("CMDQueueProcessor",function() + while true do + thread.skip(128) + local data = table.remove(c._queue,1) + if data then + local cmd = data[1] + if cmd == "send" then + local nodeName = data[2][1] + local dat = data[2][2] + c.nodes[nodeName]:send(dat) + end + end + end + end):OnError(function(...) + print(...) + end) + return c end \ No newline at end of file diff --git a/integration/networkManager/node.lua b/integration/networkManager/node.lua index 8151b57..83de9b0 100644 --- a/integration/networkManager/node.lua +++ b/integration/networkManager/node.lua @@ -1,127 +1,127 @@ ---[[ -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 net = require("net") -local cmd = require("multi.integration.networkManager.cmds") -local multi,thread = require("multi"):init() -local node = {} -node.__index = node -local rand = {} -for i = 65,90 do - rand[#rand+1] = string.char(i) -end -local function randName(n) - local str = {} - for i=1,(n or 10) do - str[#str+1] = rand[math.random(1,#rand)] - end - return table.concat(str) -end -local getNames = thread:newFunction(function(names) - local listen = socket.udp() -- make a new socket - listen:setsockname(net.getLocalIP(), 11111) - listen:settimeout(0) - local data, ip, port = listen:receivefrom() - thread.holdWithin(1,function() - if data then - local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)") - if n then - names[n]=true - end - end - end) - return multi.NIL -end) -local function setName(ref,name) - if name then - ref.name = "NODE_"..name - ref.connection:broadcast(name) - return - end - local names = {} - getNames(names).wait() -- Prevents duplicate names from spawning! - local name = randName() - while names["NODE_"..name] do - name = randName() - end - ref.name = "NODE_"..name - ref.connection:broadcast(ref.name) -end -node.ServerCode = require("multi.integration.networkManager.serverSide") -node.ClientCode = require("multi.integration.networkManager.clientSide") -function node.random() - return randName(12) -end -function node:registerWithManager(ip,port) - if self.type ~= "server" then return end - if not self.manager then - self.manager = net:newTCPClient(ip,port) - if not self.manager then - error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") - end - end - thread:newFunction(function() - thread.hold(function() return self.name end) - self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port) - end)() -end -function node:new(host,port,name) - local c = {} - c.links = {} - setmetatable(c,node) - if type(host)=="number" or type(host)=="nil" then - c.connection = net:newTCPServer(host or cmd.defaultPort) - c.connection:enableBinaryMode() - c.type = "server" - c.connection.node = c - c.connection.OnDataRecieved(self.ServerCode) - setName(c) - elseif type(host)=="table" and host.Type == "tcp" then - c.connection = host - c.connection:enableBinaryMode() - c.type = "client" - c.connection.node = c - c.connection.OnDataRecieved(self.ClientCode) - c.name = "MASTER_NODE" - elseif type(host) == "string" and type(port)=="number" then - c.connection = net:newTCPClient(host, port) - c.connection:enableBinaryMode() - c.type = "client" - c.connection.node = c - c.connection.OnDataRecieved(self.ClientCode) - c.name = "MASTER_NODE" - else - error("Invalid arguments!") - end - return c -end -function node:ping() - if self.type ~= "client" then return end - self:send("!PING!") - return {pong=self.connection.OnDataRecieved} -end -function node:send(data) - if self.type ~= "client" then return end - self.connection:send(data) -end +--[[ +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 net = require("net") +local cmd = require("multi.integration.networkManager.cmds") +local multi,thread = require("multi"):init() +local node = {} +node.__index = node +local rand = {} +for i = 65,90 do + rand[#rand+1] = string.char(i) +end +local function randName(n) + local str = {} + for i=1,(n or 10) do + str[#str+1] = rand[math.random(1,#rand)] + end + return table.concat(str) +end +local getNames = thread:newFunction(function(names) + local listen = socket.udp() -- make a new socket + listen:setsockname(net.getLocalIP(), 11111) + listen:settimeout(0) + local data, ip, port = listen:receivefrom() + thread.holdWithin(1,function() + if data then + local n, tp, ip, port = data:match("(%S-)|(%S-)|(%S-):(%d+)") + if n then + names[n]=true + end + end + end) + return multi.NIL +end) +local function setName(ref,name) + if name then + ref.name = "NODE_"..name + ref.connection:broadcast(name) + return + end + local names = {} + getNames(names).wait() -- Prevents duplicate names from spawning! + local name = randName() + while names["NODE_"..name] do + name = randName() + end + ref.name = "NODE_"..name + ref.connection:broadcast(ref.name) +end +node.ServerCode = require("multi.integration.networkManager.serverSide") +node.ClientCode = require("multi.integration.networkManager.clientSide") +function node.random() + return randName(12) +end +function node:registerWithManager(ip,port) + if self.type ~= "server" then return end + if not self.manager then + self.manager = net:newTCPClient(ip,port) + if not self.manager then + error("Unable to connect to the node Manager! Is it running? Perhaps the hostname or port is incorrect!") + end + end + thread:newFunction(function() + thread.hold(function() return self.name end) + self.manager:send("!REG_NODE!"..self.name.."|"..net.getLocalIP().."|"..self.connection.port) + end)() +end +function node:new(host,port,name) + local c = {} + c.links = {} + setmetatable(c,node) + if type(host)=="number" or type(host)=="nil" then + c.connection = net:newTCPServer(host or cmd.defaultPort) + c.connection:enableBinaryMode() + c.type = "server" + c.connection.node = c + c.connection.OnDataRecieved(self.ServerCode) + setName(c) + elseif type(host)=="table" and host.Type == "tcp" then + c.connection = host + c.connection:enableBinaryMode() + c.type = "client" + c.connection.node = c + c.connection.OnDataRecieved(self.ClientCode) + c.name = "MASTER_NODE" + elseif type(host) == "string" and type(port)=="number" then + c.connection = net:newTCPClient(host, port) + c.connection:enableBinaryMode() + c.type = "client" + c.connection.node = c + c.connection.OnDataRecieved(self.ClientCode) + c.name = "MASTER_NODE" + else + error("Invalid arguments!") + end + return c +end +function node:ping() + if self.type ~= "client" then return end + self:send("!PING!") + return {pong=self.connection.OnDataRecieved} +end +function node:send(data) + if self.type ~= "client" then return end + self.connection:send(data) +end return node \ No newline at end of file diff --git a/integration/networkManager/nodeManager.lua b/integration/networkManager/nodeManager.lua index 840bc60..3ab6737 100644 --- a/integration/networkManager/nodeManager.lua +++ b/integration/networkManager/nodeManager.lua @@ -1,48 +1,48 @@ ---[[ -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 cmd = require("multi.integration.networkManager.cmds") -local net = require("net") -local bin = require("bin") -local nodes = { -- Testing stuff - -} -function multi:newNodeManager(port) - print("Running node manager on port: "..(port or cmd.defaultManagerPort)) - local server = net:newTCPServer(port or cmd.defaultManagerPort) - server.OnDataRecieved(function(serv, data, client) - local cmd = data:match("!(.+)!") - data = data:gsub("!"..cmd.."!","") - if cmd == "NODES" then - for i,v in ipairs(nodes) do - -- Sample data - serv:send(client, "!NODE!".. v[1].."|"..v[2].."|"..v[3]) - end - elseif cmd == "REG_NODE" then - local name, ip, port = data:match("(.-)|(.-)|(.+)") - table.insert(nodes,{name,ip,port}) - print("Registering Node:",name, ip, port) - end - end) +--[[ +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 cmd = require("multi.integration.networkManager.cmds") +local net = require("net") +local bin = require("bin") +local nodes = { -- Testing stuff + +} +function multi:newNodeManager(port) + print("Running node manager on port: "..(port or cmd.defaultManagerPort)) + local server = net:newTCPServer(port or cmd.defaultManagerPort) + server.OnDataRecieved(function(serv, data, client) + local cmd = data:match("!(.+)!") + data = data:gsub("!"..cmd.."!","") + if cmd == "NODES" then + for i,v in ipairs(nodes) do + -- Sample data + serv:send(client, "!NODE!".. v[1].."|"..v[2].."|"..v[3]) + end + elseif cmd == "REG_NODE" then + local name, ip, port = data:match("(.-)|(.-)|(.+)") + table.insert(nodes,{name,ip,port}) + print("Registering Node:",name, ip, port) + end + end) end \ No newline at end of file diff --git a/integration/networkManager/serverSide.lua b/integration/networkManager/serverSide.lua index 4d8b771..85f815a 100644 --- a/integration/networkManager/serverSide.lua +++ b/integration/networkManager/serverSide.lua @@ -1,47 +1,47 @@ ---[[ -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 bin, bits = require("bin").init() -return function(self,data,client) - local cmd,data = data:match("!(.-)!(.*)") - --print("SERVER",cmd,data) - if cmd == "PING" then - self:send(client,"!PONG!") - elseif cmd == "N_THREAD" then - print(1) - local dat = bin.new(data) - print(2) - local t = dat:getBlock("t") - print(3) - local ret = bin.new() - print(4) - ret:addBlock{ID = t.id,rets = {t.func(unpack(t.args))}} - print(5) - print(client,"!RETURNS!"..ret:getData()) - self:send(client,"!RETURNS!"..ret:getData()) - print(6) - elseif cmd == "CHANNEL" then - local dat = bin.new(data):getBlock("t") - - end +--[[ +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 bin, bits = require("bin").init() +return function(self,data,client) + local cmd,data = data:match("!(.-)!(.*)") + --print("SERVER",cmd,data) + if cmd == "PING" then + self:send(client,"!PONG!") + elseif cmd == "N_THREAD" then + print(1) + local dat = bin.new(data) + print(2) + local t = dat:getBlock("t") + print(3) + local ret = bin.new() + print(4) + ret:addBlock{ID = t.id,rets = {t.func(unpack(t.args))}} + print(5) + print(client,"!RETURNS!"..ret:getData()) + self:send(client,"!RETURNS!"..ret:getData()) + print(6) + elseif cmd == "CHANNEL" then + local dat = bin.new(data):getBlock("t") + + end end \ No newline at end of file diff --git a/integration/networkManager/threads.lua b/integration/networkManager/threads.lua index c4cac79..14513e3 100644 --- a/integration/networkManager/threads.lua +++ b/integration/networkManager/threads.lua @@ -1,23 +1,23 @@ ---[[ -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. +--[[ +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. ]] \ No newline at end of file diff --git a/integration/networkManager/utils.lua b/integration/networkManager/utils.lua index 9ebdb1b..98285d5 100644 --- a/integration/networkManager/utils.lua +++ b/integration/networkManager/utils.lua @@ -1,27 +1,27 @@ ---[[ -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 bin = require("bin") -local utils = {} --- Will contain data that handles sterilizing and managing data +--[[ +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 bin = require("bin") +local utils = {} +-- Will contain data that handles sterilizing and managing data return utils \ No newline at end of file diff --git a/integration/priorityManager/init.lua b/integration/priorityManager/init.lua index 4c09007..550a727 100644 --- a/integration/priorityManager/init.lua +++ b/integration/priorityManager/init.lua @@ -1,213 +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 - +-- 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() \ No newline at end of file diff --git a/integration/pseudoManager/extensions.lua b/integration/pseudoManager/extensions.lua index 00ddfa3..a1f3707 100644 --- a/integration/pseudoManager/extensions.lua +++ b/integration/pseudoManager/extensions.lua @@ -1,221 +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 - +--[[ +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") \ No newline at end of file diff --git a/integration/pseudoManager/init.lua b/integration/pseudoManager/init.lua index 46c4c9b..5eb14ba 100644 --- a/integration/pseudoManager/init.lua +++ b/integration/pseudoManager/init.lua @@ -1,123 +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 -} +--[[ +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 +} diff --git a/integration/pseudoManager/threads.lua b/integration/pseudoManager/threads.lua index 5490607..9a99182 100644 --- a/integration/pseudoManager/threads.lua +++ b/integration/pseudoManager/threads.lua @@ -1,122 +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) +--[[ +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} \ No newline at end of file diff --git a/integration/sharedExtensions/init.lua b/integration/sharedExtensions/init.lua index 01bae7f..d0cfb53 100644 --- a/integration/sharedExtensions/init.lua +++ b/integration/sharedExtensions/init.lua @@ -1,336 +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 - +--[[ 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 + diff --git a/integration/threading.lua b/integration/threading.lua index b75dbc6..2a0cb42 100644 --- a/integration/threading.lua +++ b/integration/threading.lua @@ -1,13 +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 +-- 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 } \ No newline at end of file diff --git a/lovethreads/conf.lua b/lovethreads/conf.lua index c202fa9..fc6d1e1 100644 --- a/lovethreads/conf.lua +++ b/lovethreads/conf.lua @@ -1,20 +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 +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.version = "12.0" -- The LOVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + + -- t.modules.audio = false -- Enable the audio module (boolean) + -- t.modules.event = false -- Enable the event module (boolean) + -- t.modules.graphics = false -- Enable the graphics module (boolean) + -- t.modules.image = false -- Enable the image module (boolean) + -- t.modules.joystick = false -- Enable the joystick module (boolean) + -- t.modules.keyboard = false -- Enable the keyboard module (boolean) + -- t.modules.math = false -- Enable the math module (boolean) + -- t.modules.mouse = false -- Enable the mouse module (boolean) + -- t.modules.physics = false -- Enable the physics module (boolean) + -- t.modules.sound = false -- Enable the sound module (boolean) + -- t.modules.system = true -- Enable the system module (boolean) + -- t.modules.timer = true -- Enable the timer module (boolean) + -- t.modules.window = false -- Enable the window module (boolean) + -- t.modules.thread = true -- Enable the thread module (boolean) +end diff --git a/lovethreads/main.lua b/lovethreads/main.lua index 8936ea3..45a54f4 100644 --- a/lovethreads/main.lua +++ b/lovethreads/main.lua @@ -1,75 +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() +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 \ No newline at end of file diff --git a/oldversions/BasicCommands.lua b/oldversions/BasicCommands.lua index eec5d46..2394936 100644 --- a/oldversions/BasicCommands.lua +++ b/oldversions/BasicCommands.lua @@ -1,511 +1,511 @@ ---Needs:SystemType.bat -function RandomString(num) -string = "" -strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} -for i=1,num do -h = math.random(1,#strings) -string = string..""..strings[h] -end -return string -end ----------------------------------------------------------------------------------------------------- -function GetSystemType() -return BatCmd([[ -for /f "skip=1 delims=" %%x in ('wmic cpu get addresswidth') do if not defined AddressWidth set AddressWidth=%%x - -if %AddressWidth%==64 ( - exit /b 64 -) else ( - exit /b 32 -) -]]) -end ----------------------------------------------------------------------------------------------------- -local clock = os.clock -function sleep(n) -- seconds -if not n then n=0 end -local t0 = clock() -while clock() - t0 <= n do end -end ----------------------------------------------------------------------------------------------------- -function SetCounter() -return os.clock() -end ----------------------------------------------------------------------------------------------------- -function GetCounter(count) -if count~=nil then -return os.clock()-count -else -return 0 -end -end ----------------------------------------------------------------------------------------------------- --- sleep and wait script for n seconds -function wait(n) -sleep(n) -end ----------------------------------------------------------------------------------------------------- --- pause into any key pressed -function pause(msg) -if msg ~= nil then -print(msg) -end -io.read() -end ----------------------------------------------------------------------------------------------------- --- gets input -function getInput(msg) -if msg ~= nil then -io.write(msg) -end -return io.read() -end ----------------------------------------------------------------------------------------------------- --- Print contents of `tbl`, with indentation. --- `indent` sets the initial level of indentation. -function print(...) -arg={...} -if arg[1]==nil then -return nil -end -for i,v in ipairs(arg) do -function tprint(tbl, indent) -if not indent then indent = 0 end -for k, v in pairs(tbl) do -formatting = string.rep(" ", indent) .. k .. ": " -if type(v) == "table" then -print(formatting) -tprint(v, indent+1) -else -print(formatting .. tostring(v)) -end -end -end -if type(v)=="string" then -io.write(v..[[ - -]]) -elseif type(v)=="table" then -function test(v) -return v:tostring() -end -if v.tostring ~=nil then -print(test(v)) -else -tprint(v) -end -elseif type(v)=="number" then -io.write(tostring(v..[[ - -]])) -elseif type(v)=="boolean" then -if v then -io.write("true"..[[ - -]]) -else -io.write("false"..[[ - -]]) -end -end -end -end -math.randomseed(clock()) ----------------------------------------------------------------------------------------------------- --- gets the length of a table or a string or the number of digits in a number including the decimal -function len(T) -if type(T)=="table" then -local count = 0 -for _ in pairs(T) do count = count + 1 end -return count -elseif type(T)=="string" then -return string.len(T) -elseif type(T)=="number" then -return string.len(tostring(T)) -end -end ----------------------------------------------------------------------------------------------------- -function dump(t,indent) - local names = {} - if not indent then indent = "" end - for n,g in pairs(t) do - table.insert(names,n) - end - table.sort(names) - for i,n in pairs(names) do - local v = t[n] - if type(v) == "table" then - if(v==t) then -- prevent endless loop if table contains reference to itself - print(indent..tostring(n)..": <-") - else - print(indent..tostring(n)..":") - dump(v,indent.." ") - end - else - if type(v) == "function" then - print(indent..tostring(n).."()") - else - print(indent..tostring(n)..": "..tostring(v)) - end - end - end -end ----------------------------------------------------------------------------------------------------- -function BuildFromTree(tbl, indent,folder) -if not indent then indent = 0 end -if not folder then folder = "" end -for k, v in pairs(tbl) do -formatting = string.rep(" ", indent) .. k .. ":" -if type(v) == "table" then ---print(formatting) -mkdir(folder..string.sub(formatting,1,-2)) -BuildFromTree(v,0,folder..string.sub(formatting,1,-2).."\\") -print(v,0,folder..string.sub(formatting,1,-2).."\\") -else -a=string.find(tostring(v),":",1,true) -file=string.sub(tostring(v),1,a-1) -data=string.sub(tostring(v),a+1) -mkfile(folder..file,data,"w") -print(folder..tostring(v)) -end -end -end ----------------------------------------------------------------------------------------------------- -function CopyFile(path,topath) -print("Copy "..path.." "..topath) -os.execute("Copy "..path.." "..topath) -end ----------------------------------------------------------------------------------------------------- -function DeleteFile(path) -os.remove(path) -end ----------------------------------------------------------------------------------------------------- -function mkdir(dirname) -os.execute("mkdir \"" .. dirname.."\"") -end ----------------------------------------------------------------------------------------------------- -function mkfile(filename,data,tp) -if not(tp) then tp="w" end -if not(data) then data="" end -file = io.open(filename, tp) -file:write(data) -file:close() -end ----------------------------------------------------------------------------------------------------- -function MoveFile(path,topath) -CopyFile(path,topath) -DeleteFile(path) -end ----------------------------------------------------------------------------------------------------- -function List_Files(dir) -if not(dir) then dir="" end -local f = io.popen("dir \""..dir.."\"") -if f then - return f:read("*a") -else - print("failed to read") -end -end ----------------------------------------------------------------------------------------------------- -function StringLineToTable(s) -local t = {} -- table to store the indices -local i = 0 -while true do -i = string.find(s, "\n", i+1) -- find 'next' newline -if i == nil then return t end -table.insert(t, i) -end -end ----------------------------------------------------------------------------------------------------- -function GetDirectory(dir,flop) -s=List_Files(dir) -drive=string.sub(string.match(s,"drive.."),-1) -local t = {} -- table to store the indices -local i = 0 -while true do -i = string.find(s, "\n", i+1) -- find 'next' newline -if i == nil then -a,b=string.find(s,drive..":\\",1,true) -main = string.gsub(string.sub(s,a,t[4]), "\n", "") -if flop then -main=main:gsub("%\\", "/") -end -return main -end -table.insert(t, i) -end -end ----------------------------------------------------------------------------------------------------- -function lines(str) - local t = {} - local function helper(line) table.insert(t, line) return "" end - helper((str:gsub("(.-)\r?\n", helper))) - return t -end ----------------------------------------------------------------------------------------------------- -function File_Exist(path) -g=io.open(path or '','r') -if path =="" then -p="empty path" -return nil -end -if g~=nil and true or false then -p=(g~=nil and true or false) -end ---p=(g~=nil and true or false..(path=='' and 'empty path entered!' or (path or 'arg "path" wasn\'t define to function call!'))) -if g~=nil then -io.close(g) -else -return false -end -return p -end ----------------------------------------------------------------------------------------------------- -function file_check(file_name) -if not file_name then print("No path inputed") return false end -local file_found=io.open(file_name, "r") -if file_found==nil then -file_found=false -else -file_found=true -end -return file_found -end ----------------------------------------------------------------------------------------------------- -function Dir_Exist(strFolderName) - local fileHandle, strError = io.open(strFolderName.."\\*.*","r") - if fileHandle ~= nil then - io.close(fileHandle) - return true - else - if string.match(strError,"No such file or directory") then - return false - else - return true - end - end -end ----------------------------------------------------------------------------------------------------- -function ListItems(dir) -if Dir_Exist(dir) then -temp=List_Files(dir) -- current directory if blank -if GetDirectory(dir)=="C:\\\n" then -a,b=string.find(temp,"C:\\",1,true) -a=a+2 -else -a,b=string.find(temp,"..",1,true) -end -temp=string.sub(temp,a+2) -list=StringLineToTable(temp) -temp=string.sub(temp,1,list[#list-2]) -slist=lines(temp) -table.remove(slist,1) -table.remove(slist,#slist) -temp={} -temp2={} -for i=1,#slist do -table.insert(temp,string.sub(slist[i],40,-1)) -end -return temp -else -print("Directory does not exist") -return nil -end -end ----------------------------------------------------------------------------------------------------- -function GetDirectories(dir) -temp2={} -dirs=ListItems(dir) -for i=1,#dirs do -if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then -table.insert(temp2,dirs[i]) -end -end -return temp2 -end ----------------------------------------------------------------------------------------------------- -function GetFiles(dir) -temp2={} -dirs=ListItems(dir) -for i=1,#dirs do -if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then -else -table.insert(temp2,dirs[i]) -end -end -return temp2 -end ----------------------------------------------------------------------------------------------------- -function GetName(name) -if name then temp=name else temp=arg[0] end -if string.find(temp,"\\",1,true) then -temp=string.reverse(temp) -a,b=string.find(temp,"\\",1,true) -return string.reverse(string.sub(temp,1,b-1)) -end -return arg[0] -end ----------------------------------------------------------------------------------------------------- -BuildFromTreeE = function(tbl, indent,folder) -if not indent then indent = 0 end -if not folder then folder = "" end -for k, v in pairs(tbl) do -formatting = string.rep(" ", indent) .. k .. ":" -if type(v) == "table" then ---print(formatting) -mkdir(folder..string.sub(formatting,1,-2)) -BuildFromTreeE(v,0,folder..string.sub(formatting,1,-2).."\\") -print(v,0,folder..string.sub(formatting,1,-2).."\\") -else -a=string.find(tostring(v),":",1,true) -file=string.sub(tostring(v),1,a-1) -data=string.sub(tostring(v),a+1) -mkfile(folder..file,SuperEncode(data),"w") -print(folder..tostring(v)) -end -end -end ------------------------------------------------------------------------------------------------------- -readFile = function(file) - local f = io.open(file, "rb") - local content = f:read("*all") - f:close() - return content -end ------------------------------------------------------------------------------------------------------- -readFileE = function(file) - local f = io.open(file, "rb") - local content = f:read("*all") - f:close() - return SuperDecode(content) -end ------------------------------------------------------------------------------------------------------- -mkfileE = function(filename,data,tp) -if not(tp) then tp="w" end -if not(data) then data="" end -file = io.open(filename, tp) -file:write(SuperEncode(data)) -file:close() -end ------------------------------------------------------------------------------------------------------- -math.randomseed(clock()) ------------------------------------------------------------------------------------------------------- -function extension(file) -file=GetName(GetDirectory(file).."/"..file) -a,b=string.find(file,".",0,true) -return string.sub(file,b) -end -function InstallLua() -os.execute("Data\\LuaForWindows.exe") -end ------------------------------------------------------------------------------------------------------- -function autoCmd(cmd,a,b) -if b==nil then b=true end -if File_Exist("tempfile.ahk") and not(File_Exist("tempfile2.ahk")) then fname="tempfile2.ahk" elseif File_Exist("tempfile2.ahk") and not(File_Exist("tempfile.ahk")) then fname="tempfile.ahk" else fname=RandomString(10)..".ahk" end -mkfile(fname,[[ -#SingleInstance off -ReturnLua(val) -{ - FileAppend,%val%", %A_WorkingDir%\File.dat - Exitapp, 0 -} - -]]..cmd) -g=os.execute([[Data\Win32a\AutoHotkey.exe ]]..fname) -if b==true then -if not string.find(cmd,"ReturnLua(",1,true) then print("To Return use ReturnLua(value) (Note: values are returned as strings for booleans use '/bT' or '/bF' true/false, also \"\" are needed for string no other way works for now use \\\" to get quotes also for absolute render(does loadstring to the data don't assign a val name in the script do it to this function) call this function autoCmd(cmd,true) )") return false end ---repeat wait() until File_Exist("File.dat") or g==nil -if not(File_Exist("File.dat")) then -print("An Error has occurred") -return false -end -file = io.open("File.dat", "r") -size = file:read() -- capture file in a string -file:close() -Clean() -if size==nil then -return nil -end -if a then -loadstring("temp="..string.sub(size,1,-2))() -return temp -end -if string.sub(size,1,-2)=="/bT" then -return true -elseif string.sub(size,1,-2)=="/bF" then -return false -end -return string.sub(size,1,-2) -else -return g -end -end ------------------------------------------------------------------------------------------------------- -function BatCmd(cmd) -mkfile("temp.bat",cmd) -return os.execute([[temp.bat]]) -end ------------------------------------------------------------------------------------------------------- -inifile = {} - -local lines -local write - -if love then - lines = love.filesystem.lines - write = love.filesystem.write -else - lines = function(name) return assert(io.open(name)):lines() end - write = function(name, contents) return assert(io.open(name, "w")):write(contents) end -end - -function inifile.parse(name) - local t = {} - local section - for line in lines(name) do - local s = line:match("^%[([^%]]+)%]$") - if s then - section = s - t[section] = t[section] or {} - end - local key, value = line:match("^(%w+)%s-=%s-(.+)$") - if key and value then - if tonumber(value) then value = tonumber(value) end - if value == "true" then value = true end - if value == "false" then value = false end - t[section][key] = value - end - end - return t -end - -function inifile.save(name, t) - local contents = "" - for section, s in pairs(t) do - contents = contents .. ("[%s]\n"):format(section) - for key, value in pairs(s) do - contents = contents .. ("%s=%s\n"):format(key, tostring(value)) - end - contents = contents .. "\n" - end - write(name, contents) -end ---------------- -function DB(txt) -print(txt) -end ------------------------------------------------------------------------------------------------------- -function CreateShortcut(Target, Name , Work, Args, Desc, Icon, Short, IconN, Run) -if not(Target) or not(Name) then print("Error Target folder or file is needed and the name of the shortcut is needed") return false end -if string.sub(Name,-4)~=".lnk" then Name=Name..".lnk" end -if not(Work) then Work="," end -if not(Args) then Args="," end -if not(Desc) then Desc="," end -if not(Icon) then Icon="," end -if not(Short) then Short="," end -if not(IconN) then IconN="," end -if not(Run) then Run="" end -autoCmd([[FileCreateShortcut, ]]..Target..[[, ]]..Name..[[ ]]..Work..[[ ]]..Args..[[ ]]..Desc..[[ ]]..Icon..[[ ]]..Short..[[ ]]..IconN..[[ ]]..Run) -print("--shortcut created at "..Target.." with the name "..Name) -end +--Needs:SystemType.bat +function RandomString(num) +string = "" +strings = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} +for i=1,num do +h = math.random(1,#strings) +string = string..""..strings[h] +end +return string +end +---------------------------------------------------------------------------------------------------- +function GetSystemType() +return BatCmd([[ +for /f "skip=1 delims=" %%x in ('wmic cpu get addresswidth') do if not defined AddressWidth set AddressWidth=%%x + +if %AddressWidth%==64 ( + exit /b 64 +) else ( + exit /b 32 +) +]]) +end +---------------------------------------------------------------------------------------------------- +local clock = os.clock +function sleep(n) -- seconds +if not n then n=0 end +local t0 = clock() +while clock() - t0 <= n do end +end +---------------------------------------------------------------------------------------------------- +function SetCounter() +return os.clock() +end +---------------------------------------------------------------------------------------------------- +function GetCounter(count) +if count~=nil then +return os.clock()-count +else +return 0 +end +end +---------------------------------------------------------------------------------------------------- +-- sleep and wait script for n seconds +function wait(n) +sleep(n) +end +---------------------------------------------------------------------------------------------------- +-- pause into any key pressed +function pause(msg) +if msg ~= nil then +print(msg) +end +io.read() +end +---------------------------------------------------------------------------------------------------- +-- gets input +function getInput(msg) +if msg ~= nil then +io.write(msg) +end +return io.read() +end +---------------------------------------------------------------------------------------------------- +-- Print contents of `tbl`, with indentation. +-- `indent` sets the initial level of indentation. +function print(...) +arg={...} +if arg[1]==nil then +return nil +end +for i,v in ipairs(arg) do +function tprint(tbl, indent) +if not indent then indent = 0 end +for k, v in pairs(tbl) do +formatting = string.rep(" ", indent) .. k .. ": " +if type(v) == "table" then +print(formatting) +tprint(v, indent+1) +else +print(formatting .. tostring(v)) +end +end +end +if type(v)=="string" then +io.write(v..[[ + +]]) +elseif type(v)=="table" then +function test(v) +return v:tostring() +end +if v.tostring ~=nil then +print(test(v)) +else +tprint(v) +end +elseif type(v)=="number" then +io.write(tostring(v..[[ + +]])) +elseif type(v)=="boolean" then +if v then +io.write("true"..[[ + +]]) +else +io.write("false"..[[ + +]]) +end +end +end +end +math.randomseed(clock()) +---------------------------------------------------------------------------------------------------- +-- gets the length of a table or a string or the number of digits in a number including the decimal +function len(T) +if type(T)=="table" then +local count = 0 +for _ in pairs(T) do count = count + 1 end +return count +elseif type(T)=="string" then +return string.len(T) +elseif type(T)=="number" then +return string.len(tostring(T)) +end +end +---------------------------------------------------------------------------------------------------- +function dump(t,indent) + local names = {} + if not indent then indent = "" end + for n,g in pairs(t) do + table.insert(names,n) + end + table.sort(names) + for i,n in pairs(names) do + local v = t[n] + if type(v) == "table" then + if(v==t) then -- prevent endless loop if table contains reference to itself + print(indent..tostring(n)..": <-") + else + print(indent..tostring(n)..":") + dump(v,indent.." ") + end + else + if type(v) == "function" then + print(indent..tostring(n).."()") + else + print(indent..tostring(n)..": "..tostring(v)) + end + end + end +end +---------------------------------------------------------------------------------------------------- +function BuildFromTree(tbl, indent,folder) +if not indent then indent = 0 end +if not folder then folder = "" end +for k, v in pairs(tbl) do +formatting = string.rep(" ", indent) .. k .. ":" +if type(v) == "table" then +--print(formatting) +mkdir(folder..string.sub(formatting,1,-2)) +BuildFromTree(v,0,folder..string.sub(formatting,1,-2).."\\") +print(v,0,folder..string.sub(formatting,1,-2).."\\") +else +a=string.find(tostring(v),":",1,true) +file=string.sub(tostring(v),1,a-1) +data=string.sub(tostring(v),a+1) +mkfile(folder..file,data,"w") +print(folder..tostring(v)) +end +end +end +---------------------------------------------------------------------------------------------------- +function CopyFile(path,topath) +print("Copy "..path.." "..topath) +os.execute("Copy "..path.." "..topath) +end +---------------------------------------------------------------------------------------------------- +function DeleteFile(path) +os.remove(path) +end +---------------------------------------------------------------------------------------------------- +function mkdir(dirname) +os.execute("mkdir \"" .. dirname.."\"") +end +---------------------------------------------------------------------------------------------------- +function mkfile(filename,data,tp) +if not(tp) then tp="w" end +if not(data) then data="" end +file = io.open(filename, tp) +file:write(data) +file:close() +end +---------------------------------------------------------------------------------------------------- +function MoveFile(path,topath) +CopyFile(path,topath) +DeleteFile(path) +end +---------------------------------------------------------------------------------------------------- +function List_Files(dir) +if not(dir) then dir="" end +local f = io.popen("dir \""..dir.."\"") +if f then + return f:read("*a") +else + print("failed to read") +end +end +---------------------------------------------------------------------------------------------------- +function StringLineToTable(s) +local t = {} -- table to store the indices +local i = 0 +while true do +i = string.find(s, "\n", i+1) -- find 'next' newline +if i == nil then return t end +table.insert(t, i) +end +end +---------------------------------------------------------------------------------------------------- +function GetDirectory(dir,flop) +s=List_Files(dir) +drive=string.sub(string.match(s,"drive.."),-1) +local t = {} -- table to store the indices +local i = 0 +while true do +i = string.find(s, "\n", i+1) -- find 'next' newline +if i == nil then +a,b=string.find(s,drive..":\\",1,true) +main = string.gsub(string.sub(s,a,t[4]), "\n", "") +if flop then +main=main:gsub("%\\", "/") +end +return main +end +table.insert(t, i) +end +end +---------------------------------------------------------------------------------------------------- +function lines(str) + local t = {} + local function helper(line) table.insert(t, line) return "" end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end +---------------------------------------------------------------------------------------------------- +function File_Exist(path) +g=io.open(path or '','r') +if path =="" then +p="empty path" +return nil +end +if g~=nil and true or false then +p=(g~=nil and true or false) +end +--p=(g~=nil and true or false..(path=='' and 'empty path entered!' or (path or 'arg "path" wasn\'t define to function call!'))) +if g~=nil then +io.close(g) +else +return false +end +return p +end +---------------------------------------------------------------------------------------------------- +function file_check(file_name) +if not file_name then print("No path inputed") return false end +local file_found=io.open(file_name, "r") +if file_found==nil then +file_found=false +else +file_found=true +end +return file_found +end +---------------------------------------------------------------------------------------------------- +function Dir_Exist(strFolderName) + local fileHandle, strError = io.open(strFolderName.."\\*.*","r") + if fileHandle ~= nil then + io.close(fileHandle) + return true + else + if string.match(strError,"No such file or directory") then + return false + else + return true + end + end +end +---------------------------------------------------------------------------------------------------- +function ListItems(dir) +if Dir_Exist(dir) then +temp=List_Files(dir) -- current directory if blank +if GetDirectory(dir)=="C:\\\n" then +a,b=string.find(temp,"C:\\",1,true) +a=a+2 +else +a,b=string.find(temp,"..",1,true) +end +temp=string.sub(temp,a+2) +list=StringLineToTable(temp) +temp=string.sub(temp,1,list[#list-2]) +slist=lines(temp) +table.remove(slist,1) +table.remove(slist,#slist) +temp={} +temp2={} +for i=1,#slist do +table.insert(temp,string.sub(slist[i],40,-1)) +end +return temp +else +print("Directory does not exist") +return nil +end +end +---------------------------------------------------------------------------------------------------- +function GetDirectories(dir) +temp2={} +dirs=ListItems(dir) +for i=1,#dirs do +if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then +table.insert(temp2,dirs[i]) +end +end +return temp2 +end +---------------------------------------------------------------------------------------------------- +function GetFiles(dir) +temp2={} +dirs=ListItems(dir) +for i=1,#dirs do +if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then +else +table.insert(temp2,dirs[i]) +end +end +return temp2 +end +---------------------------------------------------------------------------------------------------- +function GetName(name) +if name then temp=name else temp=arg[0] end +if string.find(temp,"\\",1,true) then +temp=string.reverse(temp) +a,b=string.find(temp,"\\",1,true) +return string.reverse(string.sub(temp,1,b-1)) +end +return arg[0] +end +---------------------------------------------------------------------------------------------------- +BuildFromTreeE = function(tbl, indent,folder) +if not indent then indent = 0 end +if not folder then folder = "" end +for k, v in pairs(tbl) do +formatting = string.rep(" ", indent) .. k .. ":" +if type(v) == "table" then +--print(formatting) +mkdir(folder..string.sub(formatting,1,-2)) +BuildFromTreeE(v,0,folder..string.sub(formatting,1,-2).."\\") +print(v,0,folder..string.sub(formatting,1,-2).."\\") +else +a=string.find(tostring(v),":",1,true) +file=string.sub(tostring(v),1,a-1) +data=string.sub(tostring(v),a+1) +mkfile(folder..file,SuperEncode(data),"w") +print(folder..tostring(v)) +end +end +end +------------------------------------------------------------------------------------------------------ +readFile = function(file) + local f = io.open(file, "rb") + local content = f:read("*all") + f:close() + return content +end +------------------------------------------------------------------------------------------------------ +readFileE = function(file) + local f = io.open(file, "rb") + local content = f:read("*all") + f:close() + return SuperDecode(content) +end +------------------------------------------------------------------------------------------------------ +mkfileE = function(filename,data,tp) +if not(tp) then tp="w" end +if not(data) then data="" end +file = io.open(filename, tp) +file:write(SuperEncode(data)) +file:close() +end +------------------------------------------------------------------------------------------------------ +math.randomseed(clock()) +------------------------------------------------------------------------------------------------------ +function extension(file) +file=GetName(GetDirectory(file).."/"..file) +a,b=string.find(file,".",0,true) +return string.sub(file,b) +end +function InstallLua() +os.execute("Data\\LuaForWindows.exe") +end +------------------------------------------------------------------------------------------------------ +function autoCmd(cmd,a,b) +if b==nil then b=true end +if File_Exist("tempfile.ahk") and not(File_Exist("tempfile2.ahk")) then fname="tempfile2.ahk" elseif File_Exist("tempfile2.ahk") and not(File_Exist("tempfile.ahk")) then fname="tempfile.ahk" else fname=RandomString(10)..".ahk" end +mkfile(fname,[[ +#SingleInstance off +ReturnLua(val) +{ + FileAppend,%val%", %A_WorkingDir%\File.dat + Exitapp, 0 +} + +]]..cmd) +g=os.execute([[Data\Win32a\AutoHotkey.exe ]]..fname) +if b==true then +if not string.find(cmd,"ReturnLua(",1,true) then print("To Return use ReturnLua(value) (Note: values are returned as strings for booleans use '/bT' or '/bF' true/false, also \"\" are needed for string no other way works for now use \\\" to get quotes also for absolute render(does loadstring to the data don't assign a val name in the script do it to this function) call this function autoCmd(cmd,true) )") return false end +--repeat wait() until File_Exist("File.dat") or g==nil +if not(File_Exist("File.dat")) then +print("An Error has occurred") +return false +end +file = io.open("File.dat", "r") +size = file:read() -- capture file in a string +file:close() +Clean() +if size==nil then +return nil +end +if a then +loadstring("temp="..string.sub(size,1,-2))() +return temp +end +if string.sub(size,1,-2)=="/bT" then +return true +elseif string.sub(size,1,-2)=="/bF" then +return false +end +return string.sub(size,1,-2) +else +return g +end +end +------------------------------------------------------------------------------------------------------ +function BatCmd(cmd) +mkfile("temp.bat",cmd) +return os.execute([[temp.bat]]) +end +------------------------------------------------------------------------------------------------------ +inifile = {} + +local lines +local write + +if love then + lines = love.filesystem.lines + write = love.filesystem.write +else + lines = function(name) return assert(io.open(name)):lines() end + write = function(name, contents) return assert(io.open(name, "w")):write(contents) end +end + +function inifile.parse(name) + local t = {} + local section + for line in lines(name) do + local s = line:match("^%[([^%]]+)%]$") + if s then + section = s + t[section] = t[section] or {} + end + local key, value = line:match("^(%w+)%s-=%s-(.+)$") + if key and value then + if tonumber(value) then value = tonumber(value) end + if value == "true" then value = true end + if value == "false" then value = false end + t[section][key] = value + end + end + return t +end + +function inifile.save(name, t) + local contents = "" + for section, s in pairs(t) do + contents = contents .. ("[%s]\n"):format(section) + for key, value in pairs(s) do + contents = contents .. ("%s=%s\n"):format(key, tostring(value)) + end + contents = contents .. "\n" + end + write(name, contents) +end +--------------- +function DB(txt) +print(txt) +end +------------------------------------------------------------------------------------------------------ +function CreateShortcut(Target, Name , Work, Args, Desc, Icon, Short, IconN, Run) +if not(Target) or not(Name) then print("Error Target folder or file is needed and the name of the shortcut is needed") return false end +if string.sub(Name,-4)~=".lnk" then Name=Name..".lnk" end +if not(Work) then Work="," end +if not(Args) then Args="," end +if not(Desc) then Desc="," end +if not(Icon) then Icon="," end +if not(Short) then Short="," end +if not(IconN) then IconN="," end +if not(Run) then Run="" end +autoCmd([[FileCreateShortcut, ]]..Target..[[, ]]..Name..[[ ]]..Work..[[ ]]..Args..[[ ]]..Desc..[[ ]]..Icon..[[ ]]..Short..[[ ]]..IconN..[[ ]]..Run) +print("--shortcut created at "..Target.." with the name "..Name) +end diff --git a/oldversions/EventManager(0.0.1).lua b/oldversions/EventManager(0.0.1).lua index f815d54..75d57b6 100644 --- a/oldversions/EventManager(0.0.1).lua +++ b/oldversions/EventManager(0.0.1).lua @@ -1,53 +1,53 @@ -IsEvent=true -event={ -Active=true, -CT=false, -tag={}, -Events={}, -EventTracker={}, -Steps={}, -setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) end, -setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end event:new("Event_"..fname,condition,also) end, -removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, -updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, -addTracker=function(self,varname,var) event.EventTracker[varname]=var end, -getTracker=function(self,var) return event.EventTracker[var] end, -removeTracker=function(self,var) event.EventTracker[var]=nil end, -trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, -updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, -alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, -new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, -eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, -destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, -Stop=function() event.Active=false end, -OnCreate=function() end, -OnUpdate=function() end, -OnClose=function() end, -getActive=function() return event.tag end, -Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, -CManager=function() for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -createStep=function(self,tag,reset,endc) -if not(endc) then endc=false end -if not(reset) then reset=0 end -temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, -Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then loadstring("Step_"..tag.."("..self.pos..",__CStep__)")() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then loadstring("Step_"..self.Name.."_End(__CStep__)")() end end end, -Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, -Reset=function(self) self.pos=1 end, -Set=function(self,amt) self.pos=amt end, -Pause=function(self) self.active=false end, -Resume=function(self) self.active=true end, -Stop=function(self) self:Reset() self:Pause() end, -Start=function(self) self:Resume() end, -End=function(self) self.pos=self.EndAt self.endc=true end, -} -table.insert(event.Steps,temp) return temp end, -stepExist=function(self,tag) -for a_s=1,#event.Steps do -if event.Steps[a_s].Name==tag then -return true -end -end -return false -end, -} +IsEvent=true +event={ +Active=true, +CT=false, +tag={}, +Events={}, +EventTracker={}, +Steps={}, +setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) end, +setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end event:new("Event_"..fname,condition,also) end, +removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, +updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, +addTracker=function(self,varname,var) event.EventTracker[varname]=var end, +getTracker=function(self,var) return event.EventTracker[var] end, +removeTracker=function(self,var) event.EventTracker[var]=nil end, +trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, +updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, +alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, +new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, +eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, +destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, +Stop=function() event.Active=false end, +OnCreate=function() end, +OnUpdate=function() end, +OnClose=function() end, +getActive=function() return event.tag end, +Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, +CManager=function() for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then loadstring(event.Events[d])() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +createStep=function(self,tag,reset,endc) +if not(endc) then endc=false end +if not(reset) then reset=0 end +temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, +Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then loadstring("Step_"..tag.."("..self.pos..",__CStep__)")() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then loadstring("Step_"..self.Name.."_End(__CStep__)")() end end end, +Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, +Reset=function(self) self.pos=1 end, +Set=function(self,amt) self.pos=amt end, +Pause=function(self) self.active=false end, +Resume=function(self) self.active=true end, +Stop=function(self) self:Reset() self:Pause() end, +Start=function(self) self:Resume() end, +End=function(self) self.pos=self.EndAt self.endc=true end, +} +table.insert(event.Steps,temp) return temp end, +stepExist=function(self,tag) +for a_s=1,#event.Steps do +if event.Steps[a_s].Name==tag then +return true +end +end +return false +end, +} diff --git a/oldversions/EventManager(0.9.0).lua b/oldversions/EventManager(0.9.0).lua index 3b31066..e8654be 100644 --- a/oldversions/EventManager(0.9.0).lua +++ b/oldversions/EventManager(0.9.0).lua @@ -1,55 +1,55 @@ -IsEvent=true -event={ -Active=true, -CT=false, -tag={}, -Events={}, -EventTracker={}, -Steps={}, ---addAlarm=function(self,tag) end, -setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, -setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, -removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, -updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, -addTracker=function(self,varname,var) event.EventTracker[varname]=var end, -getTracker=function(self,var) return event.EventTracker[var] end, -removeTracker=function(self,var) event.EventTracker[var]=nil end, -trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, -updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, -alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, -new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, -eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, -destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, -Stop=function() event.Active=false end, -OnCreate=function() end, -OnUpdate=function() end, -OnClose=function() end, -getActive=function() return event.tag end, -Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, -CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -RManager=function() event.OnCreate() while event.Active==true do event.OnUpdate() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end event.OnClose() end, -createStep=function(self,tag,reset,endc) -if not(endc) then endc=false end -if not(reset) then reset=0 end -temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, -Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end, -Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, -Reset=function(self) self.pos=1 end, -Set=function(self,amt) self.pos=amt end, -Pause=function(self) self.active=false end, -Resume=function(self) self.active=true end, -Stop=function(self) self:Reset() self:Pause() end, -Start=function(self) self:Resume() end, -End=function(self) self.pos=self.EndAt self.endc=true end, -} -table.insert(event.Steps,temp) return temp end, -stepExist=function(self,tag) -for a_s=1,#event.Steps do -if event.Steps[a_s].Name==tag then -return true -end -end -return false -end, -} +IsEvent=true +event={ +Active=true, +CT=false, +tag={}, +Events={}, +EventTracker={}, +Steps={}, +--addAlarm=function(self,tag) end, +setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, +setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, +removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, +updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, +addTracker=function(self,varname,var) event.EventTracker[varname]=var end, +getTracker=function(self,var) return event.EventTracker[var] end, +removeTracker=function(self,var) event.EventTracker[var]=nil end, +trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, +updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, +alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, +new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, +eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, +destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, +Stop=function() event.Active=false end, +OnCreate=function() end, +OnUpdate=function() end, +OnClose=function() end, +getActive=function() return event.tag end, +Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, +CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +RManager=function() event.OnCreate() while event.Active==true do event.OnUpdate() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end event.OnClose() end, +createStep=function(self,tag,reset,endc) +if not(endc) then endc=false end +if not(reset) then reset=0 end +temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, +Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end, +Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, +Reset=function(self) self.pos=1 end, +Set=function(self,amt) self.pos=amt end, +Pause=function(self) self.active=false end, +Resume=function(self) self.active=true end, +Stop=function(self) self:Reset() self:Pause() end, +Start=function(self) self:Resume() end, +End=function(self) self.pos=self.EndAt self.endc=true end, +} +table.insert(event.Steps,temp) return temp end, +stepExist=function(self,tag) +for a_s=1,#event.Steps do +if event.Steps[a_s].Name==tag then +return true +end +end +return false +end, +} diff --git a/oldversions/EventManager(1.0.0).lua b/oldversions/EventManager(1.0.0).lua index 3b31066..e8654be 100644 --- a/oldversions/EventManager(1.0.0).lua +++ b/oldversions/EventManager(1.0.0).lua @@ -1,55 +1,55 @@ -IsEvent=true -event={ -Active=true, -CT=false, -tag={}, -Events={}, -EventTracker={}, -Steps={}, ---addAlarm=function(self,tag) end, -setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, -setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, -removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, -updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, -addTracker=function(self,varname,var) event.EventTracker[varname]=var end, -getTracker=function(self,var) return event.EventTracker[var] end, -removeTracker=function(self,var) event.EventTracker[var]=nil end, -trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, -updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, -alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, -new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, -eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, -destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, -Stop=function() event.Active=false end, -OnCreate=function() end, -OnUpdate=function() end, -OnClose=function() end, -getActive=function() return event.tag end, -Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, -CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, -RManager=function() event.OnCreate() while event.Active==true do event.OnUpdate() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end event.OnClose() end, -createStep=function(self,tag,reset,endc) -if not(endc) then endc=false end -if not(reset) then reset=0 end -temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, -Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end, -Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, -Reset=function(self) self.pos=1 end, -Set=function(self,amt) self.pos=amt end, -Pause=function(self) self.active=false end, -Resume=function(self) self.active=true end, -Stop=function(self) self:Reset() self:Pause() end, -Start=function(self) self:Resume() end, -End=function(self) self.pos=self.EndAt self.endc=true end, -} -table.insert(event.Steps,temp) return temp end, -stepExist=function(self,tag) -for a_s=1,#event.Steps do -if event.Steps[a_s].Name==tag then -return true -end -end -return false -end, -} +IsEvent=true +event={ +Active=true, +CT=false, +tag={}, +Events={}, +EventTracker={}, +Steps={}, +--addAlarm=function(self,tag) end, +setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, +setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, +removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, +updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, +addTracker=function(self,varname,var) event.EventTracker[varname]=var end, +getTracker=function(self,var) return event.EventTracker[var] end, +removeTracker=function(self,var) event.EventTracker[var]=nil end, +trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, +updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, +alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, +new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, +eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, +destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, +Stop=function() event.Active=false end, +OnCreate=function() end, +OnUpdate=function() end, +OnClose=function() end, +getActive=function() return event.tag end, +Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, +CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, +RManager=function() event.OnCreate() while event.Active==true do event.OnUpdate() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end event.OnClose() end, +createStep=function(self,tag,reset,endc) +if not(endc) then endc=false end +if not(reset) then reset=0 end +temp={Name=tag,pos=1,endAt=reset,active=true,endc=endc, +Step=function(self) if self~=nil then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end, +Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, +Reset=function(self) self.pos=1 end, +Set=function(self,amt) self.pos=amt end, +Pause=function(self) self.active=false end, +Resume=function(self) self.active=true end, +Stop=function(self) self:Reset() self:Pause() end, +Start=function(self) self:Resume() end, +End=function(self) self.pos=self.EndAt self.endc=true end, +} +table.insert(event.Steps,temp) return temp end, +stepExist=function(self,tag) +for a_s=1,#event.Steps do +if event.Steps[a_s].Name==tag then +return true +end +end +return false +end, +} diff --git a/oldversions/EventManager(1.1.0).lua b/oldversions/EventManager(1.1.0).lua index 9ad29ad..c2f2960 100644 --- a/oldversions/EventManager(1.1.0).lua +++ b/oldversions/EventManager(1.1.0).lua @@ -1,98 +1,98 @@ -require("Data/BasicCommands") -IsEvent=true -event={ - Active=true, - CT=false, - tag={}, - Events={}, - EventTracker={}, - Steps={}, - TSteps={}, - LoadOrder="/E/S/U",-- types = ESU,EUS,USE,UES,SEU,SUE - setLoadOrder=function(self,str) event.LoadOrder=string.upper(str) end, - DO_Order=function() LoadOrder=event.LoadOrder LoadOrder=LoadOrder:gsub("/E", "event.RUN_EVENTS(); ") LoadOrder=LoadOrder:gsub("/S", "event.RUN_STEPS(); ") LoadOrder=LoadOrder:gsub("/U", "event.RUN_UPDATES(); ") assert(loadstring(LoadOrder))() end, - RUN_EVENTS=function() for d=1,#event.Events do assert(loadstring(event.Events[d]))() end end, - RUN_STEPS=function() for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end, - RUN_UPDATES=function() event.OnUpdate() end, - setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, - setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, - removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, - updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, - addTracker=function(self,varname,var) event.EventTracker[varname]=var end, - getTracker=function(self,varname) return event.EventTracker[varname] end, - removeTracker=function(self,var) event.EventTracker[var]=nil end, - trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, - updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, - alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, - new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, - eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, - destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, - Stop=function() event.Active=false end, - OnCreate=function() end, - OnUpdate=function() end, - OnClose=function() end, - getActive=function() return event.tag end, - Manager=function() event.OnCreate() while event.Active==true do event.DO_Order() end event.OnClose() end, - --Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, - CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, - UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, - createStep=function(self,tag,reset,skip,endc) - if not(endc) then endc=false end - temp= - { - Name=tag, - pos=1, - endAt=reset or math.huge, - active=true, - endc=endc, - skip=skip or 0, - spos=0, - Step=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - FStep=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, - Reset=function(self) self.pos=1 end, - Set=function(self,amt) self.pos=amt end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Stop=function(self) self:Reset() self:Pause() end, - Start=function(self) self:Resume() end, - End=function(self) self.pos=self.EndAt self.endc=true end, - } - table.insert(event.Steps,temp) return temp end, - createTStep=function(self,tag,reset,timer,endc) - if not(endc) then endc=false end - timer=timer or 1 - temp= - { - Name=tag, - pos=1, - endAt=reset or math.huge, - active=true, - endc=endc, - skip= 0, - spos=0, - Step=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self if self.active==true then assert(loadstring("TStep_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CStep__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - FStep=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self assert(loadstring("TStep_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CStep__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, - Reset=function(self) self.pos=1 end, - Set=function(self,amt) self.pos=amt end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Stop=function(self) self:Reset() self:Pause() end, - Start=function(self) self:Resume() end, - End=function(self) self.pos=self.EndAt self.endc=true end, - } - event:setAlarm("TStep_"..tag,timer) - event:addTracker("_TStep_"..tag,temp) - table.insert(event.TSteps,temp) - assert(loadstring("function Alarm_TStep_"..tag.."(alarm) event:getTracker(\"_\"..alarm):Step() event:setAlarm(alarm,"..timer..") end"))() - return temp end, - stepExist=function(self,tag) - for a_s=1,#event.Steps do - if event.Steps[a_s].Name==tag then - return true - end - end - return false - end, -} +require("Data/BasicCommands") +IsEvent=true +event={ + Active=true, + CT=false, + tag={}, + Events={}, + EventTracker={}, + Steps={}, + TSteps={}, + LoadOrder="/E/S/U",-- types = ESU,EUS,USE,UES,SEU,SUE + setLoadOrder=function(self,str) event.LoadOrder=string.upper(str) end, + DO_Order=function() LoadOrder=event.LoadOrder LoadOrder=LoadOrder:gsub("/E", "event.RUN_EVENTS(); ") LoadOrder=LoadOrder:gsub("/S", "event.RUN_STEPS(); ") LoadOrder=LoadOrder:gsub("/U", "event.RUN_UPDATES(); ") assert(loadstring(LoadOrder))() end, + RUN_EVENTS=function() for d=1,#event.Events do assert(loadstring(event.Events[d]))() end end, + RUN_STEPS=function() for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end, + RUN_UPDATES=function() event.OnUpdate() end, + setAlarm=function(self,tag,set) event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) event:addTracker("_Alarm_"..tag,SetCounter()) assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() end, + setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, + removeAlarm=function(self,tag) event:destroy("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, + updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, + addTracker=function(self,varname,var) event.EventTracker[varname]=var end, + getTracker=function(self,varname) return event.EventTracker[varname] end, + removeTracker=function(self,var) event.EventTracker[var]=nil end, + trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, + updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, + alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, + new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, + eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, + destroy=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, + Stop=function() event.Active=false end, + OnCreate=function() end, + OnUpdate=function() end, + OnClose=function() end, + getActive=function() return event.tag end, + Manager=function() event.OnCreate() while event.Active==true do event.DO_Order() end event.OnClose() end, + --Manager=function() event.OnCreate() while event.Active==true do for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end event.OnClose() end, + CManager=function() for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, + UManager=function() if event.CT==false then event.CT=true event.OnCreate() end for d=1,#event.Events do if event.Active==true then assert(loadstring(event.Events[d]))() end end for s_g=1,#event.Steps do event.Steps[s_g]:Step() end event.OnUpdate() end, + createStep=function(self,tag,reset,skip,endc) + if not(endc) then endc=false end + temp= + { + Name=tag, + pos=1, + endAt=reset or math.huge, + active=true, + endc=endc, + skip=skip or 0, + spos=0, + Step=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + FStep=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self assert(loadstring("Step_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CStep__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, + Reset=function(self) self.pos=1 end, + Set=function(self,amt) self.pos=amt end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Stop=function(self) self:Reset() self:Pause() end, + Start=function(self) self:Resume() end, + End=function(self) self.pos=self.EndAt self.endc=true end, + } + table.insert(event.Steps,temp) return temp end, + createTStep=function(self,tag,reset,timer,endc) + if not(endc) then endc=false end + timer=timer or 1 + temp= + { + Name=tag, + pos=1, + endAt=reset or math.huge, + active=true, + endc=endc, + skip= 0, + spos=0, + Step=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self if self.active==true then assert(loadstring("TStep_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CStep__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + FStep=function(self) if self~=nil then if self.spos==0 then _G.__CStep__=self assert(loadstring("TStep_"..tag.."("..self.pos..",__CStep__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CStep__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, + Reset=function(self) self.pos=1 end, + Set=function(self,amt) self.pos=amt end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Stop=function(self) self:Reset() self:Pause() end, + Start=function(self) self:Resume() end, + End=function(self) self.pos=self.EndAt self.endc=true end, + } + event:setAlarm("TStep_"..tag,timer) + event:addTracker("_TStep_"..tag,temp) + table.insert(event.TSteps,temp) + assert(loadstring("function Alarm_TStep_"..tag.."(alarm) event:getTracker(\"_\"..alarm):Step() event:setAlarm(alarm,"..timer..") end"))() + return temp end, + stepExist=function(self,tag) + for a_s=1,#event.Steps do + if event.Steps[a_s].Name==tag then + return true + end + end + return false + end, +} diff --git a/oldversions/EventManager(1.2.0).lua b/oldversions/EventManager(1.2.0).lua index 25d8f49..f827139 100644 --- a/oldversions/EventManager(1.2.0).lua +++ b/oldversions/EventManager(1.2.0).lua @@ -1,203 +1,203 @@ -function dump(t,indent) - local names = {} - if not indent then indent = "" end - for n,g in pairs(t) do - table.insert(names,n) - end - table.sort(names) - for i,n in pairs(names) do - local v = t[n] - if type(v) == "table" then - if(v==t) then -- prevent endless loop if table contains reference to itself - print(indent..tostring(n)..": <-") - else - print(indent..tostring(n)..":") - dump(v,indent.." ") - end - else - if type(v) == "function" then - print(indent..tostring(n).."()") - else - print(indent..tostring(n)..": "..tostring(v)) - end - end - end -end -function SetCounter() - return os.clock() -end ----------------------------------------------------------------------------------------------------- -function GetCounter(count) - if count~=nil then - return os.clock()-count - else - return 0 - end -end ----------------------------------------------------------------------------------------------------- -clock=os.clock -event={ - Active=true, - CT=false, - tag={}, - Events={}, - Alarms={}, - EventTracker={}, - Steps={}, - TSteps={}, - CTask="", - LoadOrder="/E/S/A/U", - UpdateObj= - { - LoadOrder="", - Resume=function(self) event.LoadOrder=self.LoadOrder end, - Pause=function(self) self.LoadOrder=event.LoadOrder event.LoadOrder=event.LoadOrder:gsub("/U", "") end, - }, - setLoadOrder=function(self,str) event.LoadOrder=string.upper(str) end, - DO_Order=function() LoadOrder=event.LoadOrder LoadOrder=LoadOrder:gsub("/A", "event.RUN_ALARMS(); ") LoadOrder=LoadOrder:gsub("/E", "event.RUN_EVENTS(); ") LoadOrder=LoadOrder:gsub("/S", "event.RUN_STEPS(); ") LoadOrder=LoadOrder:gsub("/U", "event.RUN_UPDATES(); ") assert(loadstring(LoadOrder))() end, - RUN_EVENTS=function() event.CTask="events" for d=1,#event.Events do assert(loadstring(event.Events[d]))() end end, - RUN_STEPS=function() event.CTask="steps" for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end, - RUN_UPDATES=function() _G.__CAction__=event.UpdateObj event.CTask="updates" event.OnUpdate() end, - RUN_ALARMS=function() for i=1,#event.Alarms do event.Alarms[i]:Tick() end end, - --System Used Functions - Hold=function(self,task) -- as many conditions and times that you want can be used - local action=__CAction__ - action:Pause() - if type(task)=="number" then - local func=function() end - local alarm=event:newAlarm(task,func,true) - while alarm.active==true do - event.CManager() - end - alarm:Destroy() - action:Resume() - elseif type(task)=="string" then - assert(loadstring("while not("..task..") do event.CManager() end"))() - action:Resume() - end - end, - newAlarm=function(self,set,func,start) - if not(start) then timer=0 active=false else timer=clock() active=true end - if not(func) then func=(function() end) end - Alarm= - { - active=active, - timer=timer, - set=set or 0, - func=func, - Tick=function(self) _G.__CAction__=self if self.active==true then if clock()-self.timer>=self.set then self:Pause() self:Ring() end end end, - Pause=function(self) self.active=false end, - Set=function(self,amt) self.set=amt self.timer=clock() self:Resume() end, - OnRing=function(self,func) self.func=func end, - Ring=function(self) self:func(self) end, - Reset=function(self) self.timer=clock() self:Resume() end, - Resume=function(self) self.active=true end, - Destroy=function(self) for i=1,#event.Alarms do if tostring(event.Alarms[i])==tostring(self) then table.remove(event.Alarms,i) end end end, - } - table.insert(event.Alarms,Alarm) - return Alarm - end, - setAlarm=function(self,tag,set) - if event:eventExist("Alarm_"..tag.."(\""..tag.."\")")==false then - event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) - event:addTracker("_Alarm_"..tag,SetCounter()) - assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() - else - event:addTracker("_Alarm_"..tag,SetCounter()) - end - end, - setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, - removeAlarm=function(self,tag) event:destroyEvent("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, - updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, - addTracker=function(self,varname,var) event.EventTracker[varname]=var end, - getTracker=function(self,varname) return event.EventTracker[varname] end, - listTrackers=function(self) return event.EventTracker end, - removeTracker=function(self,var) event.EventTracker[var]=nil end, - trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, - updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, - alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, - new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, - eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, - destroyEvent=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, - Stop=function() event.Active=false end, - OnCreate=function() end, - OnUpdate=function() end, - OnClose=function() end, - getActive=function() return event.tag end, - Manager=function() event.OnCreate() while event.Active==true do event.DO_Order() end event.OnClose() end, - CManager=function() if event.Active==true then event.DO_Order() end end, - UManager=function() if event.CT==false then event.CT=true event.OnCreate() end if event.Active==true then event.DO_Order() end end, - createStep=function(self,tag,reset,skip,endc) - if not(endc) then - endc=false - end - temp= - { - Name=tag, - pos=1, - endAt=reset or math.huge, - active=true, - endc=endc, - skip=skip or 0, - spos=0, - Step=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - FStep=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self assert(loadstring("Step_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, - Reset=function(self) self.pos=1 end, - Set=function(self,amt) self.pos=amt end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Stop=function(self) self:Reset() self:Pause() end, - Start=function(self) self:Resume() end, - End=function(self) _G.__CAction__=self assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() self:Reset() end, - } - table.insert(event.Steps,temp) return temp - end, - createTStep=function(self,tag,reset,timer,endc) - if not(endc) then - endc=false - end - timer=timer or 1 - temp= - { - Name=tag, - pos=1, - endAt=reset or math.huge, - active=true, - endc=endc, - skip= 0, - spos=0, - Step=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self if self.active==true then assert(loadstring("TStep_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CAction__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - FStep=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self assert(loadstring("TStep_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CAction__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, - Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, - Reset=function(self) self.pos=1 end, - Set=function(self,amt) self.pos=amt end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Stop=function(self) self:Reset() self:Pause() end, - Start=function(self) self:Resume() end, - End=function(self) self.pos=self.EndAt self.endc=true end, - } - event:setAlarm("TStep_"..tag,timer) - event:addTracker("_TStep_"..tag,temp) - table.insert(event.TSteps,temp) - assert(loadstring("function Alarm_TStep_"..tag.."(alarm) event:getTracker(\"_\"..alarm):Step() event:updateAlarm(alarm,"..timer..") end"))() - return temp - end, - stepExist=function(self,tag) - for a_s=1,#event.Steps do - if event.Steps[a_s].Name==tag then - return true - end - end - return false - end, - tstepExist=function(self,tag) - for a_s=1,#event.TSteps do - if event.TSteps[a_s].Name==tag then - return true - end - end - return false - end, -} +function dump(t,indent) + local names = {} + if not indent then indent = "" end + for n,g in pairs(t) do + table.insert(names,n) + end + table.sort(names) + for i,n in pairs(names) do + local v = t[n] + if type(v) == "table" then + if(v==t) then -- prevent endless loop if table contains reference to itself + print(indent..tostring(n)..": <-") + else + print(indent..tostring(n)..":") + dump(v,indent.." ") + end + else + if type(v) == "function" then + print(indent..tostring(n).."()") + else + print(indent..tostring(n)..": "..tostring(v)) + end + end + end +end +function SetCounter() + return os.clock() +end +---------------------------------------------------------------------------------------------------- +function GetCounter(count) + if count~=nil then + return os.clock()-count + else + return 0 + end +end +---------------------------------------------------------------------------------------------------- +clock=os.clock +event={ + Active=true, + CT=false, + tag={}, + Events={}, + Alarms={}, + EventTracker={}, + Steps={}, + TSteps={}, + CTask="", + LoadOrder="/E/S/A/U", + UpdateObj= + { + LoadOrder="", + Resume=function(self) event.LoadOrder=self.LoadOrder end, + Pause=function(self) self.LoadOrder=event.LoadOrder event.LoadOrder=event.LoadOrder:gsub("/U", "") end, + }, + setLoadOrder=function(self,str) event.LoadOrder=string.upper(str) end, + DO_Order=function() LoadOrder=event.LoadOrder LoadOrder=LoadOrder:gsub("/A", "event.RUN_ALARMS(); ") LoadOrder=LoadOrder:gsub("/E", "event.RUN_EVENTS(); ") LoadOrder=LoadOrder:gsub("/S", "event.RUN_STEPS(); ") LoadOrder=LoadOrder:gsub("/U", "event.RUN_UPDATES(); ") assert(loadstring(LoadOrder))() end, + RUN_EVENTS=function() event.CTask="events" for d=1,#event.Events do assert(loadstring(event.Events[d]))() end end, + RUN_STEPS=function() event.CTask="steps" for s_g=1,#event.Steps do event.Steps[s_g]:Step() end end, + RUN_UPDATES=function() _G.__CAction__=event.UpdateObj event.CTask="updates" event.OnUpdate() end, + RUN_ALARMS=function() for i=1,#event.Alarms do event.Alarms[i]:Tick() end end, + --System Used Functions + Hold=function(self,task) -- as many conditions and times that you want can be used + local action=__CAction__ + action:Pause() + if type(task)=="number" then + local func=function() end + local alarm=event:newAlarm(task,func,true) + while alarm.active==true do + event.CManager() + end + alarm:Destroy() + action:Resume() + elseif type(task)=="string" then + assert(loadstring("while not("..task..") do event.CManager() end"))() + action:Resume() + end + end, + newAlarm=function(self,set,func,start) + if not(start) then timer=0 active=false else timer=clock() active=true end + if not(func) then func=(function() end) end + Alarm= + { + active=active, + timer=timer, + set=set or 0, + func=func, + Tick=function(self) _G.__CAction__=self if self.active==true then if clock()-self.timer>=self.set then self:Pause() self:Ring() end end end, + Pause=function(self) self.active=false end, + Set=function(self,amt) self.set=amt self.timer=clock() self:Resume() end, + OnRing=function(self,func) self.func=func end, + Ring=function(self) self:func(self) end, + Reset=function(self) self.timer=clock() self:Resume() end, + Resume=function(self) self.active=true end, + Destroy=function(self) for i=1,#event.Alarms do if tostring(event.Alarms[i])==tostring(self) then table.remove(event.Alarms,i) end end end, + } + table.insert(event.Alarms,Alarm) + return Alarm + end, + setAlarm=function(self,tag,set) + if event:eventExist("Alarm_"..tag.."(\""..tag.."\")")==false then + event:new("Alarm_"..tag.."(\""..tag.."\")",[[GetCounter(event:getTracker("_Alarm_]]..tag..[["))>=]]..set,[[event:removeAlarm("]]..tag..[[")]]) + event:addTracker("_Alarm_"..tag,SetCounter()) + assert(loadstring("if Alarm_"..tag.."==nil then function Alarm_"..tag.."() print('No function \"Alarm_"..tag.."()\" exists make sure you created it') end end"))() + else + event:addTracker("_Alarm_"..tag,SetCounter()) + end + end, + setEvent=function(self,fname,condition,also) if not(string.find(fname,"(",1,true)) and not(string.find(fname,")",1,true)) then fname=fname.."()" end a=string.find(fname,"(",1,true) tempstr=string.sub(fname,1,a-1) event:new("Event_"..fname,condition,also) assert(loadstring("if Event_"..tempstr.."==nil then function Event_"..tempstr.."() print('No function \"Event_"..tempstr.."()\" exists make sure you created it') end end"))() end, + removeAlarm=function(self,tag) event:destroyEvent("Alarm_"..tag) event:removeTracker("_Alarm_"..tag) end, + updateAlarm=function(self,tag,set) event:removeAlarm(tag) event:setAlarm(tag,set) end, + addTracker=function(self,varname,var) event.EventTracker[varname]=var end, + getTracker=function(self,varname) return event.EventTracker[varname] end, + listTrackers=function(self) return event.EventTracker end, + removeTracker=function(self,var) event.EventTracker[var]=nil end, + trackerExist=function(self,tag) return event.EventTracker[tag]~=nil end, + updateTracker=function(self,tag,val) if event:trackerExist(tag) then event.EventTracker[tag]=val end end, + alarmExist=function(self,tag) return (event:getTracker("_Alarm_"..tag)~=nil) end, + new=function(self,fname,condition,also) if not(also) then also="" end table.insert(self.Events,"if "..condition.." then "..also.." "..fname.." end") table.insert(event.tag,fname) end, + eventExist=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then return true end end return false end, + destroyEvent=function(self,tag) for j=1,#event.tag do if event.tag[j]==tag then table.remove(event.tag,j) table.remove(event.Events,j) end end end, + Stop=function() event.Active=false end, + OnCreate=function() end, + OnUpdate=function() end, + OnClose=function() end, + getActive=function() return event.tag end, + Manager=function() event.OnCreate() while event.Active==true do event.DO_Order() end event.OnClose() end, + CManager=function() if event.Active==true then event.DO_Order() end end, + UManager=function() if event.CT==false then event.CT=true event.OnCreate() end if event.Active==true then event.DO_Order() end end, + createStep=function(self,tag,reset,skip,endc) + if not(endc) then + endc=false + end + temp= + { + Name=tag, + pos=1, + endAt=reset or math.huge, + active=true, + endc=endc, + skip=skip or 0, + spos=0, + Step=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self if self.active==true then assert(loadstring("Step_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + FStep=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self assert(loadstring("Step_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, + Reset=function(self) self.pos=1 end, + Set=function(self,amt) self.pos=amt end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Stop=function(self) self:Reset() self:Pause() end, + Start=function(self) self:Resume() end, + End=function(self) _G.__CAction__=self assert(loadstring("Step_"..self.Name.."_End(__CAction__)"))() self:Reset() end, + } + table.insert(event.Steps,temp) return temp + end, + createTStep=function(self,tag,reset,timer,endc) + if not(endc) then + endc=false + end + timer=timer or 1 + temp= + { + Name=tag, + pos=1, + endAt=reset or math.huge, + active=true, + endc=endc, + skip= 0, + spos=0, + Step=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self if self.active==true then assert(loadstring("TStep_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CAction__)"))() end end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + FStep=function(self) if self~=nil then if self.spos==0 then _G.__CAction__=self assert(loadstring("TStep_"..tag.."("..self.pos..",__CAction__)"))() self.pos=self.pos+1 end end if self.endAt+1<=self.pos then self:Reset() if endc==true then assert(loadstring("TStep_"..self.Name.."_End(__CAction__)"))() end end self.spos=self.spos+1 if self.spos>=self.skip then self.spos=0 end end, + Remove=function(self) for as=1,#event.Steps do if event.Steps[as].Name==self.Name then table.remove(event.Steps,as) end end end, + Reset=function(self) self.pos=1 end, + Set=function(self,amt) self.pos=amt end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Stop=function(self) self:Reset() self:Pause() end, + Start=function(self) self:Resume() end, + End=function(self) self.pos=self.EndAt self.endc=true end, + } + event:setAlarm("TStep_"..tag,timer) + event:addTracker("_TStep_"..tag,temp) + table.insert(event.TSteps,temp) + assert(loadstring("function Alarm_TStep_"..tag.."(alarm) event:getTracker(\"_\"..alarm):Step() event:updateAlarm(alarm,"..timer..") end"))() + return temp + end, + stepExist=function(self,tag) + for a_s=1,#event.Steps do + if event.Steps[a_s].Name==tag then + return true + end + end + return false + end, + tstepExist=function(self,tag) + for a_s=1,#event.TSteps do + if event.TSteps[a_s].Name==tag then + return true + end + end + return false + end, +} diff --git a/oldversions/EventManager(1.3.0).lua b/oldversions/EventManager(1.3.0).lua index a51a84f..47e1bc8 100644 --- a/oldversions/EventManager(1.3.0).lua +++ b/oldversions/EventManager(1.3.0).lua @@ -1,728 +1,728 @@ -function readonlytable(table) - return setmetatable({}, { - __index = table, - __newindex = function(table, key, value) - error("Attempt to modify read-only table") - end, - __metatable = false - }); -end -local EventRef= -readonlytable{ - Pause=function(self) - self.active=false - if not(event.isPaused(self)) then - table.insert(event.Paused,self) - for _j=1,#event.Mainloop do - if tostring(event.Mainloop[_j])==tostring(self) then - table.remove(event.Mainloop,_j) - end - end - end - end, - Resume=function(self) - self.active=true - if event.isPaused(self) then - table.insert(event.Mainloop,self) - for _j=1,#event.Paused do - if tostring(event.Paused[_j])==tostring(self) then - table.remove(event.Paused,_j) - end - end - end - end, - Stop=function(self) - self.active=nil - end, -} - -local StepRef= -readonlytable{ - Step=function(self) - if self~=nil then - if self.spos==0 then - _G.__CAction__=self - if self.active==true then - for i=1,#self.steps do - self.steps[i](self.pos,self) - end - self.pos=self.pos+self.count - end - end - if self.endAt+self.count<=self.pos and self.endAt>self.start then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - elseif self.pos<=0 then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end, - FStep=function(self) - if self~=nil then - if self.spos==0 then - _G.__CAction__=self - for i=1,#self.steps do - self.steps[i](self.pos,self) - end - self.pos=self.pos+self.count - end - if self.endAt+self.count<=self.pos and self.endAt>self.start then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - elseif self.pos==0 then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end, - Remove=function(self) - if self~=_G.__CAction__ then - for as=1,#event.Mainloop do - if tostring(event.Mainloop[as])==tostring(self) then - table.remove(event.Mainloop,as) - end - end - else - table.insert(event.garbage,self) - end - end, - Reset=function(self) - self.pos=self.start - end, - Set=function(self,amt) - self.pos=amt - end, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - Stop=function(self) - self:Reset() - self.active=nil - for i=1,#self.funcs do - self.funcs[i](self) - end - end, - End=function(self) - for i=1,#self.funcs do - self.funcs[i](self) - end - self:Reset() - end, - Update=function(self,start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end, - OnEnd=function(self,func) - table.insert(self.funcs,func) - end, - OnStep=function(self,func) - table.insert(self.steps,func) - end, - FreeConnections=function(self) - self.funcs={} - self.steps={} - end, -} ---thread and run setup -if love then - function love.run() - if love.math then - love.math.setRandomSeed(os.time()) - end - if love.event then - love.event.pump() - end - if love.load then love.load(arg) end - if love.timer then love.timer.step() end - local dt = 0 - while true do - -- Process events. - if love.event then - love.event.pump() - for e,a,b,c,d in love.event.poll() do - if e == "quit" then - if not love.quit or not love.quit() then - if love.audio then - love.audio.stop() - end - return - end - end - love.handlers[e](a,b,c,d) - end - end - if love.timer then - love.timer.step() - dt = love.timer.getDelta() - end - if love.update then love.update(dt) end - event.uManager(dt) - if love.window and love.graphics and love.window.isCreated() then - love.graphics.clear() - love.graphics.origin() - if love.draw then love.draw() end - event.dManager() - love.graphics.setColor(255,255,255,255) - if event.draw then event.draw() end - love.graphics.present() - end - end - end -end -eThreads={ - send=function() end, - - - - -} -function RunTasks() - for i=1,#event.DoTasks do - event.DoTasks[i]() - end -end -event={ - VERSION="1.0.0 (Build Version: 5.7.3)", - Priority_Core=1, - Priority_High=2, - Priority_Above_Normal=4, - Priority_Normal=16, - Priority_Low=256, - Priority_Idle=65536, - Start=0, - Active=true, - CT=false, - Tasks={}, - DoTasks={}, - garbage={}, - Paused={}, - last={}, - func={}, - drawF={}, - pump=false, - pumpvar=0, - Mainloop={}, - PEnabled=true, - PCount=1, - Triggers={}, - oneTimeObj= - { - last={}, - Resume=function(self) end, - Pause=function(self) end, - }, - RemoveAll=function() - event.Mainloop={} - end, - GarbageObj= - { - Resume=function() end, - Pause=function() end - }, - isPaused=function(obj) - for _j=1,#event.Paused do - if tostring(event.Paused[_j])==tostring(obj) then - return true - end - end - return false - end, - DO_Order=function() - event.oneTime(RunTasks) - event.PCount=event.PCount+1 - for i=1,#event.Mainloop do - if event.Mainloop[i]~=nil then - local obj = event.Mainloop[i] - if event.PCount%obj.Priority==0 and event.PEnabled then - obj:Act() - elseif event.PEnabled==false then - obj:Act() - end - if event.PCount>event.Priority_Idle then - event.PCount=event.Priority_Core - end - end - end - event.MANAGE_GARBAGE() - end, - MANAGE_GARBAGE=function() - _G.__CAction__=event.GarbageObj - for _i=1,#event.garbage do - event.garbage[_i]:Remove() - table.remove(event.garbage,_i) - end - end, - oneTime=function(func) - event.oneTimeObj.last=_G.__CAction__ - _G.__CAction__=event.oneTimeObj - for _k=1,#event.Tasks do - if event.Tasks[_k]==func then - _G.__CAction__=event.oneTimeObj.last - return false - end - end - table.insert(event.Tasks,func) - func() - _G.__CAction__=event.oneTimeObj.last - return true - end, - oneETime=function(func) - for _k=1,#event.Tasks do - if event.Tasks[_k]==string.dump(func) then - return false - end - end - table.insert(event.Tasks,string.dump(func)) - func() - return true - end, - hold=function(task) -- as many conditions and times that you want can be used - local action=__CAction__ - action:Pause() - if type(task)=="number" then - local alarm=event.newAlarm(task,function() end,true) - while alarm.active==true do - if love then - event.lManager() - else - event.cManager() - end - end - alarm:Remove() - action:Resume() - elseif type(task)=="function" then - local env=event.newEvent(task,function(envt) envt:Pause() envt:Stop() end) - while env.active do - if love then - event.lManager() - else - event.cManager() - end - end - env:Remove() - action:Resume() - else - print("Error Data Type!!!") - end - end, - waitFor=function(obj) - local obj=obj - event.hold(function() return not(obj.active) end) - end, - getType=function(obj) - if obj.Type~=nil then - return obj.Type - end - end, - newEvent=function(test,task) - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Events, - Type="Event", - active=true, - test=test or (function() end), - task={task} or {}, - Act=function(self) - _G.__CAction__=self - if self:test(self)==true then - self:Pause() - self:Stop() - for i=1,#self.task do - self.task[i](self) - end - end - end, - Reset=function(self) self:Resume() end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - OnEvent=function(self,func) - table.insert(self.task,func) - end, - FreeConnections=function(self) - self.task={} - end, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - newAlarm=function(set,func,start) - if not(start) then - timer=0 - active=false - else - timer=os.clock() - active=true - end - if not(func) then - func=(function() end) - end - Alarm= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Alarms, - Type="Alarm", - active=active, - timer=timer, - set=set or 0, - func={func}, - Act=function(self) - _G.__CAction__=self - if self.active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - self:Stop() - self:Ring() - end - end - end, - FreeConnections=function(self) - self.func={} - end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Set=function(self,amt) - self.set=amt - self.timer=os.clock() - self:Resume() - end, - OnRing=function(self,func) - table.insert(self.func,func) - end, - Ring=function(self) - for i=1,#self.func do - self.func[i](self) - end - end, - Reset=function(self) - self.timer=os.clock() - self:Resume() - end, - Resume=EventRef.Resume, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - } - table.insert(event.Mainloop,Alarm) - event.last=temp - return Alarm - end, - newTask=function(func) - table.insert(event.DoTasks,func) - end, - createLoop=function() - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Loops, - Type="Loop", - active=true, - func={}, - OnLoop=function(self,func) - table.insert(self.func,func) - end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - Act=function(self) - _G.__CAction__=self - for i=1,#self.func do - self.func[i](os.clock()-event.Start,self) - end - end, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - FreeConnections=function(self) - self.func={} - end, - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - createStep=function(start,reset,count,skip) - think=1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - if not(endc) then - endc=false - end - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - start=start or 1, - Parent=event.Steps, - Type="Step", - pos=start or 1, - endAt=reset or math.huge, - active=true, - skip=skip or 0, - spos=0, - count=count or 1*think, - funcs={}, - steps={}, - Act=StepRef.Step, - FAct=StepRef.FStep, - Remove=StepRef.Remove, - Reset=StepRef.Reset, - Set=StepRef.Set, - Pause=StepRef.Pause, - Resume=StepRef.Resume, - Stop=StepRef.Stop, - End=StepRef.End, - Update=StepRef.Update, - OnEnd=StepRef.OnEnd, - OnStep=StepRef.OnStep, - FreeConnections=StepRef.FreeConnections - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - createTStep=function(start,reset,timer,count) - think=1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - if not(endc) then - endc=false - end - timer=timer or 1 - local _alarm=event.newAlarm(timer,function(alarm) alarm.Link:Act() alarm:Reset() end,true) - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - start=start or 1, - Parent=event.TSteps, - Type="TStep", - pos=start or 1, - endAt=reset or math.huge, - active=true, - skip= 0, - spos=0, - count=count or 1*think, - funcs={}, - steps={}, - alarm=_alarm, - Act=StepRef.Step, - FAct=StepRef.FStep, - Remove=function(self) - if self~=_G.__CAction__ then - for as=1,#event.Mainloop do - if tostring(event.Mainloop[as])==tostring(self) then - table.remove(event.Mainloop,as) - end - end - self.alarm:Remove() - else - table.insert(event.garbage,self) - end - end, - Reset=StepRef.Reset, - Set=StepRef.Set, - Pause=StepRef.Pause, - Resume=StepRef.Resume, - Stop=StepRef.Stop, - End=StepRef.End, - Update=function(self,start,reset,timer,count) - if start~=nil and reset~=nil then - if start>reset then - if not(count<0) then - print("less") - count=-count - end - end - end - self.start=start or self.start - self.endAt=reset or self.endAt - if timer~=nil then - self.alarm:Set(timer) - end - self.count=count or self.count - self.pos=self.start - self:Resume() - end, - OnEnd=StepRef.OnEnd, - OnStep=StepRef.OnStep, - FreeConnections=StepRef.FreeConnections - } - _alarm.Link=temp - event.last=temp - return temp - end, - createTrigger=function(func) - temp={ - active=true, - trigfunc=func, - Remove=function(self) - for i=1,#event.Triggers do - if event.Triggers[i]==self then - table.remove(event.Triggers,i) - end - end - end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Fire=function(self,...) - if self.active==true then - local tempA=__CAction__ - __CAction__=self - self:trigfunc(...) - __CAction__=tempA - end - end, - } - table.insert(event.Triggers,temp) - return temp - end, - stop=function() - event.Active=false - end, - onStart=function() end, - onUpdate=function(func) - local temp=event.createLoop() - temp:OnLoop(func) - temp.Priority=1 - end, - onDraw=function(func) - table.insert(event.drawF,func) - end, - onClose=function() end, - manager=function() - if not(love) then - event.onStart() - event.Start=os.clock() - while event.Active==true do - event.DO_Order() - end - event.onClose() - return os.clock()-event.Start - else - return false - end - end, - cManager=function() - if event.Active==true then - event.DO_Order() - end - end, - uManager=function(dt) - if event.CT==false then - if dt then - event.pump=true - end - event.CT=true - event.onStart() - event.Start=os.clock() - end - event.pumpvar=dt - if event.Active==true then - event.DO_Order() - end - end, - dManager=function() - for ii=1,#event.drawF do - event.drawF[ii]() - end - end, - lManager=function() - if love.event then - love.event.pump() - for e,a,b,c,d in love.event.poll() do - if e == "quit" then - if not love.quit or not love.quit() then - if love.audio then - love.audio.stop() - end - return - end - end - love.handlers[e](a,b,c,d) - end - end - if love.timer then - love.timer.step() - dt = love.timer.getDelta() - end - if love.update then love.update(dt) end - event.uManager(dt) - if love.window and love.graphics and love.window.isCreated() then - love.graphics.clear() - love.graphics.origin() - if love.draw then love.draw() end - event.dManager() - love.graphics.present() - end - end, - benchMark=function(sec,p) - p=p or event.Priority_Normal - local temp=event.createStep(10) - temp.CC=0 - temp:OnStep(function(pos,step) step.CC=step.CC+1 end) - local Loud=event.newAlarm(sec,nil,true) - Loud.Link=temp - Loud:OnRing(function(alarm) print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") end) - temp.Priority=p - Loud.Priority=p - return Loud - end, -} +function readonlytable(table) + return setmetatable({}, { + __index = table, + __newindex = function(table, key, value) + error("Attempt to modify read-only table") + end, + __metatable = false + }); +end +local EventRef= +readonlytable{ + Pause=function(self) + self.active=false + if not(event.isPaused(self)) then + table.insert(event.Paused,self) + for _j=1,#event.Mainloop do + if tostring(event.Mainloop[_j])==tostring(self) then + table.remove(event.Mainloop,_j) + end + end + end + end, + Resume=function(self) + self.active=true + if event.isPaused(self) then + table.insert(event.Mainloop,self) + for _j=1,#event.Paused do + if tostring(event.Paused[_j])==tostring(self) then + table.remove(event.Paused,_j) + end + end + end + end, + Stop=function(self) + self.active=nil + end, +} + +local StepRef= +readonlytable{ + Step=function(self) + if self~=nil then + if self.spos==0 then + _G.__CAction__=self + if self.active==true then + for i=1,#self.steps do + self.steps[i](self.pos,self) + end + self.pos=self.pos+self.count + end + end + if self.endAt+self.count<=self.pos and self.endAt>self.start then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + elseif self.pos<=0 then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end, + FStep=function(self) + if self~=nil then + if self.spos==0 then + _G.__CAction__=self + for i=1,#self.steps do + self.steps[i](self.pos,self) + end + self.pos=self.pos+self.count + end + if self.endAt+self.count<=self.pos and self.endAt>self.start then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + elseif self.pos==0 then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end, + Remove=function(self) + if self~=_G.__CAction__ then + for as=1,#event.Mainloop do + if tostring(event.Mainloop[as])==tostring(self) then + table.remove(event.Mainloop,as) + end + end + else + table.insert(event.garbage,self) + end + end, + Reset=function(self) + self.pos=self.start + end, + Set=function(self,amt) + self.pos=amt + end, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + Stop=function(self) + self:Reset() + self.active=nil + for i=1,#self.funcs do + self.funcs[i](self) + end + end, + End=function(self) + for i=1,#self.funcs do + self.funcs[i](self) + end + self:Reset() + end, + Update=function(self,start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end, + OnEnd=function(self,func) + table.insert(self.funcs,func) + end, + OnStep=function(self,func) + table.insert(self.steps,func) + end, + FreeConnections=function(self) + self.funcs={} + self.steps={} + end, +} +--thread and run setup +if love then + function love.run() + if love.math then + love.math.setRandomSeed(os.time()) + end + if love.event then + love.event.pump() + end + if love.load then love.load(arg) end + if love.timer then love.timer.step() end + local dt = 0 + while true do + -- Process events. + if love.event then + love.event.pump() + for e,a,b,c,d in love.event.poll() do + if e == "quit" then + if not love.quit or not love.quit() then + if love.audio then + love.audio.stop() + end + return + end + end + love.handlers[e](a,b,c,d) + end + end + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + if love.update then love.update(dt) end + event.uManager(dt) + if love.window and love.graphics and love.window.isCreated() then + love.graphics.clear() + love.graphics.origin() + if love.draw then love.draw() end + event.dManager() + love.graphics.setColor(255,255,255,255) + if event.draw then event.draw() end + love.graphics.present() + end + end + end +end +eThreads={ + send=function() end, + + + + +} +function RunTasks() + for i=1,#event.DoTasks do + event.DoTasks[i]() + end +end +event={ + VERSION="1.0.0 (Build Version: 5.7.3)", + Priority_Core=1, + Priority_High=2, + Priority_Above_Normal=4, + Priority_Normal=16, + Priority_Low=256, + Priority_Idle=65536, + Start=0, + Active=true, + CT=false, + Tasks={}, + DoTasks={}, + garbage={}, + Paused={}, + last={}, + func={}, + drawF={}, + pump=false, + pumpvar=0, + Mainloop={}, + PEnabled=true, + PCount=1, + Triggers={}, + oneTimeObj= + { + last={}, + Resume=function(self) end, + Pause=function(self) end, + }, + RemoveAll=function() + event.Mainloop={} + end, + GarbageObj= + { + Resume=function() end, + Pause=function() end + }, + isPaused=function(obj) + for _j=1,#event.Paused do + if tostring(event.Paused[_j])==tostring(obj) then + return true + end + end + return false + end, + DO_Order=function() + event.oneTime(RunTasks) + event.PCount=event.PCount+1 + for i=1,#event.Mainloop do + if event.Mainloop[i]~=nil then + local obj = event.Mainloop[i] + if event.PCount%obj.Priority==0 and event.PEnabled then + obj:Act() + elseif event.PEnabled==false then + obj:Act() + end + if event.PCount>event.Priority_Idle then + event.PCount=event.Priority_Core + end + end + end + event.MANAGE_GARBAGE() + end, + MANAGE_GARBAGE=function() + _G.__CAction__=event.GarbageObj + for _i=1,#event.garbage do + event.garbage[_i]:Remove() + table.remove(event.garbage,_i) + end + end, + oneTime=function(func) + event.oneTimeObj.last=_G.__CAction__ + _G.__CAction__=event.oneTimeObj + for _k=1,#event.Tasks do + if event.Tasks[_k]==func then + _G.__CAction__=event.oneTimeObj.last + return false + end + end + table.insert(event.Tasks,func) + func() + _G.__CAction__=event.oneTimeObj.last + return true + end, + oneETime=function(func) + for _k=1,#event.Tasks do + if event.Tasks[_k]==string.dump(func) then + return false + end + end + table.insert(event.Tasks,string.dump(func)) + func() + return true + end, + hold=function(task) -- as many conditions and times that you want can be used + local action=__CAction__ + action:Pause() + if type(task)=="number" then + local alarm=event.newAlarm(task,function() end,true) + while alarm.active==true do + if love then + event.lManager() + else + event.cManager() + end + end + alarm:Remove() + action:Resume() + elseif type(task)=="function" then + local env=event.newEvent(task,function(envt) envt:Pause() envt:Stop() end) + while env.active do + if love then + event.lManager() + else + event.cManager() + end + end + env:Remove() + action:Resume() + else + print("Error Data Type!!!") + end + end, + waitFor=function(obj) + local obj=obj + event.hold(function() return not(obj.active) end) + end, + getType=function(obj) + if obj.Type~=nil then + return obj.Type + end + end, + newEvent=function(test,task) + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Events, + Type="Event", + active=true, + test=test or (function() end), + task={task} or {}, + Act=function(self) + _G.__CAction__=self + if self:test(self)==true then + self:Pause() + self:Stop() + for i=1,#self.task do + self.task[i](self) + end + end + end, + Reset=function(self) self:Resume() end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + OnEvent=function(self,func) + table.insert(self.task,func) + end, + FreeConnections=function(self) + self.task={} + end, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + newAlarm=function(set,func,start) + if not(start) then + timer=0 + active=false + else + timer=os.clock() + active=true + end + if not(func) then + func=(function() end) + end + Alarm= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Alarms, + Type="Alarm", + active=active, + timer=timer, + set=set or 0, + func={func}, + Act=function(self) + _G.__CAction__=self + if self.active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + self:Stop() + self:Ring() + end + end + end, + FreeConnections=function(self) + self.func={} + end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Set=function(self,amt) + self.set=amt + self.timer=os.clock() + self:Resume() + end, + OnRing=function(self,func) + table.insert(self.func,func) + end, + Ring=function(self) + for i=1,#self.func do + self.func[i](self) + end + end, + Reset=function(self) + self.timer=os.clock() + self:Resume() + end, + Resume=EventRef.Resume, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + } + table.insert(event.Mainloop,Alarm) + event.last=temp + return Alarm + end, + newTask=function(func) + table.insert(event.DoTasks,func) + end, + createLoop=function() + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Loops, + Type="Loop", + active=true, + func={}, + OnLoop=function(self,func) + table.insert(self.func,func) + end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + Act=function(self) + _G.__CAction__=self + for i=1,#self.func do + self.func[i](os.clock()-event.Start,self) + end + end, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + FreeConnections=function(self) + self.func={} + end, + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + createStep=function(start,reset,count,skip) + think=1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + if not(endc) then + endc=false + end + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + start=start or 1, + Parent=event.Steps, + Type="Step", + pos=start or 1, + endAt=reset or math.huge, + active=true, + skip=skip or 0, + spos=0, + count=count or 1*think, + funcs={}, + steps={}, + Act=StepRef.Step, + FAct=StepRef.FStep, + Remove=StepRef.Remove, + Reset=StepRef.Reset, + Set=StepRef.Set, + Pause=StepRef.Pause, + Resume=StepRef.Resume, + Stop=StepRef.Stop, + End=StepRef.End, + Update=StepRef.Update, + OnEnd=StepRef.OnEnd, + OnStep=StepRef.OnStep, + FreeConnections=StepRef.FreeConnections + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + createTStep=function(start,reset,timer,count) + think=1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + if not(endc) then + endc=false + end + timer=timer or 1 + local _alarm=event.newAlarm(timer,function(alarm) alarm.Link:Act() alarm:Reset() end,true) + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + start=start or 1, + Parent=event.TSteps, + Type="TStep", + pos=start or 1, + endAt=reset or math.huge, + active=true, + skip= 0, + spos=0, + count=count or 1*think, + funcs={}, + steps={}, + alarm=_alarm, + Act=StepRef.Step, + FAct=StepRef.FStep, + Remove=function(self) + if self~=_G.__CAction__ then + for as=1,#event.Mainloop do + if tostring(event.Mainloop[as])==tostring(self) then + table.remove(event.Mainloop,as) + end + end + self.alarm:Remove() + else + table.insert(event.garbage,self) + end + end, + Reset=StepRef.Reset, + Set=StepRef.Set, + Pause=StepRef.Pause, + Resume=StepRef.Resume, + Stop=StepRef.Stop, + End=StepRef.End, + Update=function(self,start,reset,timer,count) + if start~=nil and reset~=nil then + if start>reset then + if not(count<0) then + print("less") + count=-count + end + end + end + self.start=start or self.start + self.endAt=reset or self.endAt + if timer~=nil then + self.alarm:Set(timer) + end + self.count=count or self.count + self.pos=self.start + self:Resume() + end, + OnEnd=StepRef.OnEnd, + OnStep=StepRef.OnStep, + FreeConnections=StepRef.FreeConnections + } + _alarm.Link=temp + event.last=temp + return temp + end, + createTrigger=function(func) + temp={ + active=true, + trigfunc=func, + Remove=function(self) + for i=1,#event.Triggers do + if event.Triggers[i]==self then + table.remove(event.Triggers,i) + end + end + end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Fire=function(self,...) + if self.active==true then + local tempA=__CAction__ + __CAction__=self + self:trigfunc(...) + __CAction__=tempA + end + end, + } + table.insert(event.Triggers,temp) + return temp + end, + stop=function() + event.Active=false + end, + onStart=function() end, + onUpdate=function(func) + local temp=event.createLoop() + temp:OnLoop(func) + temp.Priority=1 + end, + onDraw=function(func) + table.insert(event.drawF,func) + end, + onClose=function() end, + manager=function() + if not(love) then + event.onStart() + event.Start=os.clock() + while event.Active==true do + event.DO_Order() + end + event.onClose() + return os.clock()-event.Start + else + return false + end + end, + cManager=function() + if event.Active==true then + event.DO_Order() + end + end, + uManager=function(dt) + if event.CT==false then + if dt then + event.pump=true + end + event.CT=true + event.onStart() + event.Start=os.clock() + end + event.pumpvar=dt + if event.Active==true then + event.DO_Order() + end + end, + dManager=function() + for ii=1,#event.drawF do + event.drawF[ii]() + end + end, + lManager=function() + if love.event then + love.event.pump() + for e,a,b,c,d in love.event.poll() do + if e == "quit" then + if not love.quit or not love.quit() then + if love.audio then + love.audio.stop() + end + return + end + end + love.handlers[e](a,b,c,d) + end + end + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + if love.update then love.update(dt) end + event.uManager(dt) + if love.window and love.graphics and love.window.isCreated() then + love.graphics.clear() + love.graphics.origin() + if love.draw then love.draw() end + event.dManager() + love.graphics.present() + end + end, + benchMark=function(sec,p) + p=p or event.Priority_Normal + local temp=event.createStep(10) + temp.CC=0 + temp:OnStep(function(pos,step) step.CC=step.CC+1 end) + local Loud=event.newAlarm(sec,nil,true) + Loud.Link=temp + Loud:OnRing(function(alarm) print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") end) + temp.Priority=p + Loud.Priority=p + return Loud + end, +} diff --git a/oldversions/EventManager(5.7.3).lua b/oldversions/EventManager(5.7.3).lua index a51a84f..47e1bc8 100644 --- a/oldversions/EventManager(5.7.3).lua +++ b/oldversions/EventManager(5.7.3).lua @@ -1,728 +1,728 @@ -function readonlytable(table) - return setmetatable({}, { - __index = table, - __newindex = function(table, key, value) - error("Attempt to modify read-only table") - end, - __metatable = false - }); -end -local EventRef= -readonlytable{ - Pause=function(self) - self.active=false - if not(event.isPaused(self)) then - table.insert(event.Paused,self) - for _j=1,#event.Mainloop do - if tostring(event.Mainloop[_j])==tostring(self) then - table.remove(event.Mainloop,_j) - end - end - end - end, - Resume=function(self) - self.active=true - if event.isPaused(self) then - table.insert(event.Mainloop,self) - for _j=1,#event.Paused do - if tostring(event.Paused[_j])==tostring(self) then - table.remove(event.Paused,_j) - end - end - end - end, - Stop=function(self) - self.active=nil - end, -} - -local StepRef= -readonlytable{ - Step=function(self) - if self~=nil then - if self.spos==0 then - _G.__CAction__=self - if self.active==true then - for i=1,#self.steps do - self.steps[i](self.pos,self) - end - self.pos=self.pos+self.count - end - end - if self.endAt+self.count<=self.pos and self.endAt>self.start then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - elseif self.pos<=0 then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end, - FStep=function(self) - if self~=nil then - if self.spos==0 then - _G.__CAction__=self - for i=1,#self.steps do - self.steps[i](self.pos,self) - end - self.pos=self.pos+self.count - end - if self.endAt+self.count<=self.pos and self.endAt>self.start then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - elseif self.pos==0 then - self:Reset() - for i=1,#self.funcs do - self.funcs[i](self) - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end, - Remove=function(self) - if self~=_G.__CAction__ then - for as=1,#event.Mainloop do - if tostring(event.Mainloop[as])==tostring(self) then - table.remove(event.Mainloop,as) - end - end - else - table.insert(event.garbage,self) - end - end, - Reset=function(self) - self.pos=self.start - end, - Set=function(self,amt) - self.pos=amt - end, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - Stop=function(self) - self:Reset() - self.active=nil - for i=1,#self.funcs do - self.funcs[i](self) - end - end, - End=function(self) - for i=1,#self.funcs do - self.funcs[i](self) - end - self:Reset() - end, - Update=function(self,start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end, - OnEnd=function(self,func) - table.insert(self.funcs,func) - end, - OnStep=function(self,func) - table.insert(self.steps,func) - end, - FreeConnections=function(self) - self.funcs={} - self.steps={} - end, -} ---thread and run setup -if love then - function love.run() - if love.math then - love.math.setRandomSeed(os.time()) - end - if love.event then - love.event.pump() - end - if love.load then love.load(arg) end - if love.timer then love.timer.step() end - local dt = 0 - while true do - -- Process events. - if love.event then - love.event.pump() - for e,a,b,c,d in love.event.poll() do - if e == "quit" then - if not love.quit or not love.quit() then - if love.audio then - love.audio.stop() - end - return - end - end - love.handlers[e](a,b,c,d) - end - end - if love.timer then - love.timer.step() - dt = love.timer.getDelta() - end - if love.update then love.update(dt) end - event.uManager(dt) - if love.window and love.graphics and love.window.isCreated() then - love.graphics.clear() - love.graphics.origin() - if love.draw then love.draw() end - event.dManager() - love.graphics.setColor(255,255,255,255) - if event.draw then event.draw() end - love.graphics.present() - end - end - end -end -eThreads={ - send=function() end, - - - - -} -function RunTasks() - for i=1,#event.DoTasks do - event.DoTasks[i]() - end -end -event={ - VERSION="1.0.0 (Build Version: 5.7.3)", - Priority_Core=1, - Priority_High=2, - Priority_Above_Normal=4, - Priority_Normal=16, - Priority_Low=256, - Priority_Idle=65536, - Start=0, - Active=true, - CT=false, - Tasks={}, - DoTasks={}, - garbage={}, - Paused={}, - last={}, - func={}, - drawF={}, - pump=false, - pumpvar=0, - Mainloop={}, - PEnabled=true, - PCount=1, - Triggers={}, - oneTimeObj= - { - last={}, - Resume=function(self) end, - Pause=function(self) end, - }, - RemoveAll=function() - event.Mainloop={} - end, - GarbageObj= - { - Resume=function() end, - Pause=function() end - }, - isPaused=function(obj) - for _j=1,#event.Paused do - if tostring(event.Paused[_j])==tostring(obj) then - return true - end - end - return false - end, - DO_Order=function() - event.oneTime(RunTasks) - event.PCount=event.PCount+1 - for i=1,#event.Mainloop do - if event.Mainloop[i]~=nil then - local obj = event.Mainloop[i] - if event.PCount%obj.Priority==0 and event.PEnabled then - obj:Act() - elseif event.PEnabled==false then - obj:Act() - end - if event.PCount>event.Priority_Idle then - event.PCount=event.Priority_Core - end - end - end - event.MANAGE_GARBAGE() - end, - MANAGE_GARBAGE=function() - _G.__CAction__=event.GarbageObj - for _i=1,#event.garbage do - event.garbage[_i]:Remove() - table.remove(event.garbage,_i) - end - end, - oneTime=function(func) - event.oneTimeObj.last=_G.__CAction__ - _G.__CAction__=event.oneTimeObj - for _k=1,#event.Tasks do - if event.Tasks[_k]==func then - _G.__CAction__=event.oneTimeObj.last - return false - end - end - table.insert(event.Tasks,func) - func() - _G.__CAction__=event.oneTimeObj.last - return true - end, - oneETime=function(func) - for _k=1,#event.Tasks do - if event.Tasks[_k]==string.dump(func) then - return false - end - end - table.insert(event.Tasks,string.dump(func)) - func() - return true - end, - hold=function(task) -- as many conditions and times that you want can be used - local action=__CAction__ - action:Pause() - if type(task)=="number" then - local alarm=event.newAlarm(task,function() end,true) - while alarm.active==true do - if love then - event.lManager() - else - event.cManager() - end - end - alarm:Remove() - action:Resume() - elseif type(task)=="function" then - local env=event.newEvent(task,function(envt) envt:Pause() envt:Stop() end) - while env.active do - if love then - event.lManager() - else - event.cManager() - end - end - env:Remove() - action:Resume() - else - print("Error Data Type!!!") - end - end, - waitFor=function(obj) - local obj=obj - event.hold(function() return not(obj.active) end) - end, - getType=function(obj) - if obj.Type~=nil then - return obj.Type - end - end, - newEvent=function(test,task) - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Events, - Type="Event", - active=true, - test=test or (function() end), - task={task} or {}, - Act=function(self) - _G.__CAction__=self - if self:test(self)==true then - self:Pause() - self:Stop() - for i=1,#self.task do - self.task[i](self) - end - end - end, - Reset=function(self) self:Resume() end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - OnEvent=function(self,func) - table.insert(self.task,func) - end, - FreeConnections=function(self) - self.task={} - end, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - newAlarm=function(set,func,start) - if not(start) then - timer=0 - active=false - else - timer=os.clock() - active=true - end - if not(func) then - func=(function() end) - end - Alarm= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Alarms, - Type="Alarm", - active=active, - timer=timer, - set=set or 0, - func={func}, - Act=function(self) - _G.__CAction__=self - if self.active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - self:Stop() - self:Ring() - end - end - end, - FreeConnections=function(self) - self.func={} - end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Set=function(self,amt) - self.set=amt - self.timer=os.clock() - self:Resume() - end, - OnRing=function(self,func) - table.insert(self.func,func) - end, - Ring=function(self) - for i=1,#self.func do - self.func[i](self) - end - end, - Reset=function(self) - self.timer=os.clock() - self:Resume() - end, - Resume=EventRef.Resume, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - } - table.insert(event.Mainloop,Alarm) - event.last=temp - return Alarm - end, - newTask=function(func) - table.insert(event.DoTasks,func) - end, - createLoop=function() - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - Parent=event.Loops, - Type="Loop", - active=true, - func={}, - OnLoop=function(self,func) - table.insert(self.func,func) - end, - Stop=EventRef.Stop, - Pause=EventRef.Pause, - Resume=EventRef.Resume, - Act=function(self) - _G.__CAction__=self - for i=1,#self.func do - self.func[i](os.clock()-event.Start,self) - end - end, - Remove=function(self) - if self~=_G.__CAction__ then - for i=1,#event.Mainloop do - if tostring(event.Mainloop[i])==tostring(self) then - table.remove(event.Mainloop,i) - end - end - else - table.insert(event.garbage,self) - end - end, - FreeConnections=function(self) - self.func={} - end, - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - createStep=function(start,reset,count,skip) - think=1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - if not(endc) then - endc=false - end - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - start=start or 1, - Parent=event.Steps, - Type="Step", - pos=start or 1, - endAt=reset or math.huge, - active=true, - skip=skip or 0, - spos=0, - count=count or 1*think, - funcs={}, - steps={}, - Act=StepRef.Step, - FAct=StepRef.FStep, - Remove=StepRef.Remove, - Reset=StepRef.Reset, - Set=StepRef.Set, - Pause=StepRef.Pause, - Resume=StepRef.Resume, - Stop=StepRef.Stop, - End=StepRef.End, - Update=StepRef.Update, - OnEnd=StepRef.OnEnd, - OnStep=StepRef.OnStep, - FreeConnections=StepRef.FreeConnections - } - table.insert(event.Mainloop,temp) - event.last=temp - return temp - end, - createTStep=function(start,reset,timer,count) - think=1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - if not(endc) then - endc=false - end - timer=timer or 1 - local _alarm=event.newAlarm(timer,function(alarm) alarm.Link:Act() alarm:Reset() end,true) - temp= - { - Priority=event.Priority_Normal, - _Priority=0, - start=start or 1, - Parent=event.TSteps, - Type="TStep", - pos=start or 1, - endAt=reset or math.huge, - active=true, - skip= 0, - spos=0, - count=count or 1*think, - funcs={}, - steps={}, - alarm=_alarm, - Act=StepRef.Step, - FAct=StepRef.FStep, - Remove=function(self) - if self~=_G.__CAction__ then - for as=1,#event.Mainloop do - if tostring(event.Mainloop[as])==tostring(self) then - table.remove(event.Mainloop,as) - end - end - self.alarm:Remove() - else - table.insert(event.garbage,self) - end - end, - Reset=StepRef.Reset, - Set=StepRef.Set, - Pause=StepRef.Pause, - Resume=StepRef.Resume, - Stop=StepRef.Stop, - End=StepRef.End, - Update=function(self,start,reset,timer,count) - if start~=nil and reset~=nil then - if start>reset then - if not(count<0) then - print("less") - count=-count - end - end - end - self.start=start or self.start - self.endAt=reset or self.endAt - if timer~=nil then - self.alarm:Set(timer) - end - self.count=count or self.count - self.pos=self.start - self:Resume() - end, - OnEnd=StepRef.OnEnd, - OnStep=StepRef.OnStep, - FreeConnections=StepRef.FreeConnections - } - _alarm.Link=temp - event.last=temp - return temp - end, - createTrigger=function(func) - temp={ - active=true, - trigfunc=func, - Remove=function(self) - for i=1,#event.Triggers do - if event.Triggers[i]==self then - table.remove(event.Triggers,i) - end - end - end, - Pause=function(self) self.active=false end, - Resume=function(self) self.active=true end, - Fire=function(self,...) - if self.active==true then - local tempA=__CAction__ - __CAction__=self - self:trigfunc(...) - __CAction__=tempA - end - end, - } - table.insert(event.Triggers,temp) - return temp - end, - stop=function() - event.Active=false - end, - onStart=function() end, - onUpdate=function(func) - local temp=event.createLoop() - temp:OnLoop(func) - temp.Priority=1 - end, - onDraw=function(func) - table.insert(event.drawF,func) - end, - onClose=function() end, - manager=function() - if not(love) then - event.onStart() - event.Start=os.clock() - while event.Active==true do - event.DO_Order() - end - event.onClose() - return os.clock()-event.Start - else - return false - end - end, - cManager=function() - if event.Active==true then - event.DO_Order() - end - end, - uManager=function(dt) - if event.CT==false then - if dt then - event.pump=true - end - event.CT=true - event.onStart() - event.Start=os.clock() - end - event.pumpvar=dt - if event.Active==true then - event.DO_Order() - end - end, - dManager=function() - for ii=1,#event.drawF do - event.drawF[ii]() - end - end, - lManager=function() - if love.event then - love.event.pump() - for e,a,b,c,d in love.event.poll() do - if e == "quit" then - if not love.quit or not love.quit() then - if love.audio then - love.audio.stop() - end - return - end - end - love.handlers[e](a,b,c,d) - end - end - if love.timer then - love.timer.step() - dt = love.timer.getDelta() - end - if love.update then love.update(dt) end - event.uManager(dt) - if love.window and love.graphics and love.window.isCreated() then - love.graphics.clear() - love.graphics.origin() - if love.draw then love.draw() end - event.dManager() - love.graphics.present() - end - end, - benchMark=function(sec,p) - p=p or event.Priority_Normal - local temp=event.createStep(10) - temp.CC=0 - temp:OnStep(function(pos,step) step.CC=step.CC+1 end) - local Loud=event.newAlarm(sec,nil,true) - Loud.Link=temp - Loud:OnRing(function(alarm) print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") end) - temp.Priority=p - Loud.Priority=p - return Loud - end, -} +function readonlytable(table) + return setmetatable({}, { + __index = table, + __newindex = function(table, key, value) + error("Attempt to modify read-only table") + end, + __metatable = false + }); +end +local EventRef= +readonlytable{ + Pause=function(self) + self.active=false + if not(event.isPaused(self)) then + table.insert(event.Paused,self) + for _j=1,#event.Mainloop do + if tostring(event.Mainloop[_j])==tostring(self) then + table.remove(event.Mainloop,_j) + end + end + end + end, + Resume=function(self) + self.active=true + if event.isPaused(self) then + table.insert(event.Mainloop,self) + for _j=1,#event.Paused do + if tostring(event.Paused[_j])==tostring(self) then + table.remove(event.Paused,_j) + end + end + end + end, + Stop=function(self) + self.active=nil + end, +} + +local StepRef= +readonlytable{ + Step=function(self) + if self~=nil then + if self.spos==0 then + _G.__CAction__=self + if self.active==true then + for i=1,#self.steps do + self.steps[i](self.pos,self) + end + self.pos=self.pos+self.count + end + end + if self.endAt+self.count<=self.pos and self.endAt>self.start then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + elseif self.pos<=0 then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end, + FStep=function(self) + if self~=nil then + if self.spos==0 then + _G.__CAction__=self + for i=1,#self.steps do + self.steps[i](self.pos,self) + end + self.pos=self.pos+self.count + end + if self.endAt+self.count<=self.pos and self.endAt>self.start then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + elseif self.pos==0 then + self:Reset() + for i=1,#self.funcs do + self.funcs[i](self) + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end, + Remove=function(self) + if self~=_G.__CAction__ then + for as=1,#event.Mainloop do + if tostring(event.Mainloop[as])==tostring(self) then + table.remove(event.Mainloop,as) + end + end + else + table.insert(event.garbage,self) + end + end, + Reset=function(self) + self.pos=self.start + end, + Set=function(self,amt) + self.pos=amt + end, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + Stop=function(self) + self:Reset() + self.active=nil + for i=1,#self.funcs do + self.funcs[i](self) + end + end, + End=function(self) + for i=1,#self.funcs do + self.funcs[i](self) + end + self:Reset() + end, + Update=function(self,start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end, + OnEnd=function(self,func) + table.insert(self.funcs,func) + end, + OnStep=function(self,func) + table.insert(self.steps,func) + end, + FreeConnections=function(self) + self.funcs={} + self.steps={} + end, +} +--thread and run setup +if love then + function love.run() + if love.math then + love.math.setRandomSeed(os.time()) + end + if love.event then + love.event.pump() + end + if love.load then love.load(arg) end + if love.timer then love.timer.step() end + local dt = 0 + while true do + -- Process events. + if love.event then + love.event.pump() + for e,a,b,c,d in love.event.poll() do + if e == "quit" then + if not love.quit or not love.quit() then + if love.audio then + love.audio.stop() + end + return + end + end + love.handlers[e](a,b,c,d) + end + end + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + if love.update then love.update(dt) end + event.uManager(dt) + if love.window and love.graphics and love.window.isCreated() then + love.graphics.clear() + love.graphics.origin() + if love.draw then love.draw() end + event.dManager() + love.graphics.setColor(255,255,255,255) + if event.draw then event.draw() end + love.graphics.present() + end + end + end +end +eThreads={ + send=function() end, + + + + +} +function RunTasks() + for i=1,#event.DoTasks do + event.DoTasks[i]() + end +end +event={ + VERSION="1.0.0 (Build Version: 5.7.3)", + Priority_Core=1, + Priority_High=2, + Priority_Above_Normal=4, + Priority_Normal=16, + Priority_Low=256, + Priority_Idle=65536, + Start=0, + Active=true, + CT=false, + Tasks={}, + DoTasks={}, + garbage={}, + Paused={}, + last={}, + func={}, + drawF={}, + pump=false, + pumpvar=0, + Mainloop={}, + PEnabled=true, + PCount=1, + Triggers={}, + oneTimeObj= + { + last={}, + Resume=function(self) end, + Pause=function(self) end, + }, + RemoveAll=function() + event.Mainloop={} + end, + GarbageObj= + { + Resume=function() end, + Pause=function() end + }, + isPaused=function(obj) + for _j=1,#event.Paused do + if tostring(event.Paused[_j])==tostring(obj) then + return true + end + end + return false + end, + DO_Order=function() + event.oneTime(RunTasks) + event.PCount=event.PCount+1 + for i=1,#event.Mainloop do + if event.Mainloop[i]~=nil then + local obj = event.Mainloop[i] + if event.PCount%obj.Priority==0 and event.PEnabled then + obj:Act() + elseif event.PEnabled==false then + obj:Act() + end + if event.PCount>event.Priority_Idle then + event.PCount=event.Priority_Core + end + end + end + event.MANAGE_GARBAGE() + end, + MANAGE_GARBAGE=function() + _G.__CAction__=event.GarbageObj + for _i=1,#event.garbage do + event.garbage[_i]:Remove() + table.remove(event.garbage,_i) + end + end, + oneTime=function(func) + event.oneTimeObj.last=_G.__CAction__ + _G.__CAction__=event.oneTimeObj + for _k=1,#event.Tasks do + if event.Tasks[_k]==func then + _G.__CAction__=event.oneTimeObj.last + return false + end + end + table.insert(event.Tasks,func) + func() + _G.__CAction__=event.oneTimeObj.last + return true + end, + oneETime=function(func) + for _k=1,#event.Tasks do + if event.Tasks[_k]==string.dump(func) then + return false + end + end + table.insert(event.Tasks,string.dump(func)) + func() + return true + end, + hold=function(task) -- as many conditions and times that you want can be used + local action=__CAction__ + action:Pause() + if type(task)=="number" then + local alarm=event.newAlarm(task,function() end,true) + while alarm.active==true do + if love then + event.lManager() + else + event.cManager() + end + end + alarm:Remove() + action:Resume() + elseif type(task)=="function" then + local env=event.newEvent(task,function(envt) envt:Pause() envt:Stop() end) + while env.active do + if love then + event.lManager() + else + event.cManager() + end + end + env:Remove() + action:Resume() + else + print("Error Data Type!!!") + end + end, + waitFor=function(obj) + local obj=obj + event.hold(function() return not(obj.active) end) + end, + getType=function(obj) + if obj.Type~=nil then + return obj.Type + end + end, + newEvent=function(test,task) + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Events, + Type="Event", + active=true, + test=test or (function() end), + task={task} or {}, + Act=function(self) + _G.__CAction__=self + if self:test(self)==true then + self:Pause() + self:Stop() + for i=1,#self.task do + self.task[i](self) + end + end + end, + Reset=function(self) self:Resume() end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + OnEvent=function(self,func) + table.insert(self.task,func) + end, + FreeConnections=function(self) + self.task={} + end, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + newAlarm=function(set,func,start) + if not(start) then + timer=0 + active=false + else + timer=os.clock() + active=true + end + if not(func) then + func=(function() end) + end + Alarm= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Alarms, + Type="Alarm", + active=active, + timer=timer, + set=set or 0, + func={func}, + Act=function(self) + _G.__CAction__=self + if self.active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + self:Stop() + self:Ring() + end + end + end, + FreeConnections=function(self) + self.func={} + end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Set=function(self,amt) + self.set=amt + self.timer=os.clock() + self:Resume() + end, + OnRing=function(self,func) + table.insert(self.func,func) + end, + Ring=function(self) + for i=1,#self.func do + self.func[i](self) + end + end, + Reset=function(self) + self.timer=os.clock() + self:Resume() + end, + Resume=EventRef.Resume, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + } + table.insert(event.Mainloop,Alarm) + event.last=temp + return Alarm + end, + newTask=function(func) + table.insert(event.DoTasks,func) + end, + createLoop=function() + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + Parent=event.Loops, + Type="Loop", + active=true, + func={}, + OnLoop=function(self,func) + table.insert(self.func,func) + end, + Stop=EventRef.Stop, + Pause=EventRef.Pause, + Resume=EventRef.Resume, + Act=function(self) + _G.__CAction__=self + for i=1,#self.func do + self.func[i](os.clock()-event.Start,self) + end + end, + Remove=function(self) + if self~=_G.__CAction__ then + for i=1,#event.Mainloop do + if tostring(event.Mainloop[i])==tostring(self) then + table.remove(event.Mainloop,i) + end + end + else + table.insert(event.garbage,self) + end + end, + FreeConnections=function(self) + self.func={} + end, + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + createStep=function(start,reset,count,skip) + think=1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + if not(endc) then + endc=false + end + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + start=start or 1, + Parent=event.Steps, + Type="Step", + pos=start or 1, + endAt=reset or math.huge, + active=true, + skip=skip or 0, + spos=0, + count=count or 1*think, + funcs={}, + steps={}, + Act=StepRef.Step, + FAct=StepRef.FStep, + Remove=StepRef.Remove, + Reset=StepRef.Reset, + Set=StepRef.Set, + Pause=StepRef.Pause, + Resume=StepRef.Resume, + Stop=StepRef.Stop, + End=StepRef.End, + Update=StepRef.Update, + OnEnd=StepRef.OnEnd, + OnStep=StepRef.OnStep, + FreeConnections=StepRef.FreeConnections + } + table.insert(event.Mainloop,temp) + event.last=temp + return temp + end, + createTStep=function(start,reset,timer,count) + think=1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + if not(endc) then + endc=false + end + timer=timer or 1 + local _alarm=event.newAlarm(timer,function(alarm) alarm.Link:Act() alarm:Reset() end,true) + temp= + { + Priority=event.Priority_Normal, + _Priority=0, + start=start or 1, + Parent=event.TSteps, + Type="TStep", + pos=start or 1, + endAt=reset or math.huge, + active=true, + skip= 0, + spos=0, + count=count or 1*think, + funcs={}, + steps={}, + alarm=_alarm, + Act=StepRef.Step, + FAct=StepRef.FStep, + Remove=function(self) + if self~=_G.__CAction__ then + for as=1,#event.Mainloop do + if tostring(event.Mainloop[as])==tostring(self) then + table.remove(event.Mainloop,as) + end + end + self.alarm:Remove() + else + table.insert(event.garbage,self) + end + end, + Reset=StepRef.Reset, + Set=StepRef.Set, + Pause=StepRef.Pause, + Resume=StepRef.Resume, + Stop=StepRef.Stop, + End=StepRef.End, + Update=function(self,start,reset,timer,count) + if start~=nil and reset~=nil then + if start>reset then + if not(count<0) then + print("less") + count=-count + end + end + end + self.start=start or self.start + self.endAt=reset or self.endAt + if timer~=nil then + self.alarm:Set(timer) + end + self.count=count or self.count + self.pos=self.start + self:Resume() + end, + OnEnd=StepRef.OnEnd, + OnStep=StepRef.OnStep, + FreeConnections=StepRef.FreeConnections + } + _alarm.Link=temp + event.last=temp + return temp + end, + createTrigger=function(func) + temp={ + active=true, + trigfunc=func, + Remove=function(self) + for i=1,#event.Triggers do + if event.Triggers[i]==self then + table.remove(event.Triggers,i) + end + end + end, + Pause=function(self) self.active=false end, + Resume=function(self) self.active=true end, + Fire=function(self,...) + if self.active==true then + local tempA=__CAction__ + __CAction__=self + self:trigfunc(...) + __CAction__=tempA + end + end, + } + table.insert(event.Triggers,temp) + return temp + end, + stop=function() + event.Active=false + end, + onStart=function() end, + onUpdate=function(func) + local temp=event.createLoop() + temp:OnLoop(func) + temp.Priority=1 + end, + onDraw=function(func) + table.insert(event.drawF,func) + end, + onClose=function() end, + manager=function() + if not(love) then + event.onStart() + event.Start=os.clock() + while event.Active==true do + event.DO_Order() + end + event.onClose() + return os.clock()-event.Start + else + return false + end + end, + cManager=function() + if event.Active==true then + event.DO_Order() + end + end, + uManager=function(dt) + if event.CT==false then + if dt then + event.pump=true + end + event.CT=true + event.onStart() + event.Start=os.clock() + end + event.pumpvar=dt + if event.Active==true then + event.DO_Order() + end + end, + dManager=function() + for ii=1,#event.drawF do + event.drawF[ii]() + end + end, + lManager=function() + if love.event then + love.event.pump() + for e,a,b,c,d in love.event.poll() do + if e == "quit" then + if not love.quit or not love.quit() then + if love.audio then + love.audio.stop() + end + return + end + end + love.handlers[e](a,b,c,d) + end + end + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + if love.update then love.update(dt) end + event.uManager(dt) + if love.window and love.graphics and love.window.isCreated() then + love.graphics.clear() + love.graphics.origin() + if love.draw then love.draw() end + event.dManager() + love.graphics.present() + end + end, + benchMark=function(sec,p) + p=p or event.Priority_Normal + local temp=event.createStep(10) + temp.CC=0 + temp:OnStep(function(pos,step) step.CC=step.CC+1 end) + local Loud=event.newAlarm(sec,nil,true) + Loud.Link=temp + Loud:OnRing(function(alarm) print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") end) + temp.Priority=p + Loud.Priority=p + return Loud + end, +} diff --git a/oldversions/MultiManager(0.3.0).lua b/oldversions/MultiManager(0.3.0).lua index 8cd58e3..d5f999d 100644 --- a/oldversions/MultiManager(0.3.0).lua +++ b/oldversions/MultiManager(0.3.0).lua @@ -1,365 +1,365 @@ -multi = {} -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.MasterId=0 -multi.Active=true -multi.Id=-1 --- System -function multi:newBase(ins) - local c = {} - setmetatable(c, multi) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Act=function() end - if ins then - table.insert(multi.Mainloop,ins,c) - else - table.insert(multi.Mainloop,c) - end - multi.MasterId=multi.MasterId+1 - return c -end -function multi:reboot(r) - multi.Mainloop={} - multi.Tasks={} - multi.Tasks2={} - multi.Garbage={} - multi.Children={} - multi.Paused={} - multi.MasterId=0 - multi.Active=true - multi.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end -end ---Processor -function multi.Do_Order() - for _D=#multi.Mainloop,1,-1 do - if multi.Mainloop[_D]~=nil then - multi.Mainloop[_D].Id=_D - multi.Mainloop[_D]:Act() - end - end -end -function multi:benchMark(sec) - local temp=multi:newStep(2) - temp.CC=0 - temp:OnStep(function(pos,step) step.CC=step.CC+1 end) - local Loud=multi:newAlarm(sec) - Loud.Link=temp - Loud:OnRing(function(alarm) alarm.Link.CC=alarm.Link.CC print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") alarm.bench=alarm.Link.CC alarm.Link:Destroy() alarm:Destroy() end) - return Loud -end ---Helpers -function multi:FreeMainEvent() - self.func={} -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if not(n) then - self.Active=false - table.remove(multi.Mainloop,self.Id) - table.insert(multi.Paused,self) - else - self:hold(n) - end -end -function multi:Resume() - if self:isPaused() then - self.Active=true - table.remove(multi.Paused,self.Id) - table.insert(multi.Mainloop,self) - end -end -function multi:Remove() - self:Pause() - self:Destroy() -end -function multi:Destroy() - self:Pause() - if self:isPaused() then - for i=1,#multi.Paused do - if multi.Paused[i]==self then - table.remove(multi.Paused,i) - return - end - end - else - table.remove(multi.Mainloop,self.Id) - end - self.Act=function() end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=multi:newAlarm(task) - while alarm.Active==true do - if love then - multi.lManager() - else - multi.Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=multi:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - multi.lManager() - else - multi.Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - for _k=1,#multi.Tasks2 do - if multi.Tasks2[_k]==func then - return false - end - end - table.insert(multi.Tasks2,func) - func(...) - return true -end ---Constructors -function multi:newEvent(task) - local c=multi:newBase() - c.Type="Event" - c.Task=task or function() end - function c:Act() - if self.Task(self) and self.Active==true then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - return c -end -function multi:newAlarm(set) - local c=multi:newBase() - c.Type="Alarm" - c.timer=os.clock() - c.set=set or 0 - function c:Act() - if self.Active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - for i=1,#self.func do - self.func[i](self) - end - end - end - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - end - function c:OnRing(func) - table.insert(self.func,func) - end - return c -end -function multi:newTask(func) - table.insert(multi.Tasks,func) -end -function multi:newLoop() - local c=multi:newBase() - c.Type="Loop" - function c:Act() - if self.Active==true then - for i=1,#self.func do - self.func[i](os.clock()-multi.Start,self) - end - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - return c -end -function multi:newStep(start,reset,count,skip) - local c=multi:newBase() - think=1 - c.Type="Step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.Active==true then - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - c:OnStep(function(p,s) - if s.count>0 and s.endAt==p then - for fe=1,#s.funcE do - s.funcE[fe](s) - end - s.pos=s.start-1 - elseif s.count<0 and s.endAt==p then - for fe=1,#s.funcE do - s.funcE[fe](s) - end - s.pos=s.start-1 - end - end) - return c -end -function multi:newTStep(start,reset,count,set) - local c=multi:newBase() - think=1 - c.Type="TStep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=os.clock() - c.set=set or 1 - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=os.clock() - self:Resume() - end - function c:Act() - if self.Active then - if os.clock()-self.timer>=self.set then - self:Reset() - for i=1,#self.func do - self.func[i](self.pos,self) - end - if self.endAt==self.pos then - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start-1 - end - self.pos=self.pos+self.count - end - end - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - end - function c:OnStep(func) - table.insert(self.func,func) - end - return c -end - -function multi:inQueue(func) - if self.Id==-1 then - print("Error: Can't queue the multi object") - return - end - local c=multi:newBase(self.Id) - self.Id=self.Id-1 - c.Type="Queue" - c.Task=func - c.Link=self - function c:Act() - self.Task(self.Link) - self:Destroy() - end -end -function multi:newTrigger(func) - local c=multi:newBase() - c.Type="Trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - return c -end ---Managers -function multi:mainloop() - for i=1,#multi.Tasks do - multi.Tasks[i]() - end - multi.Start=os.clock() - while self.Active do - multi.Do_Order() - end -end -function multi._tFunc(dt) - if dt then - multi.pump=true - end - multi.pumpvar=dt - multi.Start=os.clock() -end -function multi:uManager(dt) - multi:oneTime(multi._tFunc,dt) - multi.Do_Order() -end +multi = {} +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.MasterId=0 +multi.Active=true +multi.Id=-1 +-- System +function multi:newBase(ins) + local c = {} + setmetatable(c, multi) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Act=function() end + if ins then + table.insert(multi.Mainloop,ins,c) + else + table.insert(multi.Mainloop,c) + end + multi.MasterId=multi.MasterId+1 + return c +end +function multi:reboot(r) + multi.Mainloop={} + multi.Tasks={} + multi.Tasks2={} + multi.Garbage={} + multi.Children={} + multi.Paused={} + multi.MasterId=0 + multi.Active=true + multi.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end +end +--Processor +function multi.Do_Order() + for _D=#multi.Mainloop,1,-1 do + if multi.Mainloop[_D]~=nil then + multi.Mainloop[_D].Id=_D + multi.Mainloop[_D]:Act() + end + end +end +function multi:benchMark(sec) + local temp=multi:newStep(2) + temp.CC=0 + temp:OnStep(function(pos,step) step.CC=step.CC+1 end) + local Loud=multi:newAlarm(sec) + Loud.Link=temp + Loud:OnRing(function(alarm) alarm.Link.CC=alarm.Link.CC print((alarm.Link.CC).." steps in "..alarm.set.." second(s)") alarm.bench=alarm.Link.CC alarm.Link:Destroy() alarm:Destroy() end) + return Loud +end +--Helpers +function multi:FreeMainEvent() + self.func={} +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if not(n) then + self.Active=false + table.remove(multi.Mainloop,self.Id) + table.insert(multi.Paused,self) + else + self:hold(n) + end +end +function multi:Resume() + if self:isPaused() then + self.Active=true + table.remove(multi.Paused,self.Id) + table.insert(multi.Mainloop,self) + end +end +function multi:Remove() + self:Pause() + self:Destroy() +end +function multi:Destroy() + self:Pause() + if self:isPaused() then + for i=1,#multi.Paused do + if multi.Paused[i]==self then + table.remove(multi.Paused,i) + return + end + end + else + table.remove(multi.Mainloop,self.Id) + end + self.Act=function() end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=multi:newAlarm(task) + while alarm.Active==true do + if love then + multi.lManager() + else + multi.Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=multi:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + multi.lManager() + else + multi.Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + for _k=1,#multi.Tasks2 do + if multi.Tasks2[_k]==func then + return false + end + end + table.insert(multi.Tasks2,func) + func(...) + return true +end +--Constructors +function multi:newEvent(task) + local c=multi:newBase() + c.Type="Event" + c.Task=task or function() end + function c:Act() + if self.Task(self) and self.Active==true then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + return c +end +function multi:newAlarm(set) + local c=multi:newBase() + c.Type="Alarm" + c.timer=os.clock() + c.set=set or 0 + function c:Act() + if self.Active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + for i=1,#self.func do + self.func[i](self) + end + end + end + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + end + function c:OnRing(func) + table.insert(self.func,func) + end + return c +end +function multi:newTask(func) + table.insert(multi.Tasks,func) +end +function multi:newLoop() + local c=multi:newBase() + c.Type="Loop" + function c:Act() + if self.Active==true then + for i=1,#self.func do + self.func[i](os.clock()-multi.Start,self) + end + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + return c +end +function multi:newStep(start,reset,count,skip) + local c=multi:newBase() + think=1 + c.Type="Step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.Active==true then + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + c:OnStep(function(p,s) + if s.count>0 and s.endAt==p then + for fe=1,#s.funcE do + s.funcE[fe](s) + end + s.pos=s.start-1 + elseif s.count<0 and s.endAt==p then + for fe=1,#s.funcE do + s.funcE[fe](s) + end + s.pos=s.start-1 + end + end) + return c +end +function multi:newTStep(start,reset,count,set) + local c=multi:newBase() + think=1 + c.Type="TStep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=os.clock() + c.set=set or 1 + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=os.clock() + self:Resume() + end + function c:Act() + if self.Active then + if os.clock()-self.timer>=self.set then + self:Reset() + for i=1,#self.func do + self.func[i](self.pos,self) + end + if self.endAt==self.pos then + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start-1 + end + self.pos=self.pos+self.count + end + end + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + end + function c:OnStep(func) + table.insert(self.func,func) + end + return c +end + +function multi:inQueue(func) + if self.Id==-1 then + print("Error: Can't queue the multi object") + return + end + local c=multi:newBase(self.Id) + self.Id=self.Id-1 + c.Type="Queue" + c.Task=func + c.Link=self + function c:Act() + self.Task(self.Link) + self:Destroy() + end +end +function multi:newTrigger(func) + local c=multi:newBase() + c.Type="Trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + return c +end +--Managers +function multi:mainloop() + for i=1,#multi.Tasks do + multi.Tasks[i]() + end + multi.Start=os.clock() + while self.Active do + multi.Do_Order() + end +end +function multi._tFunc(dt) + if dt then + multi.pump=true + end + multi.pumpvar=dt + multi.Start=os.clock() +end +function multi:uManager(dt) + multi:oneTime(multi._tFunc,dt) + multi.Do_Order() +end diff --git a/oldversions/MultiManager(0.4.0-1).lua b/oldversions/MultiManager(0.4.0-1).lua index 1e38f26..56d596a 100644 --- a/oldversions/MultiManager(0.4.0-1).lua +++ b/oldversions/MultiManager(0.4.0-1).lua @@ -1,506 +1,506 @@ -multi = {} -multi.Version="4.0.1" -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.MasterId=0 -multi.Active=true -multi.Id=-1 -multi.Type="mainint" -multi.Rest=0 -multi._type=type ---[[function type(v) - local t={} - if multi._type(v)=="table" then - t=getmetatable(v) - if v.Type~=nil then - if multi._type(v.Type)=="string" then - return v.Type - end - end - end - if t.__type~=nil then - return t.__type - else - return multi._type(v) - end -end]] --- System -function os.getOS() - if package.config:sub(1,1)=="\\" then - return "windows" - else - return "unix" - end -end -if os.getOS()=="windows" then - function os.sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end - end -else - function os.sleep(n) - os.execute("sleep " .. tonumber(n)) - end -end -function multi:newBase(ins) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - self.MasterId=self.MasterId+1 - return c -end -function multi:reboot(r) - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.MasterId=0 - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:Do_Order() - for _D=#self.Mainloop,1,-1 do - if self.Mainloop[_D]~=nil then - self.Mainloop[_D].Id=_D - self.Mainloop[_D]:Act() - end - if self.Mainloop[_D]~=nil then - if self.Mainloop[_D].rem~=nil then - table.remove(self.Mainloop,_D) - end - end - end - if self.Rest>0 then - os.sleep(self.Rest) - end -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if os.clock()-self.init>self.sec then - print(self.c.." steps in "..self.sec.." second(s)") - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=os.clock() - temp.c=0 - return temp -end -function multi:newInterface() - if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type="int" - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.MasterId=0 - c.Active=true - c.Id=-1 - c.Rest=0 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - return c -end ---Helpers -function multi:FreeMainEvent() - self.func={} -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=="int" or self.Type=="mainint" then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=="int" or self.Type=="mainint" then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:Destroy() - if self.Type=="int" or self.Type=="mainint" then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - self.rem=true - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - if not(self.Type=="mainint" or self.Type=="int") then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end ---Constructors -function multi:newEvent(task) - local c=self:newBase() - c.Type="event" - c.Task=task or function() end - function c:Act() - if self.Task(self) and self.Active==true then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - return c -end -function multi:newAlarm(set) - local c=self:newBase() - c.Type="alarm" - c.timer=os.clock() - c.set=set or 0 - function c:Act() - if self.Active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - for i=1,#self.func do - self.func[i](self) - end - end - end - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - self.Active=true - end - function c:OnRing(func) - table.insert(self.func,func) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newLoop(func) - local c=self:newBase() - c.Type="loop" - if func then - c.func={func} - end - function c:Act() - if self.Active==true then - for i=1,#self.func do - self.func[i](os.clock()-self.Parent.Start,self) - end - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - function c:Break() - self.Active=nil - end - return c -end -function multi:newStep(start,reset,count,skip) - local c=self:newBase() - think=1 - c.Type="step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.Active==true then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - return c -end -function multi:newTStep(start,reset,count,set) - local c=self:newBase() - think=1 - c.Type="tstep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=os.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=os.clock() - self:Resume() - end - function c:Act() - if self.Active then - if os.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - end - return c -end -function multi:newTrigger(func) - local c=self:newBase() - c.Type="trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - return c -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - self.Start=os.clock() - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - self.Start=os.clock() -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +multi = {} +multi.Version="4.0.1" +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.MasterId=0 +multi.Active=true +multi.Id=-1 +multi.Type="mainint" +multi.Rest=0 +multi._type=type +--[[function type(v) + local t={} + if multi._type(v)=="table" then + t=getmetatable(v) + if v.Type~=nil then + if multi._type(v.Type)=="string" then + return v.Type + end + end + end + if t.__type~=nil then + return t.__type + else + return multi._type(v) + end +end]] +-- System +function os.getOS() + if package.config:sub(1,1)=="\\" then + return "windows" + else + return "unix" + end +end +if os.getOS()=="windows" then + function os.sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end + end +else + function os.sleep(n) + os.execute("sleep " .. tonumber(n)) + end +end +function multi:newBase(ins) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + self.MasterId=self.MasterId+1 + return c +end +function multi:reboot(r) + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.MasterId=0 + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:Do_Order() + for _D=#self.Mainloop,1,-1 do + if self.Mainloop[_D]~=nil then + self.Mainloop[_D].Id=_D + self.Mainloop[_D]:Act() + end + if self.Mainloop[_D]~=nil then + if self.Mainloop[_D].rem~=nil then + table.remove(self.Mainloop,_D) + end + end + end + if self.Rest>0 then + os.sleep(self.Rest) + end +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if os.clock()-self.init>self.sec then + print(self.c.." steps in "..self.sec.." second(s)") + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=os.clock() + temp.c=0 + return temp +end +function multi:newInterface() + if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type="int" + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.MasterId=0 + c.Active=true + c.Id=-1 + c.Rest=0 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + return c +end +--Helpers +function multi:FreeMainEvent() + self.func={} +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=="int" or self.Type=="mainint" then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=="int" or self.Type=="mainint" then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:Destroy() + if self.Type=="int" or self.Type=="mainint" then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + self.rem=true + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + if not(self.Type=="mainint" or self.Type=="int") then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +--Constructors +function multi:newEvent(task) + local c=self:newBase() + c.Type="event" + c.Task=task or function() end + function c:Act() + if self.Task(self) and self.Active==true then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + return c +end +function multi:newAlarm(set) + local c=self:newBase() + c.Type="alarm" + c.timer=os.clock() + c.set=set or 0 + function c:Act() + if self.Active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + for i=1,#self.func do + self.func[i](self) + end + end + end + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + self.Active=true + end + function c:OnRing(func) + table.insert(self.func,func) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newLoop(func) + local c=self:newBase() + c.Type="loop" + if func then + c.func={func} + end + function c:Act() + if self.Active==true then + for i=1,#self.func do + self.func[i](os.clock()-self.Parent.Start,self) + end + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + function c:Break() + self.Active=nil + end + return c +end +function multi:newStep(start,reset,count,skip) + local c=self:newBase() + think=1 + c.Type="step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.Active==true then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + return c +end +function multi:newTStep(start,reset,count,set) + local c=self:newBase() + think=1 + c.Type="tstep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=os.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=os.clock() + self:Resume() + end + function c:Act() + if self.Active then + if os.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + end + return c +end +function multi:newTrigger(func) + local c=self:newBase() + c.Type="trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + return c +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + self.Start=os.clock() + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + self.Start=os.clock() +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(0.4.0-5).lua b/oldversions/MultiManager(0.4.0-5).lua index f17b2cd..9411cb7 100644 --- a/oldversions/MultiManager(0.4.0-5).lua +++ b/oldversions/MultiManager(0.4.0-5).lua @@ -1,596 +1,596 @@ -multi = {} -multi.Version="4.0.5" -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.MasterId=0 -multi.Active=true -multi.Id=-1 -multi.Type="mainint" -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 ---[[function type(v) - local t={} - if multi._type(v)=="table" then - t=getmetatable(v) - if v.Type~=nil then - if multi._type(v.Type)=="string" then - return v.Type - end - end - end - if t.__type~=nil then - return t.__type - else - return multi._type(v) - end -end]] --- System -function os.getOS() - if package.config:sub(1,1)=="\\" then - return "windows" - else - return "unix" - end -end -if os.getOS()=="windows" then - function os.sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end - end -else - function os.sleep(n) - os.execute("sleep " .. tonumber(n)) - end -end -function multi:newBase(ins) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - self.MasterId=self.MasterId+1 - return c -end -function multi:reboot(r) - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.MasterId=0 - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:Do_Order() - for _D=#self.Mainloop,1,-1 do - if self.Mainloop[_D]~=nil then - self.Mainloop[_D].Id=_D - self.Mainloop[_D]:Act() - end - if self.Mainloop[_D]~=nil then - if self.Mainloop[_D].rem~=nil then - table.remove(self.Mainloop,_D) - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if os.clock()-self.init>self.sec then - print(self.c.." steps in "..self.sec.." second(s)") - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=os.clock() - temp.c=0 - return temp -end -function multi:newInterface() - if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type="int" - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.MasterId=0 - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - return c -end ---Helpers -function multi:FreeMainEvent() - self.func={} -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=="int" or self.Type=="mainint" then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=="int" or self.Type=="mainint" then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:Destroy() - if self.Type=="int" or self.Type=="mainint" then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - self.rem=true - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - if not(self.Type=="mainint" or self.Type=="int") then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end ---Constructors -function multi:newEvent(task) - local c=self:newBase() - c.Type="event" - c.Task=task or function() end - function c:Act() - if self.Task(self) and self.Active==true then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - return c -end -function multi:newAlarm(set) - local c=self:newBase() - c.Type="alarm" - c.timer=os.clock() - c.set=set or 0 - function c:Act() - if self.Active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - for i=1,#self.func do - self.func[i](self) - end - end - end - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - self.Active=true - end - function c:OnRing(func) - table.insert(self.func,func) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newLoop(func) - local c=self:newBase() - c.Type="loop" - if func then - c.func={func} - end - function c:Act() - if self.Active==true then - for i=1,#self.func do - self.func[i](os.clock()-self.Parent.Start,self) - end - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - function c:Break() - self.Active=nil - end - return c -end -function multi:newStep(start,reset,count,skip) - local c=self:newBase() - think=1 - c.Type="step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.Active==true then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - return c -end -function multi:newTStep(start,reset,count,set) - local c=self:newBase() - think=1 - c.Type="tstep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=os.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=os.clock() - self:Resume() - end - function c:Act() - if self.Active then - if os.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - end - return c -end -function multi:newTrigger(func) - local c={}--self:newBase() - c.Type="trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - return c -end -function multi:newConnection() - local c={} - c.Type="connector" - c.func={} - function c:Fire(...) - for i=1,#self.func do - t,e=pcall(self.func[i],...) - if not(t) then - print(e) - end - end - end - function c:connect(func) - table.insert(self.func,func) - end - return c -end -function multi:newJob(func,name) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type="job" - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end ---Incomplete -function multi:addToQueue(name,job) - if self.queue[name]~=nil then - table.insert(self.queue[name],job) - else - self.queue[name]={} - end - if self.QRunner==nil then - self.QRunner=self:newAlarm(.5) - self.QRunner:OnRing(function(self) - if #self.Parent.queue>0 then - local w=math.random(1,#self.Parent.Jobs) - self.Parent.Jobs[w]:Act() - table.remove(self.Parent.Jobs,w) - end - self:Reset() - end) - end -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - self.Start=os.clock() - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - self.Start=os.clock() -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +multi = {} +multi.Version="4.0.5" +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.MasterId=0 +multi.Active=true +multi.Id=-1 +multi.Type="mainint" +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +--[[function type(v) + local t={} + if multi._type(v)=="table" then + t=getmetatable(v) + if v.Type~=nil then + if multi._type(v.Type)=="string" then + return v.Type + end + end + end + if t.__type~=nil then + return t.__type + else + return multi._type(v) + end +end]] +-- System +function os.getOS() + if package.config:sub(1,1)=="\\" then + return "windows" + else + return "unix" + end +end +if os.getOS()=="windows" then + function os.sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end + end +else + function os.sleep(n) + os.execute("sleep " .. tonumber(n)) + end +end +function multi:newBase(ins) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + self.MasterId=self.MasterId+1 + return c +end +function multi:reboot(r) + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.MasterId=0 + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:Do_Order() + for _D=#self.Mainloop,1,-1 do + if self.Mainloop[_D]~=nil then + self.Mainloop[_D].Id=_D + self.Mainloop[_D]:Act() + end + if self.Mainloop[_D]~=nil then + if self.Mainloop[_D].rem~=nil then + table.remove(self.Mainloop,_D) + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if os.clock()-self.init>self.sec then + print(self.c.." steps in "..self.sec.." second(s)") + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=os.clock() + temp.c=0 + return temp +end +function multi:newInterface() + if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type="int" + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.MasterId=0 + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + return c +end +--Helpers +function multi:FreeMainEvent() + self.func={} +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=="int" or self.Type=="mainint" then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=="int" or self.Type=="mainint" then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:Destroy() + if self.Type=="int" or self.Type=="mainint" then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + self.rem=true + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + if not(self.Type=="mainint" or self.Type=="int") then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +--Constructors +function multi:newEvent(task) + local c=self:newBase() + c.Type="event" + c.Task=task or function() end + function c:Act() + if self.Task(self) and self.Active==true then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + return c +end +function multi:newAlarm(set) + local c=self:newBase() + c.Type="alarm" + c.timer=os.clock() + c.set=set or 0 + function c:Act() + if self.Active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + for i=1,#self.func do + self.func[i](self) + end + end + end + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + self.Active=true + end + function c:OnRing(func) + table.insert(self.func,func) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newLoop(func) + local c=self:newBase() + c.Type="loop" + if func then + c.func={func} + end + function c:Act() + if self.Active==true then + for i=1,#self.func do + self.func[i](os.clock()-self.Parent.Start,self) + end + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + function c:Break() + self.Active=nil + end + return c +end +function multi:newStep(start,reset,count,skip) + local c=self:newBase() + think=1 + c.Type="step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.Active==true then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + return c +end +function multi:newTStep(start,reset,count,set) + local c=self:newBase() + think=1 + c.Type="tstep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=os.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=os.clock() + self:Resume() + end + function c:Act() + if self.Active then + if os.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + end + return c +end +function multi:newTrigger(func) + local c={}--self:newBase() + c.Type="trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + return c +end +function multi:newConnection() + local c={} + c.Type="connector" + c.func={} + function c:Fire(...) + for i=1,#self.func do + t,e=pcall(self.func[i],...) + if not(t) then + print(e) + end + end + end + function c:connect(func) + table.insert(self.func,func) + end + return c +end +function multi:newJob(func,name) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type="job" + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +--Incomplete +function multi:addToQueue(name,job) + if self.queue[name]~=nil then + table.insert(self.queue[name],job) + else + self.queue[name]={} + end + if self.QRunner==nil then + self.QRunner=self:newAlarm(.5) + self.QRunner:OnRing(function(self) + if #self.Parent.queue>0 then + local w=math.random(1,#self.Parent.Jobs) + self.Parent.Jobs[w]:Act() + table.remove(self.Parent.Jobs,w) + end + self:Reset() + end) + end +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + self.Start=os.clock() + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + self.Start=os.clock() +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(0.5.1-6).lua b/oldversions/MultiManager(0.5.1-6).lua index 8aef936..82d2633 100644 --- a/oldversions/MultiManager(0.5.1-6).lua +++ b/oldversions/MultiManager(0.5.1-6).lua @@ -1,767 +1,767 @@ -multi = {} -multi.Version="5.1.6" -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.MasterId=0 -multi.Active=true -multi.Id=-1 -multi.Type="mainint" -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 --- System -function multi:Stop() - self.Active=false -end -function os.getOS() - if package.config:sub(1,1)=="\\" then - return "windows" - else - return "unix" - end -end -if os.getOS()=="windows" then - function os.sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end - end -else - function os.sleep(n) - os.execute("sleep " .. tonumber(n)) - end -end -function multi:newBase(ins) - if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" or self.Type=="stack" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - self.MasterId=self.MasterId+1 - return c -end -function multi:reboot(r) - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.MasterId=0 - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - for _D=#self.Mainloop,1,-1 do - if self.Mainloop[_D]~=nil then - self.Mainloop[_D].Id=_D - self.Mainloop[_D]:Act() - end - if self.Mainloop[_D]~=nil then - if self.Mainloop[_D].rem~=nil then - table.remove(self.Mainloop,_D) - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if os.clock()-self.init>self.sec then - print(self.c.." steps in "..self.sec.." second(s)") - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=os.clock() - temp.c=0 - return temp -end -function multi:newInterface() - if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type="int" - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.MasterId=0 - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - return c -end -function multi:newStack(file) - local c=self:newInterface() - c.Type="stack" - stack=c - c.last={} - c.funcE={} - if file then - dofile(file) - end - function c:OnStackCompleted(func) - table.insert(self.funcE,func) - end - return c -end ---Helpers -function multi:protect() - function self:Do_Order() - for _D=#self.Mainloop,1,-1 do - if self.Mainloop[_D]~=nil then - self.Mainloop[_D].Id=_D - local status, err=pcall(self.Mainloop[_D].Act,self.Mainloop[_D]) - if err and not(self.Mainloop[_D].error) then - self.Mainloop[_D].error=err - print(err..": Ingoring error continuing...") - end - end - if self.Mainloop[_D]~=nil then - if self.Mainloop[_D].rem~=nil then - table.remove(self.Mainloop,_D) - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:unProtect() - function self:Do_Order() - for _D=#self.Mainloop,1,-1 do - if self.Mainloop[_D]~=nil then - self.Mainloop[_D].Id=_D - self.Mainloop[_D]:Act() - end - if self.Mainloop[_D]~=nil then - if self.Mainloop[_D].rem~=nil then - table.remove(self.Mainloop,_D) - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=="event" then - self:OnEvent(func) - elseif self.Type=="alarm" then - self:OnRing(func) - elseif self.Type=="step" or self.Type=="tstep" then - self:OnEnd(func) - elseif self.Type=="loop" then - self:OnBreak(func) - else - error("No final event exists for: "..self.Type) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - self.ender[i](self) - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=="int" or self.Type=="mainint" then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=="int" or self.Type=="mainint" then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:Destroy() - if self.Type=="int" or self.Type=="mainint" then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - self.rem=true - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - if not(self.Type=="mainint" or self.Type=="int") then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end ---Constructors -function multi:newEvent(task) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="event" - c.Task=task or function() end - function c:Act() - if self.Task(self) and self.Active==true then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="alarm" - c.timer=os.clock() - c.set=set or 0 - function c:Act() - if self.Active==true then - if os.clock()-self.timer>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer=os.clock() - self.Active=true - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - self.Active=true - end - function c:OnRing(func) - table.insert(self.func,func) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newLoop(func) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="loop" - c.Start=os.clock() - if func then - c.func={func} - end - function c:Act() - if self.Active==true then - for i=1,#self.func do - self.func[i](os.clock()-self.Start,self) - end - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.Active==true then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="tstep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=os.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=os.clock() - self:Resume() - end - function c:Act() - if self.Active then - if os.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=os.clock() - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTrigger(func) - local c={} - c.Type="trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - return c -end -function multi:newConnection() - local c={} - c.Type="connector" - c.func={} - function c:Fire(...) - for i=1,#self.func do - t,e=pcall(self.func[i],...) - if not(t) then - print(e) - end - end - end - function c:bind(t) - self.func=t - end - function c:connect(func) - table.insert(self.func,func) - end - return c -end -function multi:newJob(func,name) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type="job" - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - self.Start=os.clock() - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - self.Start=os.clock() -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +multi = {} +multi.Version="5.1.6" +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.MasterId=0 +multi.Active=true +multi.Id=-1 +multi.Type="mainint" +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +-- System +function multi:Stop() + self.Active=false +end +function os.getOS() + if package.config:sub(1,1)=="\\" then + return "windows" + else + return "unix" + end +end +if os.getOS()=="windows" then + function os.sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end + end +else + function os.sleep(n) + os.execute("sleep " .. tonumber(n)) + end +end +function multi:newBase(ins) + if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" or self.Type=="stack" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + self.MasterId=self.MasterId+1 + return c +end +function multi:reboot(r) + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.MasterId=0 + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + for _D=#self.Mainloop,1,-1 do + if self.Mainloop[_D]~=nil then + self.Mainloop[_D].Id=_D + self.Mainloop[_D]:Act() + end + if self.Mainloop[_D]~=nil then + if self.Mainloop[_D].rem~=nil then + table.remove(self.Mainloop,_D) + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if os.clock()-self.init>self.sec then + print(self.c.." steps in "..self.sec.." second(s)") + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=os.clock() + temp.c=0 + return temp +end +function multi:newInterface() + if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type="int" + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.MasterId=0 + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + return c +end +function multi:newStack(file) + local c=self:newInterface() + c.Type="stack" + stack=c + c.last={} + c.funcE={} + if file then + dofile(file) + end + function c:OnStackCompleted(func) + table.insert(self.funcE,func) + end + return c +end +--Helpers +function multi:protect() + function self:Do_Order() + for _D=#self.Mainloop,1,-1 do + if self.Mainloop[_D]~=nil then + self.Mainloop[_D].Id=_D + local status, err=pcall(self.Mainloop[_D].Act,self.Mainloop[_D]) + if err and not(self.Mainloop[_D].error) then + self.Mainloop[_D].error=err + print(err..": Ingoring error continuing...") + end + end + if self.Mainloop[_D]~=nil then + if self.Mainloop[_D].rem~=nil then + table.remove(self.Mainloop,_D) + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:unProtect() + function self:Do_Order() + for _D=#self.Mainloop,1,-1 do + if self.Mainloop[_D]~=nil then + self.Mainloop[_D].Id=_D + self.Mainloop[_D]:Act() + end + if self.Mainloop[_D]~=nil then + if self.Mainloop[_D].rem~=nil then + table.remove(self.Mainloop,_D) + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=="event" then + self:OnEvent(func) + elseif self.Type=="alarm" then + self:OnRing(func) + elseif self.Type=="step" or self.Type=="tstep" then + self:OnEnd(func) + elseif self.Type=="loop" then + self:OnBreak(func) + else + error("No final event exists for: "..self.Type) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + self.ender[i](self) + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=="int" or self.Type=="mainint" then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=="int" or self.Type=="mainint" then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:Destroy() + if self.Type=="int" or self.Type=="mainint" then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + self.rem=true + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + if not(self.Type=="mainint" or self.Type=="int") then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +--Constructors +function multi:newEvent(task) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="event" + c.Task=task or function() end + function c:Act() + if self.Task(self) and self.Active==true then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="alarm" + c.timer=os.clock() + c.set=set or 0 + function c:Act() + if self.Active==true then + if os.clock()-self.timer>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer=os.clock() + self.Active=true + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + self.Active=true + end + function c:OnRing(func) + table.insert(self.func,func) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newLoop(func) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="loop" + c.Start=os.clock() + if func then + c.func={func} + end + function c:Act() + if self.Active==true then + for i=1,#self.func do + self.func[i](os.clock()-self.Start,self) + end + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.Active==true then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="tstep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=os.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=os.clock() + self:Resume() + end + function c:Act() + if self.Active then + if os.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=os.clock() + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTrigger(func) + local c={} + c.Type="trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + return c +end +function multi:newConnection() + local c={} + c.Type="connector" + c.func={} + function c:Fire(...) + for i=1,#self.func do + t,e=pcall(self.func[i],...) + if not(t) then + print(e) + end + end + end + function c:bind(t) + self.func=t + end + function c:connect(func) + table.insert(self.func,func) + end + return c +end +function multi:newJob(func,name) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type="job" + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + self.Start=os.clock() + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + self.Start=os.clock() +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(0.6.1-6).lua b/oldversions/MultiManager(0.6.1-6).lua index cdd075a..b07a87f 100644 --- a/oldversions/MultiManager(0.6.1-6).lua +++ b/oldversions/MultiManager(0.6.1-6).lua @@ -1,929 +1,929 @@ -multi = {} -multi.Version={6,1,6}-- History: EventManager,EventManager+,MultiManager <-- Current -multi.stage="stable" -multi.Features=multi.Version[1].."."..multi.Version[2].."."..multi.Version[3].." "..multi.stage..[[ - Objects: - Event - Alarm - Loop - Step - TStep - Trigger - Task - Connection - Timer - Job -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.Id=-1 -multi.Type="mainint" -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time --- System -function multi:Stop() - self.Active=false -end -function os.getOS() - if package.config:sub(1,1)=="\\" then - return "windows" - else - return "unix" - end -end -if os.getOS()=="windows" then - function os.sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end - end -else - function os.sleep(n) - os.execute("sleep " .. tonumber(n)) - end -end -function multi.executeFunction(name,...) - if type(_G[name])=="function" then - _G[name](...) - else - print("Error: Not a function") - end -end -function multi:newBase(ins) - if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" or self.Type=="stack" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:reboot(r) - local before=collectgarbage("count") - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage("count") - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - Loop[_D]:Act() - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end -end -function multi:fromfile(path,int) - int=int or multi - local test2={} - local test=bin.load(path) - local tp=test:getBlock("s") - if tp=="event" then - test2=int:newEvent(test:getBlock("f")) - local t=test:getBlock("t") - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=="alarm" then - test2=int:newAlarm(test:getBlock("n")) - elseif tp=="loop" then - test2=int:newLoop(test:getBlock("t")[1]) - elseif tp=="step" or tp=="tstep" then - local func=test:getBlock("t") - local funcE=test:getBlock("t") - local funcS=test:getBlock("t") - local tab=test:getBlock("t") - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=="trigger" then - test2=int:newTrigger(test:getBlock("f")) - elseif tp=="connector" then - test2=int:newConnection() - test2.func=test:getBlock("t") - elseif tp=="timer" then - test2=int:newTimer() - test2.count=tonumber(test:getBlock("n")) - else - print("Error: The file you selected is not a valid multi file object!") - return false - end - return test2 -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if multi.clock()-self.init>self.sec then - print(self.c.." steps in "..self.sec.." second(s)") - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=multi.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path).."\\item"..item[i]..".dat") - end - local int=bin.new() - int:addBlock("int") - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi:newInterface(file) - if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type="int" - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - multi.Cself=c - loadstring("interface=multi.Cself "..io.open(file,"rb"):read("*all"))() - end - return c -end -function multi:newStack(file) - local c=self:newInterface() - c.Type="stack" - c.last={} - c.funcE={} - if file then - multi.Cself=c - loadstring("stack=multi.Cself "..io.open(file,"rb"):read("*all"))() - end - function c:OnStackCompleted(func) - table.insert(self.funcE,func) - end - return c -end ---Helpers -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - print(err..": Ingoring error continuing...") - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:unProtect() - function self:Do_Order() - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - Loop[_D]:Act() - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=="event" then - self:OnEvent(func) - elseif self.Type=="alarm" then - self:OnRing(func) - elseif self.Type=="step" or self.Type=="tstep" then - self:OnEnd(func) - elseif self.Type=="loop" then - self:OnBreak(func) - else - error("No final event exists for: "..self.Type) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - self.ender[i](self) - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=="int" or self.Type=="mainint" then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=="int" or self.Type=="mainint" then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=="int" or self.Type=="mainint" then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - if not(self.Type=="mainint" or self.Type=="int") then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end ---Constructors -function multi:newEvent(task) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="event" - c.Task=task or function() end - function c:Act() - if self.Task(self) and self.Active==true then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="alarm" - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.Active==true then - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=="stack" then - c:Pause() - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - else - c.timer:Start() - end - return c -end -function multi:newTimer() - local c={} - c.Type="timer" - c.time=0 - c.count=0 - function c:Start() - self.time=multi.clock() - end - function c:Get() - return (multi.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=multi.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newLoop(func) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="loop" - c.Start=multi.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.Active==true then - for i=1,#self.func do - self.func[i](multi.clock()-self.Start,self) - end - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.Active==true then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="tstep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=multi.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=multi.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.Active then - if multi.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=multi.clock() - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTrigger(func) - local c={} - c.Type="trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - return c -end -function multi:newConnection() - local c={} - c.Type="connector" - c.func={} - function c:Fire(...) - for i=1,#self.func do - t,e=pcall(self.func[i],...) - if not(t) then - print(e) - end - end - end - function c:bind(t) - self.func=t - end - function c:connect(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -function multi:newJob(func,name) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type="job" - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,"Start",multi.clock()) - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,"Start",multi.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +multi = {} +multi.Version={6,1,6}-- History: EventManager,EventManager+,MultiManager <-- Current +multi.stage="stable" +multi.Features=multi.Version[1].."."..multi.Version[2].."."..multi.Version[3].." "..multi.stage..[[ + Objects: + Event + Alarm + Loop + Step + TStep + Trigger + Task + Connection + Timer + Job +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.Id=-1 +multi.Type="mainint" +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +-- System +function multi:Stop() + self.Active=false +end +function os.getOS() + if package.config:sub(1,1)=="\\" then + return "windows" + else + return "unix" + end +end +if os.getOS()=="windows" then + function os.sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end + end +else + function os.sleep(n) + os.execute("sleep " .. tonumber(n)) + end +end +function multi.executeFunction(name,...) + if type(_G[name])=="function" then + _G[name](...) + else + print("Error: Not a function") + end +end +function multi:newBase(ins) + if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" or self.Type=="stack" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:reboot(r) + local before=collectgarbage("count") + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage("count") + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + Loop[_D]:Act() + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end +end +function multi:fromfile(path,int) + int=int or multi + local test2={} + local test=bin.load(path) + local tp=test:getBlock("s") + if tp=="event" then + test2=int:newEvent(test:getBlock("f")) + local t=test:getBlock("t") + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=="alarm" then + test2=int:newAlarm(test:getBlock("n")) + elseif tp=="loop" then + test2=int:newLoop(test:getBlock("t")[1]) + elseif tp=="step" or tp=="tstep" then + local func=test:getBlock("t") + local funcE=test:getBlock("t") + local funcS=test:getBlock("t") + local tab=test:getBlock("t") + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=="trigger" then + test2=int:newTrigger(test:getBlock("f")) + elseif tp=="connector" then + test2=int:newConnection() + test2.func=test:getBlock("t") + elseif tp=="timer" then + test2=int:newTimer() + test2.count=tonumber(test:getBlock("n")) + else + print("Error: The file you selected is not a valid multi file object!") + return false + end + return test2 +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if multi.clock()-self.init>self.sec then + print(self.c.." steps in "..self.sec.." second(s)") + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=multi.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path).."\\item"..item[i]..".dat") + end + local int=bin.new() + int:addBlock("int") + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi:newInterface(file) + if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type="int" + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + multi.Cself=c + loadstring("interface=multi.Cself "..io.open(file,"rb"):read("*all"))() + end + return c +end +function multi:newStack(file) + local c=self:newInterface() + c.Type="stack" + c.last={} + c.funcE={} + if file then + multi.Cself=c + loadstring("stack=multi.Cself "..io.open(file,"rb"):read("*all"))() + end + function c:OnStackCompleted(func) + table.insert(self.funcE,func) + end + return c +end +--Helpers +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + print(err..": Ingoring error continuing...") + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:unProtect() + function self:Do_Order() + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + Loop[_D]:Act() + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=="event" then + self:OnEvent(func) + elseif self.Type=="alarm" then + self:OnRing(func) + elseif self.Type=="step" or self.Type=="tstep" then + self:OnEnd(func) + elseif self.Type=="loop" then + self:OnBreak(func) + else + error("No final event exists for: "..self.Type) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + self.ender[i](self) + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=="int" or self.Type=="mainint" then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=="int" or self.Type=="mainint" then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=="int" or self.Type=="mainint" then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + if not(self.Type=="mainint" or self.Type=="int") then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +--Constructors +function multi:newEvent(task) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="event" + c.Task=task or function() end + function c:Act() + if self.Task(self) and self.Active==true then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="alarm" + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.Active==true then + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=="stack" then + c:Pause() + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + else + c.timer:Start() + end + return c +end +function multi:newTimer() + local c={} + c.Type="timer" + c.time=0 + c.count=0 + function c:Start() + self.time=multi.clock() + end + function c:Get() + return (multi.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=multi.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newLoop(func) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="loop" + c.Start=multi.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.Active==true then + for i=1,#self.func do + self.func[i](multi.clock()-self.Start,self) + end + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.Active==true then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="tstep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=multi.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=multi.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.Active then + if multi.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=multi.clock() + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTrigger(func) + local c={} + c.Type="trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + return c +end +function multi:newConnection() + local c={} + c.Type="connector" + c.func={} + function c:Fire(...) + for i=1,#self.func do + t,e=pcall(self.func[i],...) + if not(t) then + print(e) + end + end + end + function c:bind(t) + self.func=t + end + function c:connect(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +function multi:newJob(func,name) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type="job" + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,"Start",multi.clock()) + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,"Start",multi.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(0.6.2).lua b/oldversions/MultiManager(0.6.2).lua index ca66f34..84bed31 100644 --- a/oldversions/MultiManager(0.6.2).lua +++ b/oldversions/MultiManager(0.6.2).lua @@ -1,1056 +1,1056 @@ -if not bin then - print("Warning the 'bin' library wasn't required! multi:tofile(path) and the multi:fromfile(path,int) feature will not work!") -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == "table" then - if type(t1[k] or false) == "table" then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={6,2,0}-- History: EventManager,EventManager+,MultiManager <-- Current -multi.stage="stable" -multi.Features=multi.Version[1].."."..multi.Version[2].."."..multi.Version[3].." "..multi.stage..[[ - Objects: - Event - Alarm - Loop - Step - TStep - Trigger - Task - Connection - Timer - Job -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.Id=-1 -multi.Type="mainint" -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi --- System -function multi:Stop() - self.Active=false -end -function os.getOS() - if package.config:sub(1,1)=="\\" then - return "windows" - else - return "unix" - end -end -if os.getOS()=="windows" then - function os.sleep(n) - if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end - end -else - function os.sleep(n) - os.execute("sleep " .. tonumber(n)) - end -end -function multi.executeFunction(name,...) - if type(_G[name])=="function" then - _G[name](...) - else - print("Error: Not a function") - end -end -function multi:reboot(r) - local before=collectgarbage("count") - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=="table" then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage("count") - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - if Loop[_D].Active then - Loop[_D].Id=_D - Loop[_D]:Act() - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end -end -function multi:fromfile(path,int) - int=int or multi - local test2={} - local test=bin.load(path) - local tp=test:getBlock("s") - if tp=="event" then - test2=int:newEvent(test:getBlock("f")) - local t=test:getBlock("t") - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=="alarm" then - test2=int:newAlarm(test:getBlock("n")) - elseif tp=="loop" then - test2=int:newLoop(test:getBlock("t")[1]) - elseif tp=="step" or tp=="tstep" then - local func=test:getBlock("t") - local funcE=test:getBlock("t") - local funcS=test:getBlock("t") - local tab=test:getBlock("t") - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=="trigger" then - test2=int:newTrigger(test:getBlock("f")) - elseif tp=="connector" then - test2=int:newConnection() - test2.func=test:getBlock("t") - elseif tp=="timer" then - test2=int:newTimer() - test2.count=tonumber(test:getBlock("n")) - else - print("Error: The file you selected is not a valid multi file object!") - return false - end - return test2 -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if multi.clock()-self.init>self.sec then - print(self.c.." steps in "..self.sec.." second(s)") - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=multi.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path).."\\item"..item[i]..".dat") - end - local int=bin.new() - int:addBlock("int") - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - print(err..": Ingoring error continuing...") - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:unProtect() - function self:Do_Order() - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - Loop[_D]:Act() - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=="event" then - self:OnEvent(func) - elseif self.Type=="alarm" then - self:OnRing(func) - elseif self.Type=="step" or self.Type=="tstep" then - self:OnEnd(func) - elseif self.Type=="loop" or self.Type=="updater" then - self:OnBreak(func) - else - error("No final event exists for: "..self.Type) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - self.ender[i](self) - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=="int" or self.Type=="mainint" then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=="int" or self.Type=="mainint" then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=="int" or self.Type=="mainint" then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=="number" then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=="function" then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print("Error Data Type!!!") - end -end -function multi:oneTime(func,...) - if not(self.Type=="mainint" or self.Type=="int") then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" or self.Type=="stack" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newInterface(file) - if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type="int" - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - multi.Cself=c - loadstring("interface=multi.Cself "..io.open(file,"rb"):read("*all"))() - end - return c -end -function multi:newStack(file) - local c=self:newInterface() - c.Type="stack" - c.last={} - c.funcE={} - if file then - multi.Cself=c - loadstring("stack=multi.Cself "..io.open(file,"rb"):read("*all"))() - end - function c:OnStackCompleted(func) - table.insert(self.funcE,func) - end - return c -end ---Constructors [ACTORS] -function multi:coustomObject(objRef,t) - local c={} - if t=="process" then - c=self:newBase() - if type(objRef)=="table" then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type="coustomObject" - end -end -function multi:newEvent(task) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="event" - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="alarm" - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=="stack" then - c:Pause() - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - else - c.timer:Start() - end - return c -end -function multi:newLoop(func) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="loop" - c.Start=multi.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](multi.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type="updater" - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - c.OnUpdate=multi.OnMainConnect - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="step" - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=="stack" then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type="tstep" - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=multi.clock() - c.set=set or 1 - c.funcS={} - c.Reset=c.Resume - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=multi.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if multi.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=multi.clock() - self:Resume() - end - if self.Type=="stack" then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end - local c = {} - if self.Type=="int" then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type="job" - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end --- Constructors [NON-ACTORS] -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - local c=multi:newBase() - c.Type="watcher" - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - return c - end - if type(namespace)~="table" and type(namespace)=="string" then - return WatcherObj(_G,namespace) - elseif type(namespace)=="table" and (type(name)=="string" or "number") then - return WatcherObj(namespace,name) - else - print("Warning, invalid arguments! Nothing returned!") - end -end -function multi:newTimer() - local c={} - c.Type="timer" - c.time=0 - c.count=0 - function c:Start() - self.time=multi.clock() - end - function c:Get() - return (multi.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=multi.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type="trigger" - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - return c -end -function multi:newConnection() - local c={} - c.Type="connector" - c.func={} - c.ID=0 - function c:Fire(...) - for i=#self.func,1,-1 do - t,e=pcall(self.func[i][1],...) - if not(t) then - print(e) - end - end - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func) - self.ID=self.ID+1 - table.insert(self.func,{func,self.ID}) - return { - Link=self.func, - ID=self.ID, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,"Start",multi.clock()) - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,"Start",multi.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +if not bin then + print("Warning the 'bin' library wasn't required! multi:tofile(path) and the multi:fromfile(path,int) feature will not work!") +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == "table" then + if type(t1[k] or false) == "table" then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={6,2,0}-- History: EventManager,EventManager+,MultiManager <-- Current +multi.stage="stable" +multi.Features=multi.Version[1].."."..multi.Version[2].."."..multi.Version[3].." "..multi.stage..[[ + Objects: + Event + Alarm + Loop + Step + TStep + Trigger + Task + Connection + Timer + Job +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.Id=-1 +multi.Type="mainint" +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +-- System +function multi:Stop() + self.Active=false +end +function os.getOS() + if package.config:sub(1,1)=="\\" then + return "windows" + else + return "unix" + end +end +if os.getOS()=="windows" then + function os.sleep(n) + if n > 0 then os.execute("ping -n " .. tonumber(n+1) .. " localhost > NUL") end + end +else + function os.sleep(n) + os.execute("sleep " .. tonumber(n)) + end +end +function multi.executeFunction(name,...) + if type(_G[name])=="function" then + _G[name](...) + else + print("Error: Not a function") + end +end +function multi:reboot(r) + local before=collectgarbage("count") + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=="table" then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage("count") + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + if Loop[_D].Active then + Loop[_D].Id=_D + Loop[_D]:Act() + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end +end +function multi:fromfile(path,int) + int=int or multi + local test2={} + local test=bin.load(path) + local tp=test:getBlock("s") + if tp=="event" then + test2=int:newEvent(test:getBlock("f")) + local t=test:getBlock("t") + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=="alarm" then + test2=int:newAlarm(test:getBlock("n")) + elseif tp=="loop" then + test2=int:newLoop(test:getBlock("t")[1]) + elseif tp=="step" or tp=="tstep" then + local func=test:getBlock("t") + local funcE=test:getBlock("t") + local funcS=test:getBlock("t") + local tab=test:getBlock("t") + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=="trigger" then + test2=int:newTrigger(test:getBlock("f")) + elseif tp=="connector" then + test2=int:newConnection() + test2.func=test:getBlock("t") + elseif tp=="timer" then + test2=int:newTimer() + test2.count=tonumber(test:getBlock("n")) + else + print("Error: The file you selected is not a valid multi file object!") + return false + end + return test2 +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if multi.clock()-self.init>self.sec then + print(self.c.." steps in "..self.sec.." second(s)") + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=multi.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path).."\\item"..item[i]..".dat") + end + local int=bin.new() + int:addBlock("int") + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + print(err..": Ingoring error continuing...") + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:unProtect() + function self:Do_Order() + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + Loop[_D]:Act() + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=="event" then + self:OnEvent(func) + elseif self.Type=="alarm" then + self:OnRing(func) + elseif self.Type=="step" or self.Type=="tstep" then + self:OnEnd(func) + elseif self.Type=="loop" or self.Type=="updater" then + self:OnBreak(func) + else + error("No final event exists for: "..self.Type) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + self.ender[i](self) + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=="int" or self.Type=="mainint" then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=="int" or self.Type=="mainint" then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=="int" or self.Type=="mainint" then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=="number" then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=="function" then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print("Error Data Type!!!") + end +end +function multi:oneTime(func,...) + if not(self.Type=="mainint" or self.Type=="int") then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=="mainint" or self.Type=="int" or self.Type=="stack") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" or self.Type=="stack" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newInterface(file) + if not(self.Type=="mainint") then error("Can only create an interface on the multi obj") return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type="int" + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + multi.Cself=c + loadstring("interface=multi.Cself "..io.open(file,"rb"):read("*all"))() + end + return c +end +function multi:newStack(file) + local c=self:newInterface() + c.Type="stack" + c.last={} + c.funcE={} + if file then + multi.Cself=c + loadstring("stack=multi.Cself "..io.open(file,"rb"):read("*all"))() + end + function c:OnStackCompleted(func) + table.insert(self.funcE,func) + end + return c +end +--Constructors [ACTORS] +function multi:coustomObject(objRef,t) + local c={} + if t=="process" then + c=self:newBase() + if type(objRef)=="table" then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type="coustomObject" + end +end +function multi:newEvent(task) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="event" + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="alarm" + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=="stack" then + c:Pause() + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + else + c.timer:Start() + end + return c +end +function multi:newLoop(func) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="loop" + c.Start=multi.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](multi.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type="updater" + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + c.OnUpdate=multi.OnMainConnect + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="step" + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=="stack" then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type="tstep" + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=multi.clock() + c.set=set or 1 + c.funcS={} + c.Reset=c.Resume + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=multi.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if multi.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=multi.clock() + self:Resume() + end + if self.Type=="stack" then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=="mainint" or self.Type=="int") then error("Can only create an object on multi or an interface obj") return false end + local c = {} + if self.Type=="int" then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type="job" + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +-- Constructors [NON-ACTORS] +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + local c=multi:newBase() + c.Type="watcher" + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + return c + end + if type(namespace)~="table" and type(namespace)=="string" then + return WatcherObj(_G,namespace) + elseif type(namespace)=="table" and (type(name)=="string" or "number") then + return WatcherObj(namespace,name) + else + print("Warning, invalid arguments! Nothing returned!") + end +end +function multi:newTimer() + local c={} + c.Type="timer" + c.time=0 + c.count=0 + function c:Start() + self.time=multi.clock() + end + function c:Get() + return (multi.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=multi.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type="trigger" + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + return c +end +function multi:newConnection() + local c={} + c.Type="connector" + c.func={} + c.ID=0 + function c:Fire(...) + for i=#self.func,1,-1 do + t,e=pcall(self.func[i][1],...) + if not(t) then + print(e) + end + end + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func) + self.ID=self.ID+1 + table.insert(self.func,{func,self.ID}) + return { + Link=self.func, + ID=self.ID, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,"Start",multi.clock()) + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,"Start",multi.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(0.6.3).lua b/oldversions/MultiManager(0.6.3).lua index 5221c83..0c58e16 100644 --- a/oldversions/MultiManager(0.6.3).lua +++ b/oldversions/MultiManager(0.6.3).lua @@ -1,1112 +1,1112 @@ -if not bin then - print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={6,3,0}-- History: EventManager,EventManager+,MultiManager <-- Current -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features="Current Version: "..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 15 Objects: -+Events -+Alarms -+Loops -+Steps -+TSteps -+Triggers -+Tasks -+Connections -+Timers -+Jobs -+Interfaces -+Stacks -+Updaters -+Watchers -+CustomObjects - -Constructors [Runners] ----------------------- -multi:newInterface([string: FILE defualt: nil]) -multi:newStack([string: FILE defualt: nil]) - -Constructors [ACTORS] ---------------------- -multi:newEvent([function: TASK defualt: function() end]) -multi:newAlarm([number: SET defualt: 0]) -multi:newLoop([function: FUNC]) -multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -multi:newUpdater([number: SKIP defualt: 0]) -multi:newWatcher(table: NAMESPACE,string: NAME) -multi:newCustomObject([table: OBJREF],[string: T="process"]) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob() - -Constructors [NON-ACTORS] -------------------------- -multi:newTriggers() -multi:newTasks() -multi:newConnections() -multi:newTimers() -]] -multi.changelog=[[Changelog starts at Version 6.3.0]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.Id=-1 -multi.Type='mainint' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.range={ - getN = function(self) multi:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link:Resume() return nil end return self.n end, -} -setmetatable(multi.range,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=multi.Mainloop[multi.CID] - self.Link:Pause() - return function() return self:getN() end - end -}) --- System -function multi:Stop() - self.Active=false -end -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end -end -function multi:fromfile(path,int) - int=int or multi - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec) - local temp=self:newLoop(function(t,self) - if multi.clock()-self.init>self.sec then - print(self.c..' steps in '..self.sec..' second(s)') - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=multi.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('int') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - print(err..': Ingoring error continuing...') - end - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:unProtect() - function self:Do_Order() - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - Loop[_D]:Act() - end - end - if self.Rest~=0 then - os.sleep(self.Rest) - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - elseif self.Type=='loop' or self.Type=='updater' then - self:OnBreak(func) - else - error('No final event exists for: '..self.Type) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - self.ender[i](self) - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:Pause(n) - if self.Type=='int' or self.Type=='mainint' then - self.Active=false - if not(n) then - local c=self:getChildren() - for i=1,#c do - c[i]:Pause() - end - else - self:hold(n) - end - else - if n==nil then - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - else - self:hold(n) - end - end -end -function multi:Resume() - if self.Type=='int' or self.Type=='mainint' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='int' or self.Type=='mainint' then - local c=self:getChildren() - for i=1,#c do - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end -function multi:hold(task) - self:Pause() - if type(task)=='number' then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - elseif type(task)=='function' then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt:Stop() end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - else - print('Error Data Type!!!') - end -end -function multi:oneTime(func,...) - if not(self.Type=='mainint' or self.Type=='int') then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=='mainint' or self.Type=='int' or self.Type=='stack') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='int' or self.Type=='stack' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newInterface(file) - if not(self.Type=='mainint') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type='int' - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Stop() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - multi.Cself=c - loadstring('interface=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - return c -end -function multi:newStack(file) - local c=self:newInterface() - c.Type='stack' - c.last={} - c.funcE={} - if file then - multi.Cself=c - loadstring('stack=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - function c:OnStackCompleted(func) - table.insert(self.funcE,func) - end - return c -end ---Constructors [ACTORS] -function multi:newCustomObject(objRef,t) - local c={} - if t=='process' then - c=self:newBase() - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end -end -function multi:newEvent(task) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='event' - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=='stack' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='alarm' - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=='stack' then - c:Pause() - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - else - c.timer:Start() - end - return c -end -function multi:newLoop(func) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='loop' - c.Start=multi.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](multi.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=='stack' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - c.OnUpdate=multi.OnMainConnect - if self.Type=='stack' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=='stack' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=='stack' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='tstep' - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=multi.clock() - c.set=set or 1 - c.funcS={} - c.Reset=c.Resume - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=multi.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if multi.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=multi.clock() - self:Resume() - end - if self.Type=='stack' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(function(self) - if self.Parent.last==self then - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end - self:Destroy() - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end) - end - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - local c=multi:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=='mainint' or self.Type=='int') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='int' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end --- Constructors [NON-ACTORS] -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=multi.clock() - end - function c:Get() - return (multi.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=multi.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - return c -end -function multi:newConnection() - local c={} - c.Type='connector' - c.func={} - c.ID=0 - function c:Fire(...) - for i=#self.func,1,-1 do - t,e=pcall(self.func[i][1],...) - if not(t) then - print(e) - end - end - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func) - self.ID=self.ID+1 - table.insert(self.func,{func,self.ID}) - return { - Link=self.func, - ID=self.ID, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',multi.clock()) - while self.Active do - self:Do_Order() - end -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',multi.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - self:Do_Order() - end -end +if not bin then + print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={6,3,0}-- History: EventManager,EventManager+,MultiManager <-- Current +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features="Current Version: "..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 15 Objects: ++Events ++Alarms ++Loops ++Steps ++TSteps ++Triggers ++Tasks ++Connections ++Timers ++Jobs ++Interfaces ++Stacks ++Updaters ++Watchers ++CustomObjects + +Constructors [Runners] +---------------------- +multi:newInterface([string: FILE defualt: nil]) +multi:newStack([string: FILE defualt: nil]) + +Constructors [ACTORS] +--------------------- +multi:newEvent([function: TASK defualt: function() end]) +multi:newAlarm([number: SET defualt: 0]) +multi:newLoop([function: FUNC]) +multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +multi:newUpdater([number: SKIP defualt: 0]) +multi:newWatcher(table: NAMESPACE,string: NAME) +multi:newCustomObject([table: OBJREF],[string: T="process"]) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob() + +Constructors [NON-ACTORS] +------------------------- +multi:newTriggers() +multi:newTasks() +multi:newConnections() +multi:newTimers() +]] +multi.changelog=[[Changelog starts at Version 6.3.0]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.Id=-1 +multi.Type='mainint' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.range={ + getN = function(self) multi:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link:Resume() return nil end return self.n end, +} +setmetatable(multi.range,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=multi.Mainloop[multi.CID] + self.Link:Pause() + return function() return self:getN() end + end +}) +-- System +function multi:Stop() + self.Active=false +end +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end +end +function multi:fromfile(path,int) + int=int or multi + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec) + local temp=self:newLoop(function(t,self) + if multi.clock()-self.init>self.sec then + print(self.c..' steps in '..self.sec..' second(s)') + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=multi.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('int') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + print(err..': Ingoring error continuing...') + end + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:unProtect() + function self:Do_Order() + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + Loop[_D]:Act() + end + end + if self.Rest~=0 then + os.sleep(self.Rest) + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + elseif self.Type=='loop' or self.Type=='updater' then + self:OnBreak(func) + else + error('No final event exists for: '..self.Type) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + self.ender[i](self) + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:Pause(n) + if self.Type=='int' or self.Type=='mainint' then + self.Active=false + if not(n) then + local c=self:getChildren() + for i=1,#c do + c[i]:Pause() + end + else + self:hold(n) + end + else + if n==nil then + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + else + self:hold(n) + end + end +end +function multi:Resume() + if self.Type=='int' or self.Type=='mainint' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='int' or self.Type=='mainint' then + local c=self:getChildren() + for i=1,#c do + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end +function multi:hold(task) + self:Pause() + if type(task)=='number' then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + elseif type(task)=='function' then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt:Stop() end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + else + print('Error Data Type!!!') + end +end +function multi:oneTime(func,...) + if not(self.Type=='mainint' or self.Type=='int') then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=='mainint' or self.Type=='int' or self.Type=='stack') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='int' or self.Type=='stack' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newInterface(file) + if not(self.Type=='mainint') then error('Can only create an interface on the multi obj') return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type='int' + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Stop() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + multi.Cself=c + loadstring('interface=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + return c +end +function multi:newStack(file) + local c=self:newInterface() + c.Type='stack' + c.last={} + c.funcE={} + if file then + multi.Cself=c + loadstring('stack=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + function c:OnStackCompleted(func) + table.insert(self.funcE,func) + end + return c +end +--Constructors [ACTORS] +function multi:newCustomObject(objRef,t) + local c={} + if t=='process' then + c=self:newBase() + if type(objRef)=='table' then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type='coustomObject' + end +end +function multi:newEvent(task) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='event' + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=='stack' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='alarm' + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=='stack' then + c:Pause() + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + table.remove(self.Parent.Mainloop,#self.Parent.Mainloop) + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + else + c.timer:Start() + end + return c +end +function multi:newLoop(func) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='loop' + c.Start=multi.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](multi.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=='stack' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + c.OnUpdate=multi.OnMainConnect + if self.Type=='stack' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=='stack' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=='stack' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='tstep' + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=multi.clock() + c.set=set or 1 + c.funcS={} + c.Reset=c.Resume + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=multi.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if multi.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=multi.clock() + self:Resume() + end + if self.Type=='stack' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(function(self) + if self.Parent.last==self then + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end + self:Destroy() + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end) + end + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + local c=multi:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=='mainint' or self.Type=='int') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='int' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +-- Constructors [NON-ACTORS] +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=multi.clock() + end + function c:Get() + return (multi.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=multi.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + return c +end +function multi:newConnection() + local c={} + c.Type='connector' + c.func={} + c.ID=0 + function c:Fire(...) + for i=#self.func,1,-1 do + t,e=pcall(self.func[i][1],...) + if not(t) then + print(e) + end + end + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func) + self.ID=self.ID+1 + table.insert(self.func,{func,self.ID}) + return { + Link=self.func, + ID=self.ID, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',multi.clock()) + while self.Active do + self:Do_Order() + end +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',multi.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + self:Do_Order() + end +end diff --git a/oldversions/MultiManager(1.0.0).lua b/oldversions/MultiManager(1.0.0).lua index efd1991..2900808 100644 --- a/oldversions/MultiManager(1.0.0).lua +++ b/oldversions/MultiManager(1.0.0).lua @@ -1,1375 +1,1375 @@ -if not bin then - print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') -end -if table.unpack then - unpack=table.unpack -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={'A',0,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 19 Objects: -+Events -+Alarms -+Loops -+Steps -+TSteps -+Triggers -+Tasks -+Connections -+Timers -+Jobs -+Interfaces -+Conditions -+Ranges -+Threads -+Functions -+Queuers -+Updaters -+Watchers -+CustomObjects - -Constructors [Runners] ----------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) -intObj=multi:newProcessor([string: FILE defualt: nil]) -intObj=multi:newQueuer([string: FILE defualt: nil]) - -Constructors [ACTORS] ---------------------- Note: everything is a multiObj! -eventObj=multi:newEvent([function: TASK defualt: function() end]) -alarmObj=multi:newAlarm([number: SET defualt: 0]) -loopObj=multi:newLoop([function: FUNC]) -stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -updaterObj=multi:newUpdater([number: SKIP defualt: 0]) -watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) -multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) -void=multi:newThread(string: name,function: func) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob(function: func,[string: name]) -multi:newRange(number: a,number: b,[number: c]) -multi:newCondition(func) - -Constructors [NON-ACTORS] -------------------------- -multi:newTrigger(function: func) -multi:newTask(function: func) -multi:newConnection() -multi:newTimer() -multi:newFunction(function: func) -]] -multi.changelog=[[Changelog starts at Version A.0.0 - -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.ender={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.fps=60 -multi.Id=-1 -multi.Type='mainprocess' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.queuefinal=function(self) - self:Destroy() - if self.Parent.Mainloop[#self.Parent.Mainloop] then - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - else - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end -end ---Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) -multi.Priority_Core=1 -multi.Priority_High=4 -multi.Priority_Above_Normal=16 -multi.Priority_Normal=64 -multi.Priority_Below_Normal=256 -multi.Priority_Low=1024 -multi.Priority_Idle=4096 -multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} -multi.PStep=1 ---^^^^ -multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise -multi.Priority=multi.Priority_Core -function multi:setDomainName(name) - self[name]={} -end -function multi:linkDomain(name) - return self[name] -end -function multi:_Pause() - self.Active=false -end -function multi:setPriority(s) - if type(s)==number then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='an' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='bn' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - end -end --- System -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end -function multi:Stop() - self.Active=false -end -function multi:condition(cond) - if not self.CD then - self:Pause() - self.held=true - self.CD=cond.condition - elseif not(cond.condition()) then - self.held=false - self:Resume() - self.CD=nil - return false - end - self.Parent:Do_Order() - return true -end -function multi:isHeld() - return self.held -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:waitFor(obj) - self:hold(function() return obj:isActive() end) -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:enablePriority() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>7 then - PS.PStep=1 - end - end -end -function multi:enablePriority2() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=1 - end - end -end -multi.disablePriority=multi.unProtect -function multi:fromfile(path,int) - int=int or self - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec,p) - local temp=self:newLoop(function(t,self) - if self.clock()-self.init>self.sec then - print(self.c..' steps in '..self.sec..' second(s)') - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - temp.Priority=p or 1 - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=self.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('process') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi.startFPSMonitior() - if not multi.runFPS then - multi.doFPS(s) - multi.runFPS=true - end -end -function multi.doFPS(s) - multi:benchMark(1):OnBench(doFPS) - if s then - multi.fps=s - end -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - self.CID=_D - if Loop[_D].Active then - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and Loop[_D].error=="" then - Loop[_D].error=err - self.OnError:Fire(err,Loop[_D]) - end - end - end - end - end -end -function multi:unProtect() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") - self:OnBreak(func) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:isActive() - return self.Active -end -function multi:getType() - return self.Type -end -function multi:Sleep(n) - self:hold(n) -end -function multi:Pause() - if self.Type=='mainprocess' then - print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - end -end -function multi:Resume() - if self.Type=='process' or self.Type=='mainprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='process' or self.Type=='mainprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end - -function multi:hold(task) - self:Pause() - self.held=true - if type(task)=='number' then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - self.held=false - elseif type(task)=='function' then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt.Active=false end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - self.held=false - else - print('Error Data Type!!!') - end -end -function multi:oneTime(func,...) - if not(self.Type=='mainprocess' or self.Type=='process') then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end -function multi:create(ref) - multi.OnObjectCreated:Fire(ref) -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' or self.Type=='queue' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - c.error="" - c.held=false - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newProcess(file) - if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type='process' - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Pause() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - self.Cself=c - loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end -function multi:newQueuer(file) - local c=self:newProcess() - c.Type='queue' - c.last={} - c.funcE={} - function c:OnQueueCompleted(func) - table.insert(self.funcE,func) - end - if file then - self.Cself=c - loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end ---Constructors [ACTORS] -function multi:newCustomObject(objRef,t) - local c={} - if t=='process' then - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") - else - c=self:newBase() - end - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newEvent(task) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='event' - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='alarm' - c.Priority=self.Priority_Low - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=='queue' then - c:Pause() - c:connectFinal(multi.queuefinal) - else - c.timer:Start() - end - self:create(c) - return c -end -function multi:newLoop(func) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='loop' - c.Start=self.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](self.Parent.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - function c:setSkip(n) - self.skip=n - end - c.OnUpdate=self.OnMainConnect - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.Active then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='tstep' - c.Priority=self.Priority_Low - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=self.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=self.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=self.clock() - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end -function multi:newThread(name,func) - local c={} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.firstRunDone=false - c.timer=multi.scheduler:newTimer() - c.ref.Globals=self:linkDomain("Globals") - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - self:syncGlobals(ret) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - err=coroutine.yield({"_kill_"}) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - n = tonumber(n) or 0 - ret=coroutine.yield({"_sleep_",n}) - self:syncGlobals(ret) - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(self:linkDomain("Threads"),c) - if not multi.scheduler:isActive() then - multi.scheduler:Resume() - end -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:newRange() - selflink=self - local temp={ - getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, - } - setmetatable(temp,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=selflink.Parent.Mainloop[selflink.CID] - self.Link:Pause() - self.Link.held=true - return function() return self:getN() end - end - }) - self:create(temp) - return temp -end -function multi:newCondition(func) - local c={['condition']=func} - self:create(c) - return c -end --- Constructors [NON-ACTORS] -function multi:newFunction(func) - local c={} - c.func=func - mt={ - __index=multi, - __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end - } - c.Parent=self - function c:Pause() - self.Active=false - end - function c:Resume() - self.Active=true - end - setmetatable(c,mt) - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=os.clock() - end - function c:Get() - return (os.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=os.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newConnection(protect) - local c={} - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - function c:Fire(...) - local ret={} - for i=#self.func,1,-1 do - if self.protect then - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - print(temp[2]) - end - else - table.insert(ret,{self.func[i][1](...)}) - end - end - return ret - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func) - self.ID=self.ID+1 - table.insert(self.func,1,{func,self.ID}) - return { - Link=self.func, - ID=self.ID, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',self.clock()) - while self.Active do - self:Do_Order() - end - print("Did you call multi:_Pause()? This method should not be used when using multi:mainloop()! There is now no way to restart the multiManager, except by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',self.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - function self:uManager(dt) - self:Do_Order() - end - self:Do_Order() - end -end ---Thread Setup Stuff -multi:setDomainName("Threads") -multi:setDomainName("Globals") --- Scheduler -multi.scheduler=multi:newUpdater() -multi.scheduler.Type="scheduler" -function multi.scheduler:setStep(n) - self.skip=tonumber(n) or 24 -end -multi.scheduler.Threads=multi:linkDomain("Threads") -multi.scheduler.Globals=multi:linkDomain("Globals") -multi.scheduler:OnUpdate(function(self) - for i=#self.Threads,1,-1 do - ret={} - if coroutine.status(self.Threads[i].thread)=="dead" then - table.remove(self.Threads,i) - else - if self.Threads[i].timer:Get()>=self.Threads[i].sleep then - if self.Threads[i].firstRunDone==false then - self.Threads[i].firstRunDone=true - self.Threads[i].timer:Start() - _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) - else - _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) - end - if ret==true or ret==false then - print("Thread Ended!!!") - ret={} - end - end - if ret then - if ret[1]=="_kill_" then - table.remove(self.Threads,i) - elseif ret[1]=="_sleep_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=ret[2] - elseif ret.Name then - self.Globals[ret.Name]=ret.Value - end - end - end - end -end) -multi.scheduler:setStep() -multi.scheduler:Pause() -multi.OnError=multi:newConnection() +if not bin then + print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') +end +if table.unpack then + unpack=table.unpack +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={'A',0,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 19 Objects: ++Events ++Alarms ++Loops ++Steps ++TSteps ++Triggers ++Tasks ++Connections ++Timers ++Jobs ++Interfaces ++Conditions ++Ranges ++Threads ++Functions ++Queuers ++Updaters ++Watchers ++CustomObjects + +Constructors [Runners] +---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) +intObj=multi:newProcessor([string: FILE defualt: nil]) +intObj=multi:newQueuer([string: FILE defualt: nil]) + +Constructors [ACTORS] +--------------------- Note: everything is a multiObj! +eventObj=multi:newEvent([function: TASK defualt: function() end]) +alarmObj=multi:newAlarm([number: SET defualt: 0]) +loopObj=multi:newLoop([function: FUNC]) +stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +updaterObj=multi:newUpdater([number: SKIP defualt: 0]) +watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) +multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) +void=multi:newThread(string: name,function: func) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob(function: func,[string: name]) +multi:newRange(number: a,number: b,[number: c]) +multi:newCondition(func) + +Constructors [NON-ACTORS] +------------------------- +multi:newTrigger(function: func) +multi:newTask(function: func) +multi:newConnection() +multi:newTimer() +multi:newFunction(function: func) +]] +multi.changelog=[[Changelog starts at Version A.0.0 + +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.ender={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.fps=60 +multi.Id=-1 +multi.Type='mainprocess' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.queuefinal=function(self) + self:Destroy() + if self.Parent.Mainloop[#self.Parent.Mainloop] then + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + else + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end +end +--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) +multi.Priority_Core=1 +multi.Priority_High=4 +multi.Priority_Above_Normal=16 +multi.Priority_Normal=64 +multi.Priority_Below_Normal=256 +multi.Priority_Low=1024 +multi.Priority_Idle=4096 +multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} +multi.PStep=1 +--^^^^ +multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise +multi.Priority=multi.Priority_Core +function multi:setDomainName(name) + self[name]={} +end +function multi:linkDomain(name) + return self[name] +end +function multi:_Pause() + self.Active=false +end +function multi:setPriority(s) + if type(s)==number then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='an' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='bn' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + end +end +-- System +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi:getParentProcess() + return self.Mainloop[self.CID] +end +function multi:Stop() + self.Active=false +end +function multi:condition(cond) + if not self.CD then + self:Pause() + self.held=true + self.CD=cond.condition + elseif not(cond.condition()) then + self.held=false + self:Resume() + self.CD=nil + return false + end + self.Parent:Do_Order() + return true +end +function multi:isHeld() + return self.held +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:waitFor(obj) + self:hold(function() return obj:isActive() end) +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:enablePriority() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>7 then + PS.PStep=1 + end + end +end +function multi:enablePriority2() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PStep)%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=1 + end + end +end +multi.disablePriority=multi.unProtect +function multi:fromfile(path,int) + int=int or self + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec,p) + local temp=self:newLoop(function(t,self) + if self.clock()-self.init>self.sec then + print(self.c..' steps in '..self.sec..' second(s)') + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + temp.Priority=p or 1 + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=self.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('process') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi.startFPSMonitior() + if not multi.runFPS then + multi.doFPS(s) + multi.runFPS=true + end +end +function multi.doFPS(s) + multi:benchMark(1):OnBench(doFPS) + if s then + multi.fps=s + end +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + self.CID=_D + if Loop[_D].Active then + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and Loop[_D].error=="" then + Loop[_D].error=err + self.OnError:Fire(err,Loop[_D]) + end + end + end + end + end +end +function multi:unProtect() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + else + print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") + self:OnBreak(func) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + if self.ender[i] then + self.ender[i](self) + end + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:isActive() + return self.Active +end +function multi:getType() + return self.Type +end +function multi:Sleep(n) + self:hold(n) +end +function multi:Pause() + if self.Type=='mainprocess' then + print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + end +end +function multi:Resume() + if self.Type=='process' or self.Type=='mainprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='process' or self.Type=='mainprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end + +function multi:hold(task) + self:Pause() + self.held=true + if type(task)=='number' then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + self.held=false + elseif type(task)=='function' then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt.Active=false end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + self.held=false + else + print('Error Data Type!!!') + end +end +function multi:oneTime(func,...) + if not(self.Type=='mainprocess' or self.Type=='process') then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +function multi:create(ref) + multi.OnObjectCreated:Fire(ref) +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' or self.Type=='queue' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + c.error="" + c.held=false + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newProcess(file) + if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type='process' + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Pause() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + self.Cself=c + loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +function multi:newQueuer(file) + local c=self:newProcess() + c.Type='queue' + c.last={} + c.funcE={} + function c:OnQueueCompleted(func) + table.insert(self.funcE,func) + end + if file then + self.Cself=c + loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +--Constructors [ACTORS] +function multi:newCustomObject(objRef,t) + local c={} + if t=='process' then + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") + else + c=self:newBase() + end + if type(objRef)=='table' then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type='coustomObject' + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newEvent(task) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='event' + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='alarm' + c.Priority=self.Priority_Low + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=='queue' then + c:Pause() + c:connectFinal(multi.queuefinal) + else + c.timer:Start() + end + self:create(c) + return c +end +function multi:newLoop(func) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='loop' + c.Start=self.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](self.Parent.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=self.skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + function c:setSkip(n) + self.skip=n + end + c.OnUpdate=self.OnMainConnect + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.Active then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='tstep' + c.Priority=self.Priority_Low + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=self.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=self.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=self.clock() + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + if self.Type=='queue' then + print("Cannot create a watcher on a queue! Creating on 'multi' instead!") + self=multi + end + local c=self:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + self:create(c) + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +function multi:newThread(name,func) + local c={} + c.ref={} + c.Name=name + c.thread=coroutine.create(func) + c.sleep=1 + c.firstRunDone=false + c.timer=multi.scheduler:newTimer() + c.ref.Globals=self:linkDomain("Globals") + function c.ref:send(name,val) + ret=coroutine.yield({Name=name,Value=val}) + self:syncGlobals(ret) + end + function c.ref:get(name) + return self.Globals[name] + end + function c.ref:kill() + err=coroutine.yield({"_kill_"}) + if err then + error("Failed to kill a thread! Exiting...") + end + end + function c.ref:sleep(n) + n = tonumber(n) or 0 + ret=coroutine.yield({"_sleep_",n}) + self:syncGlobals(ret) + end + function c.ref:syncGlobals(v) + self.Globals=v + end + table.insert(self:linkDomain("Threads"),c) + if not multi.scheduler:isActive() then + multi.scheduler:Resume() + end +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:newRange() + selflink=self + local temp={ + getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, + } + setmetatable(temp,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=selflink.Parent.Mainloop[selflink.CID] + self.Link:Pause() + self.Link.held=true + return function() return self:getN() end + end + }) + self:create(temp) + return temp +end +function multi:newCondition(func) + local c={['condition']=func} + self:create(c) + return c +end +-- Constructors [NON-ACTORS] +function multi:newFunction(func) + local c={} + c.func=func + mt={ + __index=multi, + __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end + } + c.Parent=self + function c:Pause() + self.Active=false + end + function c:Resume() + self.Active=true + end + setmetatable(c,mt) + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=os.clock() + end + function c:Get() + return (os.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=os.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newConnection(protect) + local c={} + c.Type='connector' + c.func={} + c.ID=0 + c.protect=protect or true + function c:Fire(...) + local ret={} + for i=#self.func,1,-1 do + if self.protect then + local temp={pcall(self.func[i][1],...)} + if temp[1] then + table.remove(temp,1) + table.insert(ret,temp) + else + print(temp[2]) + end + else + table.insert(ret,{self.func[i][1](...)}) + end + end + return ret + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func) + self.ID=self.ID+1 + table.insert(self.func,1,{func,self.ID}) + return { + Link=self.func, + ID=self.ID, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +multi.OnObjectCreated=multi:newConnection() +multi.OnObjectDestroyed=multi:newConnection() +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',self.clock()) + while self.Active do + self:Do_Order() + end + print("Did you call multi:_Pause()? This method should not be used when using multi:mainloop()! There is now no way to restart the multiManager, except by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',self.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + function self:uManager(dt) + self:Do_Order() + end + self:Do_Order() + end +end +--Thread Setup Stuff +multi:setDomainName("Threads") +multi:setDomainName("Globals") +-- Scheduler +multi.scheduler=multi:newUpdater() +multi.scheduler.Type="scheduler" +function multi.scheduler:setStep(n) + self.skip=tonumber(n) or 24 +end +multi.scheduler.Threads=multi:linkDomain("Threads") +multi.scheduler.Globals=multi:linkDomain("Globals") +multi.scheduler:OnUpdate(function(self) + for i=#self.Threads,1,-1 do + ret={} + if coroutine.status(self.Threads[i].thread)=="dead" then + table.remove(self.Threads,i) + else + if self.Threads[i].timer:Get()>=self.Threads[i].sleep then + if self.Threads[i].firstRunDone==false then + self.Threads[i].firstRunDone=true + self.Threads[i].timer:Start() + _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) + else + _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) + end + if ret==true or ret==false then + print("Thread Ended!!!") + ret={} + end + end + if ret then + if ret[1]=="_kill_" then + table.remove(self.Threads,i) + elseif ret[1]=="_sleep_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=ret[2] + elseif ret.Name then + self.Globals[ret.Name]=ret.Value + end + end + end + end +end) +multi.scheduler:setStep() +multi.scheduler:Pause() +multi.OnError=multi:newConnection() diff --git a/oldversions/MultiManager(1.0.1).lua b/oldversions/MultiManager(1.0.1).lua index d3a2dd2..0da8be3 100644 --- a/oldversions/MultiManager(1.0.1).lua +++ b/oldversions/MultiManager(1.0.1).lua @@ -1,1391 +1,1391 @@ -if not bin then - print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') -end -if table.unpack then - unpack=table.unpack -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={'A',0,1}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me -+Events #7 -+Alarms #2 -+Loops #3 -+Steps #4 -+TSteps #6 -+Triggers #16 -+Tasks #12 -+Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library -+Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent -+Jobs #11 -+Process #10 -+Conditions #15 -+Ranges #8 -+Threads #13 -+Functions #5 -+Queuers #17 -+Updaters #9 -+Watchers #18 -+CustomObjects #19 - -Constructors [Runners] ----------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) -intObj=multi:newProcess([string: FILE defualt: nil]) -intObj=multi:newQueuer([string: FILE defualt: nil]) - -Constructors [ACTORS] ---------------------- Note: everything is a multiObj! -eventObj=multi:newEvent([function: TASK defualt: function() end]) -alarmObj=multi:newAlarm([number: SET defualt: 0]) -loopObj=multi:newLoop([function: FUNC]) -stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -updaterObj=multi:newUpdater([number: SKIP defualt: 0]) -watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) -multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) -void=multi:newThread(string: name,function: func) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob(function: func,[string: name]) -multi:newRange(number: a,number: b,[number: c]) -multi:newCondition(func) - -Constructors [NON-ACTORS] -------------------------- -multi:newTrigger(function: func) -multi:newTask(function: func) -multi:newConnection() -multi:newTimer() -multi:newFunction(function: func) -]] -multi.changelog=[[Changelog starts at Version A.0.0 -New in A.0.0 - Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment - version.major.minor -New in A.1.0 - Changed: multi:newConnection(protect) method - Changed the way you are able to interact with it by adding the __call metamethod - Old usage: - - OnUpdate=multi:newConnection() - OnUpdate:connect(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) - - New usage: notice that connect is no longer needed! Both ways still work! and always will work :) - - OnUpdate=multi:newConnection() - OnUpdate(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.ender={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.fps=60 -multi.Id=-1 -multi.Type='mainprocess' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.queuefinal=function(self) - self:Destroy() - if self.Parent.Mainloop[#self.Parent.Mainloop] then - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - else - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end -end ---Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) -multi.Priority_Core=1 -multi.Priority_High=4 -multi.Priority_Above_Normal=16 -multi.Priority_Normal=64 -multi.Priority_Below_Normal=256 -multi.Priority_Low=1024 -multi.Priority_Idle=4096 -multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} -multi.PStep=1 ---^^^^ -multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise -multi.Priority=multi.Priority_Core -function multi:setDomainName(name) - self[name]={} -end -function multi:linkDomain(name) - return self[name] -end -function multi:_Pause() - self.Active=false -end -function multi:setPriority(s) - if type(s)==number then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='an' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='bn' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - end -end --- System -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end -function multi:Stop() - self.Active=false -end -function multi:condition(cond) - if not self.CD then - self:Pause() - self.held=true - self.CD=cond.condition - elseif not(cond.condition()) then - self.held=false - self:Resume() - self.CD=nil - return false - end - self.Parent:Do_Order() - return true -end -function multi:isHeld() - return self.held -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:waitFor(obj) - self:hold(function() return obj:isActive() end) -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:enablePriority() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>7 then - PS.PStep=1 - end - end -end -function multi:enablePriority2() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=1 - end - end -end -multi.disablePriority=multi.unProtect -function multi:fromfile(path,int) - int=int or self - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec,p) - local temp=self:newLoop(function(t,self) - if self.clock()-self.init>self.sec then - print(self.c..' steps in '..self.sec..' second(s)') - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - temp.Priority=p or 1 - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=self.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('process') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi.startFPSMonitior() - if not multi.runFPS then - multi.doFPS(s) - multi.runFPS=true - end -end -function multi.doFPS(s) - multi:benchMark(1):OnBench(doFPS) - if s then - multi.fps=s - end -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - self.CID=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - self.OnError:Fire(err,Loop[_D]) - end - end - end - end -end -function multi:unProtect() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") - self:OnBreak(func) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:isActive() - return self.Active -end -function multi:getType() - return self.Type -end -function multi:Sleep(n) - self:hold(n) -end -function multi:Pause() - if self.Type=='mainprocess' then - print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - end -end -function multi:Resume() - if self.Type=='process' or self.Type=='mainprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='process' or self.Type=='mainprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end - -function multi:hold(task) - self:Pause() - self.held=true - if type(task)=='number' then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - self.held=false - elseif type(task)=='function' then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt.Active=false end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - self.held=false - else - print('Error Data Type!!!') - end -end -function multi:oneTime(func,...) - if not(self.Type=='mainprocess' or self.Type=='process') then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end -function multi:create(ref) - multi.OnObjectCreated:Fire(ref) -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' or self.Type=='queue' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - c.held=false - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newProcess(file) - if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type='process' - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Pause() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - self.Cself=c - loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end -function multi:newQueuer(file) - local c=self:newProcess() - c.Type='queue' - c.last={} - c.funcE={} - function c:OnQueueCompleted(func) - table.insert(self.funcE,func) - end - if file then - self.Cself=c - loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end ---Constructors [ACTORS] -function multi:newCustomObject(objRef,t) - local c={} - if t=='process' then - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") - else - c=self:newBase() - end - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newEvent(task) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='event' - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='alarm' - c.Priority=self.Priority_Low - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=='queue' then - c:Pause() - c:connectFinal(multi.queuefinal) - else - c.timer:Start() - end - self:create(c) - return c -end -function multi:newLoop(func) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='loop' - c.Start=self.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](self.Parent.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - function c:setSkip(n) - self.skip=n - end - c.OnUpdate=self.OnMainConnect - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='tstep' - c.Priority=self.Priority_Low - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=self.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=self.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=self.clock() - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end -function multi:newThread(name,func) - local c={} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.firstRunDone=false - c.timer=multi.scheduler:newTimer() - c.ref.Globals=self:linkDomain("Globals") - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - self:syncGlobals(ret) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - err=coroutine.yield({"_kill_"}) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - n = tonumber(n) or 0 - ret=coroutine.yield({"_sleep_",n}) - self:syncGlobals(ret) - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(self:linkDomain("Threads"),c) - if not multi.scheduler:isActive() then - multi.scheduler:Resume() - end -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:newRange() - selflink=self - local temp={ - getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, - } - setmetatable(temp,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=selflink.Parent.Mainloop[selflink.CID] - self.Link:Pause() - self.Link.held=true - return function() return self:getN() end - end - }) - self:create(temp) - return temp -end -function multi:newCondition(func) - local c={['condition']=func} - self:create(c) - return c -end --- Constructors [NON-ACTORS] -function multi:newFunction(func) - local c={} - c.func=func - mt={ - __index=multi, - __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end - } - c.Parent=self - function c:Pause() - self.Active=false - end - function c:Resume() - self.Active=true - end - setmetatable(c,mt) - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=os.clock() - end - function c:Get() - return (os.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=os.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newConnection(protect) - local c={} - setmetatable(c,{__call=function(self,...) self:connect(...) end}) - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - function c:Fire(...) - local ret={} - for i=#self.func,1,-1 do - if self.protect then - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - print(temp[2]) - end - else - table.insert(ret,{self.func[i][1](...)}) - end - end - return ret - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func) - self.ID=self.ID+1 - table.insert(self.func,1,{func,self.ID}) - return { - Link=self.func, - ID=self.ID, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',self.clock()) - while self.Active do - self:Do_Order() - end - print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',self.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - function self:uManager(dt) - self:Do_Order() - end - self:Do_Order() - end -end ---Thread Setup Stuff -multi:setDomainName("Threads") -multi:setDomainName("Globals") --- Scheduler -multi.scheduler=multi:newUpdater() -multi.scheduler.Type="scheduler" -function multi.scheduler:setStep(n) - self.skip=tonumber(n) or 24 -end -multi.scheduler.Threads=multi:linkDomain("Threads") -multi.scheduler.Globals=multi:linkDomain("Globals") -multi.scheduler:OnUpdate(function(self) - for i=#self.Threads,1,-1 do - ret={} - if coroutine.status(self.Threads[i].thread)=="dead" then - table.remove(self.Threads,i) - else - if self.Threads[i].timer:Get()>=self.Threads[i].sleep then - if self.Threads[i].firstRunDone==false then - self.Threads[i].firstRunDone=true - self.Threads[i].timer:Start() - _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) - else - _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) - end - if ret==true or ret==false then - print("Thread Ended!!!") - ret={} - end - end - if ret then - if ret[1]=="_kill_" then - table.remove(self.Threads,i) - elseif ret[1]=="_sleep_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=ret[2] - elseif ret.Name then - self.Globals[ret.Name]=ret.Value - end - end - end - end -end) -multi.scheduler:setStep() -multi.scheduler:Pause() -multi.OnError=multi:newConnection() +if not bin then + print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') +end +if table.unpack then + unpack=table.unpack +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={'A',0,1}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me ++Events #7 ++Alarms #2 ++Loops #3 ++Steps #4 ++TSteps #6 ++Triggers #16 ++Tasks #12 ++Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library ++Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent ++Jobs #11 ++Process #10 ++Conditions #15 ++Ranges #8 ++Threads #13 ++Functions #5 ++Queuers #17 ++Updaters #9 ++Watchers #18 ++CustomObjects #19 + +Constructors [Runners] +---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) +intObj=multi:newProcess([string: FILE defualt: nil]) +intObj=multi:newQueuer([string: FILE defualt: nil]) + +Constructors [ACTORS] +--------------------- Note: everything is a multiObj! +eventObj=multi:newEvent([function: TASK defualt: function() end]) +alarmObj=multi:newAlarm([number: SET defualt: 0]) +loopObj=multi:newLoop([function: FUNC]) +stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +updaterObj=multi:newUpdater([number: SKIP defualt: 0]) +watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) +multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) +void=multi:newThread(string: name,function: func) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob(function: func,[string: name]) +multi:newRange(number: a,number: b,[number: c]) +multi:newCondition(func) + +Constructors [NON-ACTORS] +------------------------- +multi:newTrigger(function: func) +multi:newTask(function: func) +multi:newConnection() +multi:newTimer() +multi:newFunction(function: func) +]] +multi.changelog=[[Changelog starts at Version A.0.0 +New in A.0.0 + Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment + version.major.minor +New in A.1.0 + Changed: multi:newConnection(protect) method + Changed the way you are able to interact with it by adding the __call metamethod + Old usage: + + OnUpdate=multi:newConnection() + OnUpdate:connect(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) + + New usage: notice that connect is no longer needed! Both ways still work! and always will work :) + + OnUpdate=multi:newConnection() + OnUpdate(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.ender={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.fps=60 +multi.Id=-1 +multi.Type='mainprocess' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.queuefinal=function(self) + self:Destroy() + if self.Parent.Mainloop[#self.Parent.Mainloop] then + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + else + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end +end +--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) +multi.Priority_Core=1 +multi.Priority_High=4 +multi.Priority_Above_Normal=16 +multi.Priority_Normal=64 +multi.Priority_Below_Normal=256 +multi.Priority_Low=1024 +multi.Priority_Idle=4096 +multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} +multi.PStep=1 +--^^^^ +multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise +multi.Priority=multi.Priority_Core +function multi:setDomainName(name) + self[name]={} +end +function multi:linkDomain(name) + return self[name] +end +function multi:_Pause() + self.Active=false +end +function multi:setPriority(s) + if type(s)==number then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='an' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='bn' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + end +end +-- System +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi:getParentProcess() + return self.Mainloop[self.CID] +end +function multi:Stop() + self.Active=false +end +function multi:condition(cond) + if not self.CD then + self:Pause() + self.held=true + self.CD=cond.condition + elseif not(cond.condition()) then + self.held=false + self:Resume() + self.CD=nil + return false + end + self.Parent:Do_Order() + return true +end +function multi:isHeld() + return self.held +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:waitFor(obj) + self:hold(function() return obj:isActive() end) +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:enablePriority() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>7 then + PS.PStep=1 + end + end +end +function multi:enablePriority2() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PStep)%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=1 + end + end +end +multi.disablePriority=multi.unProtect +function multi:fromfile(path,int) + int=int or self + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec,p) + local temp=self:newLoop(function(t,self) + if self.clock()-self.init>self.sec then + print(self.c..' steps in '..self.sec..' second(s)') + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + temp.Priority=p or 1 + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=self.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('process') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi.startFPSMonitior() + if not multi.runFPS then + multi.doFPS(s) + multi.runFPS=true + end +end +function multi.doFPS(s) + multi:benchMark(1):OnBench(doFPS) + if s then + multi.fps=s + end +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + self.CID=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + self.OnError:Fire(err,Loop[_D]) + end + end + end + end +end +function multi:unProtect() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + else + print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") + self:OnBreak(func) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + if self.ender[i] then + self.ender[i](self) + end + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:isActive() + return self.Active +end +function multi:getType() + return self.Type +end +function multi:Sleep(n) + self:hold(n) +end +function multi:Pause() + if self.Type=='mainprocess' then + print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + end +end +function multi:Resume() + if self.Type=='process' or self.Type=='mainprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='process' or self.Type=='mainprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end + +function multi:hold(task) + self:Pause() + self.held=true + if type(task)=='number' then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + self.held=false + elseif type(task)=='function' then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt.Active=false end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + self.held=false + else + print('Error Data Type!!!') + end +end +function multi:oneTime(func,...) + if not(self.Type=='mainprocess' or self.Type=='process') then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +function multi:create(ref) + multi.OnObjectCreated:Fire(ref) +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' or self.Type=='queue' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + c.held=false + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newProcess(file) + if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type='process' + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Pause() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + self.Cself=c + loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +function multi:newQueuer(file) + local c=self:newProcess() + c.Type='queue' + c.last={} + c.funcE={} + function c:OnQueueCompleted(func) + table.insert(self.funcE,func) + end + if file then + self.Cself=c + loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +--Constructors [ACTORS] +function multi:newCustomObject(objRef,t) + local c={} + if t=='process' then + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") + else + c=self:newBase() + end + if type(objRef)=='table' then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type='coustomObject' + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newEvent(task) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='event' + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='alarm' + c.Priority=self.Priority_Low + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=='queue' then + c:Pause() + c:connectFinal(multi.queuefinal) + else + c.timer:Start() + end + self:create(c) + return c +end +function multi:newLoop(func) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='loop' + c.Start=self.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](self.Parent.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=self.skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + function c:setSkip(n) + self.skip=n + end + c.OnUpdate=self.OnMainConnect + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='tstep' + c.Priority=self.Priority_Low + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=self.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=self.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=self.clock() + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + if self.Type=='queue' then + print("Cannot create a watcher on a queue! Creating on 'multi' instead!") + self=multi + end + local c=self:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + self:create(c) + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +function multi:newThread(name,func) + local c={} + c.ref={} + c.Name=name + c.thread=coroutine.create(func) + c.sleep=1 + c.firstRunDone=false + c.timer=multi.scheduler:newTimer() + c.ref.Globals=self:linkDomain("Globals") + function c.ref:send(name,val) + ret=coroutine.yield({Name=name,Value=val}) + self:syncGlobals(ret) + end + function c.ref:get(name) + return self.Globals[name] + end + function c.ref:kill() + err=coroutine.yield({"_kill_"}) + if err then + error("Failed to kill a thread! Exiting...") + end + end + function c.ref:sleep(n) + n = tonumber(n) or 0 + ret=coroutine.yield({"_sleep_",n}) + self:syncGlobals(ret) + end + function c.ref:syncGlobals(v) + self.Globals=v + end + table.insert(self:linkDomain("Threads"),c) + if not multi.scheduler:isActive() then + multi.scheduler:Resume() + end +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:newRange() + selflink=self + local temp={ + getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, + } + setmetatable(temp,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=selflink.Parent.Mainloop[selflink.CID] + self.Link:Pause() + self.Link.held=true + return function() return self:getN() end + end + }) + self:create(temp) + return temp +end +function multi:newCondition(func) + local c={['condition']=func} + self:create(c) + return c +end +-- Constructors [NON-ACTORS] +function multi:newFunction(func) + local c={} + c.func=func + mt={ + __index=multi, + __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end + } + c.Parent=self + function c:Pause() + self.Active=false + end + function c:Resume() + self.Active=true + end + setmetatable(c,mt) + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=os.clock() + end + function c:Get() + return (os.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=os.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newConnection(protect) + local c={} + setmetatable(c,{__call=function(self,...) self:connect(...) end}) + c.Type='connector' + c.func={} + c.ID=0 + c.protect=protect or true + function c:Fire(...) + local ret={} + for i=#self.func,1,-1 do + if self.protect then + local temp={pcall(self.func[i][1],...)} + if temp[1] then + table.remove(temp,1) + table.insert(ret,temp) + else + print(temp[2]) + end + else + table.insert(ret,{self.func[i][1](...)}) + end + end + return ret + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func) + self.ID=self.ID+1 + table.insert(self.func,1,{func,self.ID}) + return { + Link=self.func, + ID=self.ID, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +multi.OnObjectCreated=multi:newConnection() +multi.OnObjectDestroyed=multi:newConnection() +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',self.clock()) + while self.Active do + self:Do_Order() + end + print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',self.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + function self:uManager(dt) + self:Do_Order() + end + self:Do_Order() + end +end +--Thread Setup Stuff +multi:setDomainName("Threads") +multi:setDomainName("Globals") +-- Scheduler +multi.scheduler=multi:newUpdater() +multi.scheduler.Type="scheduler" +function multi.scheduler:setStep(n) + self.skip=tonumber(n) or 24 +end +multi.scheduler.Threads=multi:linkDomain("Threads") +multi.scheduler.Globals=multi:linkDomain("Globals") +multi.scheduler:OnUpdate(function(self) + for i=#self.Threads,1,-1 do + ret={} + if coroutine.status(self.Threads[i].thread)=="dead" then + table.remove(self.Threads,i) + else + if self.Threads[i].timer:Get()>=self.Threads[i].sleep then + if self.Threads[i].firstRunDone==false then + self.Threads[i].firstRunDone=true + self.Threads[i].timer:Start() + _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) + else + _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) + end + if ret==true or ret==false then + print("Thread Ended!!!") + ret={} + end + end + if ret then + if ret[1]=="_kill_" then + table.remove(self.Threads,i) + elseif ret[1]=="_sleep_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=ret[2] + elseif ret.Name then + self.Globals[ret.Name]=ret.Value + end + end + end + end +end) +multi.scheduler:setStep() +multi.scheduler:Pause() +multi.OnError=multi:newConnection() diff --git a/oldversions/MultiManager(1.2.0).lua b/oldversions/MultiManager(1.2.0).lua index 94f47e5..5df7d75 100644 --- a/oldversions/MultiManager(1.2.0).lua +++ b/oldversions/MultiManager(1.2.0).lua @@ -1,1434 +1,1434 @@ -if not bin then - print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') -end -if table.unpack then - unpack=table.unpack -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={'A',2,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me -+Events #7 -+Alarms #2 -+Loops #3 -+Steps #4 -+TSteps #6 -+Triggers #16 -+Tasks #12 -+Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library -+Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent -+Jobs #11 -+Process #10 -+Conditions #15 -+Ranges #8 -+Threads #13 -+Functions #5 -+Queuers #17 -+Updaters #9 -+Watchers #18 -+CustomObjects #19 - -Constructors [Runners] ----------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) -intObj=multi:newProcess([string: FILE defualt: nil]) -intObj=multi:newQueuer([string: FILE defualt: nil]) - -Constructors [ACTORS] ---------------------- Note: everything is a multiObj! -eventObj=multi:newEvent([function: TASK defualt: function() end]) -alarmObj=multi:newAlarm([number: SET defualt: 0]) -loopObj=multi:newLoop([function: FUNC]) -stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -updaterObj=multi:newUpdater([number: SKIP defualt: 0]) -watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) -multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) -void=multi:newThread(string: name,function: func) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob(function: func,[string: name]) -multi:newRange(number: a,number: b,[number: c]) -multi:newCondition(func) - -Constructors [NON-ACTORS] -------------------------- -multi:newTrigger(function: func) -multi:newTask(function: func) -multi:newConnection() -multi:newTimer() -multi:newFunction(function: func) -]] -multi.changelog=[[Changelog starts at Version A.0.0 -New in A.0.0 - Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment - version.major.minor -New in A.1.0 - Changed: multi:newConnection(protect) method - Changed the way you are able to interact with it by adding the __call metamethod - Old usage: - OnUpdate=multi:newConnection() - OnUpdate:connect(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) - New usage: notice that connect is no longer needed! Both ways still work! and always will work :) - OnUpdate=multi:newConnection() - OnUpdate(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) -New in A.2.0 (12/31/2016) - Added: - connectionobj.getConnection(name) - returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) - if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used - Changed: obj=multi:newConnection() - obj:connect(func,name) and obj(func,name) - Added the name argument to allow indexing specific connection objects... Useful when creating an async library -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.ender={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.fps=60 -multi.Id=-1 -multi.Type='mainprocess' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.queuefinal=function(self) - self:Destroy() - if self.Parent.Mainloop[#self.Parent.Mainloop] then - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - else - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end -end ---Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) -multi.Priority_Core=1 -multi.Priority_High=4 -multi.Priority_Above_Normal=16 -multi.Priority_Normal=64 -multi.Priority_Below_Normal=256 -multi.Priority_Low=1024 -multi.Priority_Idle=4096 -multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} -multi.PStep=1 ---^^^^ -multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise -multi.Priority=multi.Priority_Core -function multi:setDomainName(name) - self[name]={} -end -function multi:linkDomain(name) - return self[name] -end -function multi:_Pause() - self.Active=false -end -function multi:setPriority(s) - if type(s)==number then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='an' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='bn' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - end -end --- System -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end -function multi:Stop() - self.Active=false -end -function multi:condition(cond) - if not self.CD then - self:Pause() - self.held=true - self.CD=cond.condition - elseif not(cond.condition()) then - self.held=false - self:Resume() - self.CD=nil - return false - end - self.Parent:Do_Order() - return true -end -function multi:isHeld() - return self.held -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:waitFor(obj) - self:hold(function() return obj:isActive() end) -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:enablePriority() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>7 then - PS.PStep=1 - end - end -end -function multi:enablePriority2() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=1 - end - end -end -multi.disablePriority=multi.unProtect -function multi:fromfile(path,int) - int=int or self - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec,p) - local temp=self:newLoop(function(t,self) - if self.clock()-self.init>self.sec then - print(self.c..' steps in '..self.sec..' second(s)') - self.tt(self.sec) - self:Destroy() - else - self.c=self.c+1 - end - end) - temp.Priority=p or 1 - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=self.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('process') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi.startFPSMonitior() - if not multi.runFPS then - multi.doFPS(s) - multi.runFPS=true - end -end -function multi.doFPS(s) - multi:benchMark(1):OnBench(doFPS) - if s then - multi.fps=s - end -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - self.CID=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - self.OnError:Fire(err,Loop[_D]) - end - end - end - end -end -function multi:unProtect() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") - self:OnBreak(func) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:isActive() - return self.Active -end -function multi:getType() - return self.Type -end -function multi:Sleep(n) - self:hold(n) -end -function multi:Pause() - if self.Type=='mainprocess' then - print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - end -end -function multi:Resume() - if self.Type=='process' or self.Type=='mainprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='process' or self.Type=='mainprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end - -function multi:hold(task) - self:Pause() - self.held=true - if type(task)=='number' then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - self.held=false - elseif type(task)=='function' then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt.Active=false end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - self.held=false - else - print('Error Data Type!!!') - end -end -function multi:oneTime(func,...) - if not(self.Type=='mainprocess' or self.Type=='process') then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end -function multi:create(ref) - multi.OnObjectCreated:Fire(ref) -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' or self.Type=='queue' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - c.held=false - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newProcess(file) - if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type='process' - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Pause() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - self.Cself=c - loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end -function multi:newQueuer(file) - local c=self:newProcess() - c.Type='queue' - c.last={} - c.funcE={} - function c:OnQueueCompleted(func) - table.insert(self.funcE,func) - end - if file then - self.Cself=c - loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end ---Constructors [ACTORS] -function multi:newCustomObject(objRef,t) - local c={} - if t=='process' then - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") - else - c=self:newBase() - end - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newEvent(task) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='event' - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='alarm' - c.Priority=self.Priority_Low - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=='queue' then - c:Pause() - c:connectFinal(multi.queuefinal) - else - c.timer:Start() - end - self:create(c) - return c -end -function multi:newLoop(func) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='loop' - c.Start=self.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](self.Parent.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - function c:setSkip(n) - self.skip=n - end - c.OnUpdate=self.OnMainConnect - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='tstep' - c.Priority=self.Priority_Low - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=self.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=self.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=self.clock() - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end -function multi:newThread(name,func) - local c={} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.firstRunDone=false - c.timer=multi.scheduler:newTimer() - c.ref.Globals=self:linkDomain("Globals") - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - self:syncGlobals(ret) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - err=coroutine.yield({"_kill_"}) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - n = tonumber(n) or 0 - ret=coroutine.yield({"_sleep_",n}) - self:syncGlobals(ret) - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(self:linkDomain("Threads"),c) - if not multi.scheduler:isActive() then - multi.scheduler:Resume() - end -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:newRange() - local selflink=self - local temp={ - getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, - } - setmetatable(temp,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=selflink--.Parent.Mainloop[selflink.CID] or - self.Link:Pause() - self.Link.held=true - return function() return self:getN() end - end - }) - self:create(temp) - return temp -end -function multi:newCondition(func) - local c={['condition']=func} - self:create(c) - return c -end --- Constructors [NON-ACTORS] -function multi:newFunction(func) - local c={} - c.func=func - mt={ - __index=multi, - __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end - } - c.Parent=self - function c:Pause() - self.Active=false - end - function c:Resume() - self.Active=true - end - setmetatable(c,mt) - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=os.clock() - end - function c:Get() - return (os.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=os.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newConnection(protect) - local c={} - setmetatable(c,{__call=function(self,...) self:connect(...) end}) - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - c.connections={} - c.fconnections={} - c.FC=0 - function c:fConnect(func) - local temp=self:connect(func) - table.insert(self.fconnections,temp) - self.FC=self.FC+1 - end - function c:getConnection(name,ingore) - if ingore then - return self.connections[name] or { - Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore - } - else - return self.connections[name] or self - end - end - function c:Fire(...) - local ret={} - for i=#self.func,1,-1 do - if self.protect then - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - print(temp[2]) - end - else - table.insert(ret,{self.func[i][1](...)}) - end - end - return ret - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func,name) - self.ID=self.ID+1 - table.insert(self.func,1,{func,self.ID}) - local temp = { - Link=self.func, - func=func, - ID=self.ID, - Parent=self, - Fire=function(self,...) - if self.Parent.FC>0 then - for i=1,#self.Parent.FC do - self.Parent.FC[i]:Fire(...) - end - end - if self.Parent.protect then - local t=pcall(self.func,...) - if t then - return t - end - else - return self.func(...) - end - end, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - if name then - self.connections[name]=temp - end - return temp - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',self.clock()) - while self.Active do - self:Do_Order() - end - print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',self.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - function self:uManager(dt) - self:Do_Order() - end - self:Do_Order() - end -end ---Thread Setup Stuff -multi:setDomainName("Threads") -multi:setDomainName("Globals") --- Scheduler -multi.scheduler=multi:newUpdater() -multi.scheduler.Type="scheduler" -function multi.scheduler:setStep(n) - self.skip=tonumber(n) or 24 -end -multi.scheduler.Threads=multi:linkDomain("Threads") -multi.scheduler.Globals=multi:linkDomain("Globals") -multi.scheduler:OnUpdate(function(self) - for i=#self.Threads,1,-1 do - ret={} - if coroutine.status(self.Threads[i].thread)=="dead" then - table.remove(self.Threads,i) - else - if self.Threads[i].timer:Get()>=self.Threads[i].sleep then - if self.Threads[i].firstRunDone==false then - self.Threads[i].firstRunDone=true - self.Threads[i].timer:Start() - _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) - else - _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) - end - if ret==true or ret==false then - print("Thread Ended!!!") - ret={} - end - end - if ret then - if ret[1]=="_kill_" then - table.remove(self.Threads,i) - elseif ret[1]=="_sleep_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=ret[2] - elseif ret.Name then - self.Globals[ret.Name]=ret.Value - end - end - end - end -end) -multi.scheduler:setStep() -multi.scheduler:Pause() -multi.OnError=multi:newConnection() +if not bin then + print('Warning the \'bin\' library wasn\'t required! multi:tofile(path) and the multi:fromfile(path,int) features will not work!') +end +if table.unpack then + unpack=table.unpack +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={'A',2,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me ++Events #7 ++Alarms #2 ++Loops #3 ++Steps #4 ++TSteps #6 ++Triggers #16 ++Tasks #12 ++Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library ++Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent ++Jobs #11 ++Process #10 ++Conditions #15 ++Ranges #8 ++Threads #13 ++Functions #5 ++Queuers #17 ++Updaters #9 ++Watchers #18 ++CustomObjects #19 + +Constructors [Runners] +---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) +intObj=multi:newProcess([string: FILE defualt: nil]) +intObj=multi:newQueuer([string: FILE defualt: nil]) + +Constructors [ACTORS] +--------------------- Note: everything is a multiObj! +eventObj=multi:newEvent([function: TASK defualt: function() end]) +alarmObj=multi:newAlarm([number: SET defualt: 0]) +loopObj=multi:newLoop([function: FUNC]) +stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +updaterObj=multi:newUpdater([number: SKIP defualt: 0]) +watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) +multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) +void=multi:newThread(string: name,function: func) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob(function: func,[string: name]) +multi:newRange(number: a,number: b,[number: c]) +multi:newCondition(func) + +Constructors [NON-ACTORS] +------------------------- +multi:newTrigger(function: func) +multi:newTask(function: func) +multi:newConnection() +multi:newTimer() +multi:newFunction(function: func) +]] +multi.changelog=[[Changelog starts at Version A.0.0 +New in A.0.0 + Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment + version.major.minor +New in A.1.0 + Changed: multi:newConnection(protect) method + Changed the way you are able to interact with it by adding the __call metamethod + Old usage: + OnUpdate=multi:newConnection() + OnUpdate:connect(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) + New usage: notice that connect is no longer needed! Both ways still work! and always will work :) + OnUpdate=multi:newConnection() + OnUpdate(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) +New in A.2.0 (12/31/2016) + Added: + connectionobj.getConnection(name) + returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) + if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + Changed: obj=multi:newConnection() + obj:connect(func,name) and obj(func,name) + Added the name argument to allow indexing specific connection objects... Useful when creating an async library +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.ender={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.fps=60 +multi.Id=-1 +multi.Type='mainprocess' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.queuefinal=function(self) + self:Destroy() + if self.Parent.Mainloop[#self.Parent.Mainloop] then + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + else + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end +end +--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) +multi.Priority_Core=1 +multi.Priority_High=4 +multi.Priority_Above_Normal=16 +multi.Priority_Normal=64 +multi.Priority_Below_Normal=256 +multi.Priority_Low=1024 +multi.Priority_Idle=4096 +multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} +multi.PStep=1 +--^^^^ +multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise +multi.Priority=multi.Priority_Core +function multi:setDomainName(name) + self[name]={} +end +function multi:linkDomain(name) + return self[name] +end +function multi:_Pause() + self.Active=false +end +function multi:setPriority(s) + if type(s)==number then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='an' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='bn' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + end +end +-- System +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi:getParentProcess() + return self.Mainloop[self.CID] +end +function multi:Stop() + self.Active=false +end +function multi:condition(cond) + if not self.CD then + self:Pause() + self.held=true + self.CD=cond.condition + elseif not(cond.condition()) then + self.held=false + self:Resume() + self.CD=nil + return false + end + self.Parent:Do_Order() + return true +end +function multi:isHeld() + return self.held +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:waitFor(obj) + self:hold(function() return obj:isActive() end) +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:enablePriority() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>7 then + PS.PStep=1 + end + end +end +function multi:enablePriority2() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PStep)%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=1 + end + end +end +multi.disablePriority=multi.unProtect +function multi:fromfile(path,int) + int=int or self + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec,p) + local temp=self:newLoop(function(t,self) + if self.clock()-self.init>self.sec then + print(self.c..' steps in '..self.sec..' second(s)') + self.tt(self.sec) + self:Destroy() + else + self.c=self.c+1 + end + end) + temp.Priority=p or 1 + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=self.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('process') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi.startFPSMonitior() + if not multi.runFPS then + multi.doFPS(s) + multi.runFPS=true + end +end +function multi.doFPS(s) + multi:benchMark(1):OnBench(doFPS) + if s then + multi.fps=s + end +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + self.CID=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + self.OnError:Fire(err,Loop[_D]) + end + end + end + end +end +function multi:unProtect() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + else + print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") + self:OnBreak(func) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + if self.ender[i] then + self.ender[i](self) + end + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:isActive() + return self.Active +end +function multi:getType() + return self.Type +end +function multi:Sleep(n) + self:hold(n) +end +function multi:Pause() + if self.Type=='mainprocess' then + print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + end +end +function multi:Resume() + if self.Type=='process' or self.Type=='mainprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='process' or self.Type=='mainprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end + +function multi:hold(task) + self:Pause() + self.held=true + if type(task)=='number' then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + self.held=false + elseif type(task)=='function' then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt.Active=false end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + self.held=false + else + print('Error Data Type!!!') + end +end +function multi:oneTime(func,...) + if not(self.Type=='mainprocess' or self.Type=='process') then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +function multi:create(ref) + multi.OnObjectCreated:Fire(ref) +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' or self.Type=='queue' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + c.held=false + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newProcess(file) + if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type='process' + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Pause() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + self.Cself=c + loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +function multi:newQueuer(file) + local c=self:newProcess() + c.Type='queue' + c.last={} + c.funcE={} + function c:OnQueueCompleted(func) + table.insert(self.funcE,func) + end + if file then + self.Cself=c + loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +--Constructors [ACTORS] +function multi:newCustomObject(objRef,t) + local c={} + if t=='process' then + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") + else + c=self:newBase() + end + if type(objRef)=='table' then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type='coustomObject' + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newEvent(task) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='event' + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='alarm' + c.Priority=self.Priority_Low + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=='queue' then + c:Pause() + c:connectFinal(multi.queuefinal) + else + c.timer:Start() + end + self:create(c) + return c +end +function multi:newLoop(func) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='loop' + c.Start=self.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](self.Parent.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=self.skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + function c:setSkip(n) + self.skip=n + end + c.OnUpdate=self.OnMainConnect + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='tstep' + c.Priority=self.Priority_Low + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=self.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=self.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=self.clock() + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + if self.Type=='queue' then + print("Cannot create a watcher on a queue! Creating on 'multi' instead!") + self=multi + end + local c=self:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + self:create(c) + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +function multi:newThread(name,func) + local c={} + c.ref={} + c.Name=name + c.thread=coroutine.create(func) + c.sleep=1 + c.firstRunDone=false + c.timer=multi.scheduler:newTimer() + c.ref.Globals=self:linkDomain("Globals") + function c.ref:send(name,val) + ret=coroutine.yield({Name=name,Value=val}) + self:syncGlobals(ret) + end + function c.ref:get(name) + return self.Globals[name] + end + function c.ref:kill() + err=coroutine.yield({"_kill_"}) + if err then + error("Failed to kill a thread! Exiting...") + end + end + function c.ref:sleep(n) + n = tonumber(n) or 0 + ret=coroutine.yield({"_sleep_",n}) + self:syncGlobals(ret) + end + function c.ref:syncGlobals(v) + self.Globals=v + end + table.insert(self:linkDomain("Threads"),c) + if not multi.scheduler:isActive() then + multi.scheduler:Resume() + end +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:newRange() + local selflink=self + local temp={ + getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, + } + setmetatable(temp,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=selflink--.Parent.Mainloop[selflink.CID] or + self.Link:Pause() + self.Link.held=true + return function() return self:getN() end + end + }) + self:create(temp) + return temp +end +function multi:newCondition(func) + local c={['condition']=func} + self:create(c) + return c +end +-- Constructors [NON-ACTORS] +function multi:newFunction(func) + local c={} + c.func=func + mt={ + __index=multi, + __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end + } + c.Parent=self + function c:Pause() + self.Active=false + end + function c:Resume() + self.Active=true + end + setmetatable(c,mt) + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=os.clock() + end + function c:Get() + return (os.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=os.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newConnection(protect) + local c={} + setmetatable(c,{__call=function(self,...) self:connect(...) end}) + c.Type='connector' + c.func={} + c.ID=0 + c.protect=protect or true + c.connections={} + c.fconnections={} + c.FC=0 + function c:fConnect(func) + local temp=self:connect(func) + table.insert(self.fconnections,temp) + self.FC=self.FC+1 + end + function c:getConnection(name,ingore) + if ingore then + return self.connections[name] or { + Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore + } + else + return self.connections[name] or self + end + end + function c:Fire(...) + local ret={} + for i=#self.func,1,-1 do + if self.protect then + local temp={pcall(self.func[i][1],...)} + if temp[1] then + table.remove(temp,1) + table.insert(ret,temp) + else + print(temp[2]) + end + else + table.insert(ret,{self.func[i][1](...)}) + end + end + return ret + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func,name) + self.ID=self.ID+1 + table.insert(self.func,1,{func,self.ID}) + local temp = { + Link=self.func, + func=func, + ID=self.ID, + Parent=self, + Fire=function(self,...) + if self.Parent.FC>0 then + for i=1,#self.Parent.FC do + self.Parent.FC[i]:Fire(...) + end + end + if self.Parent.protect then + local t=pcall(self.func,...) + if t then + return t + end + else + return self.func(...) + end + end, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + if name then + self.connections[name]=temp + end + return temp + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +multi.OnObjectCreated=multi:newConnection() +multi.OnObjectDestroyed=multi:newConnection() +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',self.clock()) + while self.Active do + self:Do_Order() + end + print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',self.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + function self:uManager(dt) + self:Do_Order() + end + self:Do_Order() + end +end +--Thread Setup Stuff +multi:setDomainName("Threads") +multi:setDomainName("Globals") +-- Scheduler +multi.scheduler=multi:newUpdater() +multi.scheduler.Type="scheduler" +function multi.scheduler:setStep(n) + self.skip=tonumber(n) or 24 +end +multi.scheduler.Threads=multi:linkDomain("Threads") +multi.scheduler.Globals=multi:linkDomain("Globals") +multi.scheduler:OnUpdate(function(self) + for i=#self.Threads,1,-1 do + ret={} + if coroutine.status(self.Threads[i].thread)=="dead" then + table.remove(self.Threads,i) + else + if self.Threads[i].timer:Get()>=self.Threads[i].sleep then + if self.Threads[i].firstRunDone==false then + self.Threads[i].firstRunDone=true + self.Threads[i].timer:Start() + _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) + else + _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) + end + if ret==true or ret==false then + print("Thread Ended!!!") + ret={} + end + end + if ret then + if ret[1]=="_kill_" then + table.remove(self.Threads,i) + elseif ret[1]=="_sleep_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=ret[2] + elseif ret.Name then + self.Globals[ret.Name]=ret.Value + end + end + end + end +end) +multi.scheduler:setStep() +multi.scheduler:Pause() +multi.OnError=multi:newConnection() diff --git a/oldversions/MultiManager(1.3.0).lua b/oldversions/MultiManager(1.3.0).lua index 204ea05..130ec39 100644 --- a/oldversions/MultiManager(1.3.0).lua +++ b/oldversions/MultiManager(1.3.0).lua @@ -1,1460 +1,1460 @@ -if table.unpack then - unpack=table.unpack -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -multi = {} -multi.Version={'A',3,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me -+Events #7 -+Alarms #2 -+Loops #3 -+Steps #4 -+TSteps #6 -+Triggers #16 -+Tasks #12 -+Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library -+Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent -+Jobs #11 -+Process #10 -+Conditions #15 -+Ranges #8 -+Threads #13 -+Functions #5 -+Queuers #17 -+Updaters #9 -+Watchers #18 -+CustomObjects #19 - -Constructors [Runners] ----------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) -intObj=multi:newProcess([string: FILE defualt: nil]) -intObj=multi:newQueuer([string: FILE defualt: nil]) - -Constructors [ACTORS] ---------------------- Note: everything is a multiObj! -eventObj=multi:newEvent([function: TASK defualt: function() end]) -alarmObj=multi:newAlarm([number: SET defualt: 0]) -loopObj=multi:newLoop([function: FUNC]) -stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -updaterObj=multi:newUpdater([number: SKIP defualt: 0]) -watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) -multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) -void=multi:newThread(string: name,function: func) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob(function: func,[string: name]) -multi:newRange(number: a,number: b,[number: c]) -multi:newCondition(func) - -Constructors [NON-ACTORS] -------------------------- -multi:newTrigger(function: func) -multi:newTask(function: func) -multi:newConnection() -multi:newTimer() -multi:newFunction(function: func) -]] -multi.changelog=[[Changelog starts at Version A.0.0 -New in A.0.0 - Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment - version.major.minor -New in A.1.0 - Changed: multi:newConnection(protect) method - Changed the way you are able to interact with it by adding the __call metamethod - Old usage: - OnUpdate=multi:newConnection() - OnUpdate:connect(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) - New usage: notice that connect is no longer needed! Both ways still work! and always will work :) - OnUpdate=multi:newConnection() - OnUpdate(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) -New in A.2.0 (12/31/2016) - Added: - connectionobj.getConnection(name) - returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) - if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used - Changed: obj=multi:newConnection() - obj:connect(func,name) and obj(func,name) - Added the name argument to allow indexing specific connection objects... Useful when creating an async library -New in A.3.0 (1/29/2017) - Added: - Load detection! - multi.threshold -- minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 - multi.threstimed -- amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 - multi:setThreshold(n) -- method used to set multi.threshold - multi:setThrestimed(n) -- method used to set multi.threstimed - multi:getLoad() -- returns a number between 0 and 100 -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.ender={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.fps=60 -multi.Id=-1 -multi.Type='mainprocess' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.queuefinal=function(self) - self:Destroy() - if self.Parent.Mainloop[#self.Parent.Mainloop] then - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - else - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end -end ---Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) -multi.Priority_Core=1 -multi.Priority_High=4 -multi.Priority_Above_Normal=16 -multi.Priority_Normal=64 -multi.Priority_Below_Normal=256 -multi.Priority_Low=1024 -multi.Priority_Idle=4096 -multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} -multi.PStep=1 ---^^^^ -multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise -multi.Priority=multi.Priority_Core -multi.threshold=256 -multi.threstimed=.001 -function multi:setThreshold(n) - self.threshold=n or 120 -end -function multi:setThrestimed(n) - self.threstimed=n or .001 -end -function multi:getLoad() - return multi:newFunction(function(self) - local sample=#multi.Mainloop - local FFloadtest=0 - multi:benchMark(multi.threstimed):OnBench(function(_,l3) FFloadtest=l3*(1/multi.threstimed) end) - self:hold(function() return FFloadtest~=0 end) - local val=FFloadtest/sample - if val>multi.threshold then - return 0 - else - return 100-((val/multi.threshold)*100) - end - end)() -end -function multi:setDomainName(name) - self[name]={} -end -function multi:linkDomain(name) - return self[name] -end -function multi:_Pause() - self.Active=false -end -function multi:setPriority(s) - if type(s)==number then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='an' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='bn' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - end -end --- System -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end -function multi:Stop() - self.Active=false -end -function multi:condition(cond) - if not self.CD then - self:Pause() - self.held=true - self.CD=cond.condition - elseif not(cond.condition()) then - self.held=false - self:Resume() - self.CD=nil - return false - end - self.Parent:Do_Order() - return true -end -function multi:isHeld() - return self.held -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:waitFor(obj) - self:hold(function() return obj:isActive() end) -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:enablePriority() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>7 then - PS.PStep=1 - end - end -end -function multi:enablePriority2() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=1 - end - end -end -multi.disablePriority=multi.unProtect -function multi:fromfile(path,int) - int=int or self - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec,p) - local temp=self:newLoop(function(t,self) - if self.clock()-self.init>self.sec then - self.tt(self.sec,self.c) - self:Destroy() - else - self.c=self.c+1 - end - end) - temp.Priority=p or 1 - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=self.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('process') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi.startFPSMonitior() - if not multi.runFPS then - multi.doFPS(s) - multi.runFPS=true - end -end -function multi.doFPS(s) - multi:benchMark(1):OnBench(doFPS) - if s then - multi.fps=s - end -end ---Helpers -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - self.CID=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - self.OnError:Fire(err,Loop[_D]) - end - end - end - end -end -function multi:unProtect() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") - self:OnBreak(func) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:isActive() - return self.Active -end -function multi:getType() - return self.Type -end -function multi:Sleep(n) - self:hold(n) -end -function multi:Pause() - if self.Type=='mainprocess' then - print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - end -end -function multi:Resume() - if self.Type=='process' or self.Type=='mainprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='process' or self.Type=='mainprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end - -function multi:hold(task) - self:Pause() - self.held=true - if type(task)=='number' then - local alarm=self.Parent:newAlarm(task) - while alarm.Active==true do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - alarm:Destroy() - self:Resume() - self.held=false - elseif type(task)=='function' then - local env=self.Parent:newEvent(task) - env:OnEvent(function(envt) envt:Pause() envt.Active=false end) - while env.Active do - if love then - self.Parent:lManager() - else - self.Parent:Do_Order() - end - end - env:Destroy() - self:Resume() - self.held=false - else - print('Error Data Type!!!') - end -end -function multi:oneTime(func,...) - if not(self.Type=='mainprocess' or self.Type=='process') then - for _k=1,#self.Parent.Tasks2 do - if self.Parent.Tasks2[_k]==func then - return false - end - end - table.insert(self.Parent.Tasks2,func) - func(...) - return true - else - for _k=1,#self.Tasks2 do - if self.Tasks2[_k]==func then - return false - end - end - table.insert(self.Tasks2,func) - func(...) - return true - end -end -function multi:Reset(n) - self:Resume() -end -function multi:isDone() - return self.Active~=true -end -function multi:create(ref) - multi.OnObjectCreated:Fire(ref) -end ---Constructors [CORE] -function multi:newBase(ins) - if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' or self.Type=='queue' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.ender={} - c.Id=0 - c.PId=0 - c.Act=function() end - c.Parent=self - c.held=false - if ins then - table.insert(self.Mainloop,ins,c) - else - table.insert(self.Mainloop,c) - end - return c -end -function multi:newProcess(file) - if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Id=0 - c.Type='process' - c.Mainloop={} - c.Tasks={} - c.Tasks2={} - c.Garbage={} - c.Children={} - c.Paused={} - c.Active=true - c.Id=-1 - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - function c:Start() - if self.l then - self.l:Resume() - else - self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) - end - end - function c:Pause() - if self.l then - self.l:Pause() - end - end - function c:Remove() - self:Destroy() - self.l:Destroy() - end - if file then - self.Cself=c - loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end -function multi:newQueuer(file) - local c=self:newProcess() - c.Type='queue' - c.last={} - c.funcE={} - function c:OnQueueCompleted(func) - table.insert(self.funcE,func) - end - if file then - self.Cself=c - loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end ---Constructors [ACTORS] -function multi:newCustomObject(objRef,t) - local c={} - if t=='process' then - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") - else - c=self:newBase() - end - if type(objRef)=='table' then - table.merge(c,objRef) - end - if not c.Act then - function c:Act() - -- Empty function - end - end - else - c=objRef or {} - end - if not c.Type then - c.Type='coustomObject' - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newEvent(task) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='event' - c.Task=task or function() end - function c:Act() - if self.Task(self) then - self:Pause() - for _E=1,#self.func do - self.func[_E](self) - end - end - end - function c:OnEvent(func) - table.insert(self.func,func) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.Task) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newAlarm(set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='alarm' - c.Priority=self.Priority_Low - c.timer=self:newTimer() - c.set=set or 0 - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.set) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.timer:Get()>=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - if self.Type=='queue' then - c:Pause() - c:connectFinal(multi.queuefinal) - else - c.timer:Start() - end - self:create(c) - return c -end -function multi:newLoop(func) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='loop' - c.Start=self.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](self.Parent.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newUpdater(skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - function c:setSkip(n) - self.skip=n - end - c.OnUpdate=self.OnMainConnect - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newStep(start,reset,count,skip) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newTStep(start,reset,count,set) - local c={} - if self.Type=='queue' then - c=self:newBase(1) - self.last=c - else - c=self:newBase() - end - think=1 - c.Type='tstep' - c.Priority=self.Priority_Low - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=self.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=self.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=self.clock() - self:Resume() - end - if self.Type=='queue' then - if #self.Mainloop>1 then - c:Pause() - end - c:connectFinal(multi.queuefinal) - end - self:create(c) - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end -function multi:newThread(name,func) - local c={} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.firstRunDone=false - c.timer=multi.scheduler:newTimer() - c.ref.Globals=self:linkDomain("Globals") - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - self:syncGlobals(ret) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - err=coroutine.yield({"_kill_"}) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - n = tonumber(n) or 0 - ret=coroutine.yield({"_sleep_",n}) - self:syncGlobals(ret) - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(self:linkDomain("Threads"),c) - if not multi.scheduler:isActive() then - multi.scheduler:Resume() - end -end --- Constructors [SEMI-ACTORS] -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:newRange() - local selflink=self - local temp={ - getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, - } - setmetatable(temp,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=selflink--.Parent.Mainloop[selflink.CID] or - self.Link:Pause() - self.Link.held=true - return function() return self:getN() end - end - }) - self:create(temp) - return temp -end -function multi:newCondition(func) - local c={['condition']=func} - self:create(c) - return c -end --- Constructors [NON-ACTORS] -function multi:newFunction(func) - local c={} - c.func=func - mt={ - __index=multi, - __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end - } - c.Parent=self - function c:Pause() - self.Active=false - end - function c:Resume() - self.Active=true - end - setmetatable(c,mt) - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=os.clock() - end - function c:Get() - return (os.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=os.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newConnection(protect) - local c={} - setmetatable(c,{__call=function(self,...) self:connect(...) end}) - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - c.connections={} - c.fconnections={} - c.FC=0 - function c:fConnect(func) - local temp=self:connect(func) - table.insert(self.fconnections,temp) - self.FC=self.FC+1 - end - function c:getConnection(name,ingore) - if ingore then - return self.connections[name] or { - Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore - } - else - return self.connections[name] or self - end - end - function c:Fire(...) - local ret={} - for i=#self.func,1,-1 do - if self.protect then - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - print(temp[2]) - end - else - table.insert(ret,{self.func[i][1](...)}) - end - end - return ret - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func,name) - self.ID=self.ID+1 - table.insert(self.func,1,{func,self.ID}) - local temp = { - Link=self.func, - func=func, - ID=self.ID, - Parent=self, - Fire=function(self,...) - if self.Parent.FC>0 then - for i=1,#self.Parent.FC do - self.Parent.FC[i]:Fire(...) - end - end - if self.Parent.protect then - local t=pcall(self.func,...) - if t then - return t - end - else - return self.func(...) - end - end, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - if name then - self.connections[name]=temp - end - return temp - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',self.clock()) - while self.Active do - self:Do_Order() - end - print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',self.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - function self:uManager(dt) - self:Do_Order() - end - self:Do_Order() - end -end ---Thread Setup Stuff -multi:setDomainName("Threads") -multi:setDomainName("Globals") --- Scheduler -multi.scheduler=multi:newUpdater() -multi.scheduler.Type="scheduler" -function multi.scheduler:setStep(n) - self.skip=tonumber(n) or 24 -end -multi.scheduler.Threads=multi:linkDomain("Threads") -multi.scheduler.Globals=multi:linkDomain("Globals") -multi.scheduler:OnUpdate(function(self) - for i=#self.Threads,1,-1 do - ret={} - if coroutine.status(self.Threads[i].thread)=="dead" then - table.remove(self.Threads,i) - else - if self.Threads[i].timer:Get()>=self.Threads[i].sleep then - if self.Threads[i].firstRunDone==false then - self.Threads[i].firstRunDone=true - self.Threads[i].timer:Start() - _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) - else - _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) - end - if ret==true or ret==false then - print("Thread Ended!!!") - ret={} - end - end - if ret then - if ret[1]=="_kill_" then - table.remove(self.Threads,i) - elseif ret[1]=="_sleep_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=ret[2] - elseif ret.Name then - self.Globals[ret.Name]=ret.Value - end - end - end - end -end) -multi.scheduler:setStep() -multi.scheduler:Pause() -multi.OnError=multi:newConnection() +if table.unpack then + unpack=table.unpack +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +multi = {} +multi.Version={'A',3,0}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me ++Events #7 ++Alarms #2 ++Loops #3 ++Steps #4 ++TSteps #6 ++Triggers #16 ++Tasks #12 ++Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library ++Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent ++Jobs #11 ++Process #10 ++Conditions #15 ++Ranges #8 ++Threads #13 ++Functions #5 ++Queuers #17 ++Updaters #9 ++Watchers #18 ++CustomObjects #19 + +Constructors [Runners] +---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) +intObj=multi:newProcess([string: FILE defualt: nil]) +intObj=multi:newQueuer([string: FILE defualt: nil]) + +Constructors [ACTORS] +--------------------- Note: everything is a multiObj! +eventObj=multi:newEvent([function: TASK defualt: function() end]) +alarmObj=multi:newAlarm([number: SET defualt: 0]) +loopObj=multi:newLoop([function: FUNC]) +stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +updaterObj=multi:newUpdater([number: SKIP defualt: 0]) +watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) +multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) +void=multi:newThread(string: name,function: func) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob(function: func,[string: name]) +multi:newRange(number: a,number: b,[number: c]) +multi:newCondition(func) + +Constructors [NON-ACTORS] +------------------------- +multi:newTrigger(function: func) +multi:newTask(function: func) +multi:newConnection() +multi:newTimer() +multi:newFunction(function: func) +]] +multi.changelog=[[Changelog starts at Version A.0.0 +New in A.0.0 + Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requriment + version.major.minor +New in A.1.0 + Changed: multi:newConnection(protect) method + Changed the way you are able to interact with it by adding the __call metamethod + Old usage: + OnUpdate=multi:newConnection() + OnUpdate:connect(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) + New usage: notice that connect is no longer needed! Both ways still work! and always will work :) + OnUpdate=multi:newConnection() + OnUpdate(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) +New in A.2.0 (12/31/2016) + Added: + connectionobj.getConnection(name) + returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) + if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + Changed: obj=multi:newConnection() + obj:connect(func,name) and obj(func,name) + Added the name argument to allow indexing specific connection objects... Useful when creating an async library +New in A.3.0 (1/29/2017) + Added: + Load detection! + multi.threshold -- minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 + multi.threstimed -- amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 + multi:setThreshold(n) -- method used to set multi.threshold + multi:setThrestimed(n) -- method used to set multi.threstimed + multi:getLoad() -- returns a number between 0 and 100 +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.ender={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.fps=60 +multi.Id=-1 +multi.Type='mainprocess' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.queuefinal=function(self) + self:Destroy() + if self.Parent.Mainloop[#self.Parent.Mainloop] then + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + else + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end +end +--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) +multi.Priority_Core=1 +multi.Priority_High=4 +multi.Priority_Above_Normal=16 +multi.Priority_Normal=64 +multi.Priority_Below_Normal=256 +multi.Priority_Low=1024 +multi.Priority_Idle=4096 +multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} +multi.PStep=1 +--^^^^ +multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise +multi.Priority=multi.Priority_Core +multi.threshold=256 +multi.threstimed=.001 +function multi:setThreshold(n) + self.threshold=n or 120 +end +function multi:setThrestimed(n) + self.threstimed=n or .001 +end +function multi:getLoad() + return multi:newFunction(function(self) + local sample=#multi.Mainloop + local FFloadtest=0 + multi:benchMark(multi.threstimed):OnBench(function(_,l3) FFloadtest=l3*(1/multi.threstimed) end) + self:hold(function() return FFloadtest~=0 end) + local val=FFloadtest/sample + if val>multi.threshold then + return 0 + else + return 100-((val/multi.threshold)*100) + end + end)() +end +function multi:setDomainName(name) + self[name]={} +end +function multi:linkDomain(name) + return self[name] +end +function multi:_Pause() + self.Active=false +end +function multi:setPriority(s) + if type(s)==number then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='an' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='bn' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + end +end +-- System +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi:getParentProcess() + return self.Mainloop[self.CID] +end +function multi:Stop() + self.Active=false +end +function multi:condition(cond) + if not self.CD then + self:Pause() + self.held=true + self.CD=cond.condition + elseif not(cond.condition()) then + self.held=false + self:Resume() + self.CD=nil + return false + end + self.Parent:Do_Order() + return true +end +function multi:isHeld() + return self.held +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:waitFor(obj) + self:hold(function() return obj:isActive() end) +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:enablePriority() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>7 then + PS.PStep=1 + end + end +end +function multi:enablePriority2() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PStep)%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=1 + end + end +end +multi.disablePriority=multi.unProtect +function multi:fromfile(path,int) + int=int or self + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec,p) + local temp=self:newLoop(function(t,self) + if self.clock()-self.init>self.sec then + self.tt(self.sec,self.c) + self:Destroy() + else + self.c=self.c+1 + end + end) + temp.Priority=p or 1 + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=self.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('process') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi.startFPSMonitior() + if not multi.runFPS then + multi.doFPS(s) + multi.runFPS=true + end +end +function multi.doFPS(s) + multi:benchMark(1):OnBench(doFPS) + if s then + multi.fps=s + end +end +--Helpers +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + self.CID=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + self.OnError:Fire(err,Loop[_D]) + end + end + end + end +end +function multi:unProtect() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + else + print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") + self:OnBreak(func) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + if self.ender[i] then + self.ender[i](self) + end + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:isActive() + return self.Active +end +function multi:getType() + return self.Type +end +function multi:Sleep(n) + self:hold(n) +end +function multi:Pause() + if self.Type=='mainprocess' then + print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + end +end +function multi:Resume() + if self.Type=='process' or self.Type=='mainprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='process' or self.Type=='mainprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end + +function multi:hold(task) + self:Pause() + self.held=true + if type(task)=='number' then + local alarm=self.Parent:newAlarm(task) + while alarm.Active==true do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + alarm:Destroy() + self:Resume() + self.held=false + elseif type(task)=='function' then + local env=self.Parent:newEvent(task) + env:OnEvent(function(envt) envt:Pause() envt.Active=false end) + while env.Active do + if love then + self.Parent:lManager() + else + self.Parent:Do_Order() + end + end + env:Destroy() + self:Resume() + self.held=false + else + print('Error Data Type!!!') + end +end +function multi:oneTime(func,...) + if not(self.Type=='mainprocess' or self.Type=='process') then + for _k=1,#self.Parent.Tasks2 do + if self.Parent.Tasks2[_k]==func then + return false + end + end + table.insert(self.Parent.Tasks2,func) + func(...) + return true + else + for _k=1,#self.Tasks2 do + if self.Tasks2[_k]==func then + return false + end + end + table.insert(self.Tasks2,func) + func(...) + return true + end +end +function multi:Reset(n) + self:Resume() +end +function multi:isDone() + return self.Active~=true +end +function multi:create(ref) + multi.OnObjectCreated:Fire(ref) +end +--Constructors [CORE] +function multi:newBase(ins) + if not(self.Type=='mainprocess' or self.Type=='process' or self.Type=='queue') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' or self.Type=='queue' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.ender={} + c.Id=0 + c.PId=0 + c.Act=function() end + c.Parent=self + c.held=false + if ins then + table.insert(self.Mainloop,ins,c) + else + table.insert(self.Mainloop,c) + end + return c +end +function multi:newProcess(file) + if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end + local c = {} + setmetatable(c, self) + c.Parent=self + c.Active=true + c.func={} + c.Id=0 + c.Type='process' + c.Mainloop={} + c.Tasks={} + c.Tasks2={} + c.Garbage={} + c.Children={} + c.Paused={} + c.Active=true + c.Id=-1 + c.Rest=0 + c.Jobs={} + c.queue={} + c.jobUS=2 + function c:Start() + if self.l then + self.l:Resume() + else + self.l=self.Parent:newLoop(function(dt) c:uManager(dt) end) + end + end + function c:Pause() + if self.l then + self.l:Pause() + end + end + function c:Remove() + self:Destroy() + self.l:Destroy() + end + if file then + self.Cself=c + loadstring('local interface=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +function multi:newQueuer(file) + local c=self:newProcess() + c.Type='queue' + c.last={} + c.funcE={} + function c:OnQueueCompleted(func) + table.insert(self.funcE,func) + end + if file then + self.Cself=c + loadstring('local queue=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +--Constructors [ACTORS] +function multi:newCustomObject(objRef,t) + local c={} + if t=='process' then + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + print("This Custom Object was created on a queue! Ensure that it has a way to end! All objects have a obj:Break() method!") + else + c=self:newBase() + end + if type(objRef)=='table' then + table.merge(c,objRef) + end + if not c.Act then + function c:Act() + -- Empty function + end + end + else + c=objRef or {} + end + if not c.Type then + c.Type='coustomObject' + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newEvent(task) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='event' + c.Task=task or function() end + function c:Act() + if self.Task(self) then + self:Pause() + for _E=1,#self.func do + self.func[_E](self) + end + end + end + function c:OnEvent(func) + table.insert(self.func,func) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.Task) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newAlarm(set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='alarm' + c.Priority=self.Priority_Low + c.timer=self:newTimer() + c.set=set or 0 + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.set) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.timer:Get()>=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + if self.Type=='queue' then + c:Pause() + c:connectFinal(multi.queuefinal) + else + c.timer:Start() + end + self:create(c) + return c +end +function multi:newLoop(func) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='loop' + c.Start=self.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](self.Parent.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newUpdater(skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=self.skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + function c:setSkip(n) + self.skip=n + end + c.OnUpdate=self.OnMainConnect + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newStep(start,reset,count,skip) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newTStep(start,reset,count,set) + local c={} + if self.Type=='queue' then + c=self:newBase(1) + self.last=c + else + c=self:newBase() + end + think=1 + c.Type='tstep' + c.Priority=self.Priority_Low + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=self.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=self.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=self.clock() + self:Resume() + end + if self.Type=='queue' then + if #self.Mainloop>1 then + c:Pause() + end + c:connectFinal(multi.queuefinal) + end + self:create(c) + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + if self.Type=='queue' then + print("Cannot create a watcher on a queue! Creating on 'multi' instead!") + self=multi + end + local c=self:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + self:create(c) + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +function multi:newThread(name,func) + local c={} + c.ref={} + c.Name=name + c.thread=coroutine.create(func) + c.sleep=1 + c.firstRunDone=false + c.timer=multi.scheduler:newTimer() + c.ref.Globals=self:linkDomain("Globals") + function c.ref:send(name,val) + ret=coroutine.yield({Name=name,Value=val}) + self:syncGlobals(ret) + end + function c.ref:get(name) + return self.Globals[name] + end + function c.ref:kill() + err=coroutine.yield({"_kill_"}) + if err then + error("Failed to kill a thread! Exiting...") + end + end + function c.ref:sleep(n) + n = tonumber(n) or 0 + ret=coroutine.yield({"_sleep_",n}) + self:syncGlobals(ret) + end + function c.ref:syncGlobals(v) + self.Globals=v + end + table.insert(self:linkDomain("Threads"),c) + if not multi.scheduler:isActive() then + multi.scheduler:Resume() + end +end +-- Constructors [SEMI-ACTORS] +function multi:newJob(func,name) + if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:newRange() + local selflink=self + local temp={ + getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, + } + setmetatable(temp,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=selflink--.Parent.Mainloop[selflink.CID] or + self.Link:Pause() + self.Link.held=true + return function() return self:getN() end + end + }) + self:create(temp) + return temp +end +function multi:newCondition(func) + local c={['condition']=func} + self:create(c) + return c +end +-- Constructors [NON-ACTORS] +function multi:newFunction(func) + local c={} + c.func=func + mt={ + __index=multi, + __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end + } + c.Parent=self + function c:Pause() + self.Active=false + end + function c:Resume() + self.Active=true + end + setmetatable(c,mt) + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=os.clock() + end + function c:Get() + return (os.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=os.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newConnection(protect) + local c={} + setmetatable(c,{__call=function(self,...) self:connect(...) end}) + c.Type='connector' + c.func={} + c.ID=0 + c.protect=protect or true + c.connections={} + c.fconnections={} + c.FC=0 + function c:fConnect(func) + local temp=self:connect(func) + table.insert(self.fconnections,temp) + self.FC=self.FC+1 + end + function c:getConnection(name,ingore) + if ingore then + return self.connections[name] or { + Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore + } + else + return self.connections[name] or self + end + end + function c:Fire(...) + local ret={} + for i=#self.func,1,-1 do + if self.protect then + local temp={pcall(self.func[i][1],...)} + if temp[1] then + table.remove(temp,1) + table.insert(ret,temp) + else + print(temp[2]) + end + else + table.insert(ret,{self.func[i][1](...)}) + end + end + return ret + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func,name) + self.ID=self.ID+1 + table.insert(self.func,1,{func,self.ID}) + local temp = { + Link=self.func, + func=func, + ID=self.ID, + Parent=self, + Fire=function(self,...) + if self.Parent.FC>0 then + for i=1,#self.Parent.FC do + self.Parent.FC[i]:Fire(...) + end + end + if self.Parent.protect then + local t=pcall(self.func,...) + if t then + return t + end + else + return self.func(...) + end + end, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + if name then + self.connections[name]=temp + end + return temp + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +multi.OnObjectCreated=multi:newConnection() +multi.OnObjectDestroyed=multi:newConnection() +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',self.clock()) + while self.Active do + self:Do_Order() + end + print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',self.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + function self:uManager(dt) + self:Do_Order() + end + self:Do_Order() + end +end +--Thread Setup Stuff +multi:setDomainName("Threads") +multi:setDomainName("Globals") +-- Scheduler +multi.scheduler=multi:newUpdater() +multi.scheduler.Type="scheduler" +function multi.scheduler:setStep(n) + self.skip=tonumber(n) or 24 +end +multi.scheduler.Threads=multi:linkDomain("Threads") +multi.scheduler.Globals=multi:linkDomain("Globals") +multi.scheduler:OnUpdate(function(self) + for i=#self.Threads,1,-1 do + ret={} + if coroutine.status(self.Threads[i].thread)=="dead" then + table.remove(self.Threads,i) + else + if self.Threads[i].timer:Get()>=self.Threads[i].sleep then + if self.Threads[i].firstRunDone==false then + self.Threads[i].firstRunDone=true + self.Threads[i].timer:Start() + _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) + else + _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) + end + if ret==true or ret==false then + print("Thread Ended!!!") + ret={} + end + end + if ret then + if ret[1]=="_kill_" then + table.remove(self.Threads,i) + elseif ret[1]=="_sleep_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=ret[2] + elseif ret.Name then + self.Globals[ret.Name]=ret.Value + end + end + end + end +end) +multi.scheduler:setStep() +multi.scheduler:Pause() +multi.OnError=multi:newConnection() diff --git a/oldversions/MultiManager(1.4.1).lua b/oldversions/MultiManager(1.4.1).lua index b370e19..55ae615 100644 --- a/oldversions/MultiManager(1.4.1).lua +++ b/oldversions/MultiManager(1.4.1).lua @@ -1,1518 +1,1518 @@ -if table.unpack then - unpack=table.unpack -end -function table.merge(t1, t2) - for k,v in pairs(t2) do - if type(v) == 'table' then - if type(t1[k] or false) == 'table' then - table.merge(t1[k] or {}, t2[k] or {}) - else - t1[k] = v - end - else - t1[k] = v - end - end - return t1 -end -_print=print -function print(...) - if not __SUPPRESSPRINTS then - _print(...) - end -end -multi = {} -multi.Version={'A',4,1}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 -multi.help=[[ -For a list of features do print(multi.Features) -For a list of changes do print(multi.changelog) -For current version do print(multi.Version) -For current stage do print(multi.stage) -For help do print(multi.help) :D -]] -multi.stage='stable' -multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ -MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me -+Events #7 -+Alarms #2 -+Loops #3 -+Steps #4 -+TSteps #6 -+Triggers #16 -+Tasks #12 -+Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library -+Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent -+Jobs #11 -+Process #10 -+Conditions #15 -+Ranges #8 -+Threads #13 -+Functions #5 -+Queuers #17 -+Updaters #9 -+Watchers #18 -+CustomObjects #19 - -Constructors [Runners] ----------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) -ProcessObj=multi:newProcess([string: FILE defualt: nil]) -ProcessObj=multi:newQueuer([string: FILE defualt: nil]) - -NOTE: The multi namespace is also a ProcessObj - - -Constructors [ACTORS] ---------------------- Note: everything is a multiObj! -eventObj=multi:newEvent([function: TASK defualt: function() end]) -alarmObj=multi:newAlarm([number: SET defualt: 0]) -loopObj=multi:newLoop([function: FUNC]) -stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) -tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) -updaterObj=multi:newUpdater([number: SKIP defualt: 0]) -watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) -multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) - -Constructors [Semi-ACTORS] --------------------------- -multi:newJob(function: func,[string: name]) -multi:newRange(number: a,number: b,[number: c]) -multi:newCondition(func) -void=multi:newThread(string: name,function: func) - -Constructors [NON-ACTORS] -------------------------- -multi:newTrigger(function: func) -multi:newTask(function: func) -multi:newConnection() -multi:newTimer() -multi:newFunction(function: func) -]] -multi.changelog=[[Changelog starts at Version A.0.0 -New in A.0.0 - Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requirement - version.major.minor -New in A.1.0 - Changed: multi:newConnection(protect) method - Changed the way you are able to interact with it by adding the __call metamethod - Old usage: - OnUpdate=multi:newConnection() - OnUpdate:connect(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) - New usage: notice that connect is no longer needed! Both ways still work! and always will work :) - OnUpdate=multi:newConnection() - OnUpdate(function(...) - print("Updating",...) - end) - OnUpdate:Fire(1,2,3) -New in A.2.0 (12/31/2016) - Added: - connectionobj.getConnection(name) - returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) - if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used - Changed: obj=multi:newConnection() - obj:connect(func,name) and obj(func,name) - Added the name argument to allow indexing specific connection objects... Useful when creating an async library -New in A.3.0 (1/29/2017) - Added: - Load detection! - multi.threshold -- minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 - multi.threstimed -- amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 - multi:setThreshold(n) -- method used to set multi.threshold - multi:setThrestimed(n) -- method used to set multi.threstimed - multi:getLoad() -- returns a number between 0 and 100 -New in A.4.0 (3/20/2017) - Added: - multiobj:reallocate(ProcessObj) -- changes the parent process of an object - ProcessObj:getController() -- returns the mThread so you can opperate on it like a multiobj - Example1: - require("multimanager") -- require the library - int1=multi:newProcess() -- create a process - int1.NAME="int1" -- give it a name for example purposes - int2=multi:newProcess() -- create another process to reallocate - int2.NAME="int2" -- name this a different name - step=int1:newTStep(1,10) -- create a TStep so we can slowly see what is going on - step:OnStep(function(p,s) -- connect to the onstep event - print(p,s.Parent.NAME) -- print the position and process name - end) - step:OnEnd(function(s) -- when the step ends lets reallocate it to the other process - if s.Parent.NAME=="int1" then -- lets only do this if it is in the int1 process - s:reallocate(int2) -- send it to int2 - s:Reset() -- reset the object - else - print("We are done!") - os.exit() -- end the program when int2 did its thing - end - end) - int1:Start() -- start process 1 - int2:Start() -- start process 2 - multi:mainloop() -- start the main loop - Fixed/Updated: - queuer=multi:newQueuer([string: file]) - Alarms now preform as they should on a queuer - Example2: - int=multi:newQueuer() - step=int:newTStep(1,10,1,.5) - alarm=int:newAlarm(2) - step2=int:newTStep(1,5,1,.5) - step:OnStep(function(p,s) - print(p) - end) - step2:OnStep(function(p,s) - print(p,"!") - end) - alarm:OnRing(function(a) - print("Ring1!!!") - end) - int:OnQueueCompleted(function(s) - s:Pause() - print("Done!") - os.exit() - end) - int:Start() - multi:mainloop() -New in A.4.1 (4/10/2017) - Change: - small change to the hold method to make it a bit more lightweight - Using a timer instead of an alarm object! - Limits to hold: - cannot hold more than 1 object at a time, and doing so could cause a deadlock! - Upcomming: - Threaded objects wrapped in corutines, so you can hold/sleep without problems! -]] -multi.__index = multi -multi.Mainloop={} -multi.Tasks={} -multi.Tasks2={} -multi.Garbage={} -multi.ender={} -multi.Children={} -multi.Paused={} -multi.Active=true -multi.fps=60 -multi.Id=-1 -multi.Type='mainprocess' -multi.Rest=0 -multi._type=type -multi.Jobs={} -multi.queue={} -multi.jobUS=2 -multi.clock=os.clock -multi.time=os.time -multi.LinkedPath=multi -multi.queuefinal=function(self) - self:Destroy() - if self.Parent.Mainloop[#self.Parent.Mainloop] then - if self.Parent.Mainloop[#self.Parent.Mainloop].Type=="alarm" then - self.Parent.Mainloop[#self.Parent.Mainloop]:Reset() - self.Parent.Mainloop[#self.Parent.Mainloop].Active=true - else - self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() - end - else - for i=1,#self.Parent.funcE do - self.Parent.funcE[i](self) - end - self.Parent:Remove() - end -end ---Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) -multi.Priority_Core=1 -multi.Priority_High=4 -multi.Priority_Above_Normal=16 -multi.Priority_Normal=64 -multi.Priority_Below_Normal=256 -multi.Priority_Low=1024 -multi.Priority_Idle=4096 -multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} -multi.PStep=1 ---^^^^ -multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise -multi.Priority=multi.Priority_Core -multi.threshold=256 -multi.threstimed=.001 -function multi:setThreshold(n) - self.threshold=n or 120 -end -function multi:setThrestimed(n) - self.threstimed=n or .001 -end -function multi:getLoad() - return multi:newFunction(function(self) - local sample=#multi.Mainloop - local FFloadtest=0 - multi:benchMark(multi.threstimed):OnBench(function(_,l3) FFloadtest=l3*(1/multi.threstimed) end) - self:hold(function() return FFloadtest~=0 end) - local val=FFloadtest/sample - if val>multi.threshold then - return 0 - else - return 100-((val/multi.threshold)*100) - end - end)() -end -function multi:setDomainName(name) - self[name]={} -end -function multi:linkDomain(name) - return self[name] -end -function multi:_Pause() - self.Active=false -end -function multi:setPriority(s) - if type(s)==number then - self.Priority=s - elseif type(s)=='string' then - if s:lower()=='core' or s:lower()=='c' then - self.Priority=self.Priority_Core - elseif s:lower()=='high' or s:lower()=='h' then - self.Priority=self.Priority_High - elseif s:lower()=='above' or s:lower()=='an' then - self.Priority=self.Priority_Above_Normal - elseif s:lower()=='normal' or s:lower()=='n' then - self.Priority=self.Priority_Normal - elseif s:lower()=='below' or s:lower()=='bn' then - self.Priority=self.Priority_Below_Normal - elseif s:lower()=='low' or s:lower()=='l' then - self.Priority=self.Priority_Low - elseif s:lower()=='idle' or s:lower()=='i' then - self.Priority=self.Priority_Idle - end - end -end --- System -function os.getOS() - if package.config:sub(1,1)=='\\' then - return 'windows' - else - return 'unix' - end -end -if os.getOS()=='windows' then - function os.sleep(n) - if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end - end -else - function os.sleep(n) - os.execute('sleep ' .. tonumber(n)) - end -end -function multi:getParentProcess() - return self.Mainloop[self.CID] -end -function multi:Stop() - self.Active=false -end -function multi:condition(cond) - if not self.CD then - self:Pause() - self.held=true - self.CD=cond.condition - elseif not(cond.condition()) then - self.held=false - self:Resume() - self.CD=nil - return false - end - self.Parent:Do_Order() - return true -end -function multi:isHeld() - return self.held -end -function multi.executeFunction(name,...) - if type(_G[name])=='function' then - _G[name](...) - else - print('Error: Not a function') - end -end -function multi:waitFor(obj) - local value=false - self.__waiting=function() - value=true - end - obj:connectFinal(self.__waiting) - self:hold(function() return value end) -end -function multi:reboot(r) - local before=collectgarbage('count') - self.Mainloop={} - self.Tasks={} - self.Tasks2={} - self.Garbage={} - self.Children={} - self.Paused={} - self.Active=true - self.Id=-1 - if r then - for i,v in pairs(_G) do - if type(i)=='table' then - if i.Parent and i.Id and i.Act then - i={} - end - end - end - end - collectgarbage() - local after=collectgarbage('count') - print([[Before rebooting total Ram used was ]]..before..[[Kb -After rebooting total Ram used is ]]..after..[[ Kb -A total of ]]..(before-after)..[[Kb was cleaned up]]) -end -function multi:getChildren() - return self.Mainloop -end ---Processor -function multi:getError() - if self.error then - return self.error - end -end -function multi:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:enablePriority() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>7 then - PS.PStep=1 - end - end -end -function multi:enablePriority2() - function self:Do_Order() - local Loop=self.Mainloop - _G.ID=0 - local PS=self - for _D=#Loop,1,-1 do - if Loop[_D] then - if (PS.PStep)%Loop[_D].Priority==0 then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end - end - PS.PStep=PS.PStep+1 - if PS.PStep>self.Priority_Idle then - PS.PStep=1 - end - end -end -multi.disablePriority=multi.unProtect -function multi:fromfile(path,int) - int=int or self - local test2={} - local test=bin.load(path) - local tp=test:getBlock('s') - if tp=='event' then - test2=int:newEvent(test:getBlock('f')) - local t=test:getBlock('t') - for i=1,#t do - test2:OnEvent(t[i]) - end - elseif tp=='alarm' then - test2=int:newAlarm(test:getBlock('n')) - elseif tp=='loop' then - test2=int:newLoop(test:getBlock('t')[1]) - elseif tp=='step' or tp=='tstep' then - local func=test:getBlock('t') - local funcE=test:getBlock('t') - local funcS=test:getBlock('t') - local tab=test:getBlock('t') - test2=int:newStep() - table.merge(test2,tab) - test2.funcE=funcE - test2.funcS=funcS - test2.func=func - elseif tp=='trigger' then - test2=int:newTrigger(test:getBlock('f')) - elseif tp=='connector' then - test2=int:newConnection() - test2.func=test:getBlock('t') - elseif tp=='timer' then - test2=int:newTimer() - test2.count=tonumber(test:getBlock('n')) - else - print('Error: The file you selected is not a valid multi file object!') - return false - end - return test2 -end -function multi:benchMark(sec,p) - local temp=self:newLoop(function(t,self) - if self.clock()-self.init>self.sec then - self.tt(self.sec,self.c) - self:Destroy() - else - self.c=self.c+1 - end - end) - temp.Priority=p or 1 - function temp:OnBench(func) - self.tt=func - end - self.tt=function() end - temp.sec=sec - temp.init=self.clock() - temp.c=0 - return temp -end -function multi:tofile(path) - local items=self:getChildren() - io.mkDir(io.getName(path)) - for i=1,#items do - items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') - end - local int=bin.new() - int:addBlock('process') - int:addBlock(io.getName(path)) - int:addBlock(#self.Mainloop) - int:addBlock(self.Active) - int:addBlock(self.Rest) - int:addBlock(self.Jobs) - int:tofile() -end -function multi.startFPSMonitior() - if not multi.runFPS then - multi.doFPS(s) - multi.runFPS=true - end -end -function multi.doFPS(s) - multi:benchMark(1):OnBench(doFPS) - if s then - multi.fps=s - end -end ---Helpers -function multi:isAnActor() - return ({watcher=true,tstep=true,step=true,updater=true,loop=true,alarm=true,event=true})[self.Type] -end -function multi:OnMainConnect(func) - table.insert(self.func,func) -end -function multi:protect() - function self:Do_Order() - local Loop=self.Mainloop - for _D=#Loop,1,-1 do - if Loop[_D]~=nil then - Loop[_D].Id=_D - self.CID=_D - local status, err=pcall(Loop[_D].Act,Loop[_D]) - if err and not(Loop[_D].error) then - Loop[_D].error=err - self.OnError:Fire(err,Loop[_D]) - end - end - end - end -end -function multi:unProtect() - local Loop=self.Mainloop - _G.ID=0 - for _D=#Loop,1,-1 do - if Loop[_D] then - if Loop[_D].Active then - Loop[_D].Id=_D - self.CID=_D - Loop[_D]:Act() - end - end - end -end -function multi:reallocate(o,n) - n=n or #o.Mainloop+1 - local int=self.Parent - self:Destroy() - self.Parent=o - table.insert(o.Mainloop,n,self) - self.Active=true -end -function multi:setJobSpeed(n) - self.jobUS=n -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - end - end -end -function multi:FreeMainEvent() - self.func={} -end -function multi:connectFinal(func) - if self.Type=='event' then - self:OnEvent(func) - elseif self.Type=='alarm' then - self:OnRing(func) - elseif self.Type=='step' or self.Type=='tstep' then - self:OnEnd(func) - else - print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") - self:OnBreak(func) - end -end -function multi:Break() - self:Pause() - self.Active=nil - for i=1,#self.ender do - if self.ender[i] then - self.ender[i](self) - end - end -end -function multi:OnBreak(func) - table.insert(self.ender,func) -end -function multi:isPaused() - return not(self.Active) -end -function multi:isActive() - return self.Active -end -function multi:getType() - return self.Type -end -function multi:Sleep(n) - self:hold(n) -end -function multi:Pause() - if self.Type=='mainprocess' then - print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") - else - self.Active=false - if self.Parent.Mainloop[self.Id]~=nil then - table.remove(self.Parent.Mainloop,self.Id) - table.insert(self.Parent.Paused,self) - self.PId=#self.Parent.Paused - end - end -end -function multi:Resume() - if self.Type=='process' or self.Type=='mainprocess' then - self.Active=true - local c=self:getChildren() - for i=1,#c do - c[i]:Resume() - end - else - if self:isPaused() then - table.remove(self.Parent.Paused,self.PId) - table.insert(self.Parent.Mainloop,self) - self.Id=#self.Parent.Mainloop - self.Active=true - end - end -end -function multi:resurrect() - table.insert(self.Parent.Mainloop,self) - self.Active=true -end -function multi:Destroy() - if self.Type=='process' or self.Type=='mainprocess' then - local c=self:getChildren() - for i=1,#c do - self.OnObjectDestroyed:Fire(c[i]) - c[i]:Destroy() - end - else - for i=1,#self.Parent.Mainloop do - if self.Parent.Mainloop[i]==self then - self.Parent.OnObjectDestroyed:Fire(self) - table.remove(self.Parent.Mainloop,i) - break - end - end - self.Active=false - end -end - -function multi:hold(task) - self:Pause() - self.held=true - if type(task)=='number' then - local timer=multi:newTimer() - timer:Start() - while timer:Get()=self.set then - self:Pause() - self.Active=false - for i=1,#self.func do - self.func[i](self) - end - end - end - function c:Resume() - self.Parent.Resume(self) - self.timer:Resume() - end - function c:Reset(n) - if n then self.set=n end - self:Resume() - self.timer:Reset() - end - function c:OnRing(func) - table.insert(self.func,func) - end - function c:Pause() - self.timer:Pause() - self.Parent.Pause(self) - end - self:create(c) - return c -end -function multi:newLoop(func) - local c=self:newBase() - c.Type='loop' - c.Start=self.clock() - if func then - c.func={func} - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - for i=1,#self.func do - self.func[i](self.Parent.clock()-self.Start,self) - end - end - function c:OnLoop(func) - table.insert(self.func,func) - end - self:create(c) - return c -end -function multi:newUpdater(skip) - local c=self:newBase() - c.Type='updater' - c.pos=1 - c.skip=skip or 1 - function c:Act() - if self.pos>=self.skip then - self.pos=0 - for i=1,#self.func do - self.func[i](self) - end - end - self.pos=self.pos+1 - end - function c:setSkip(n) - self.skip=n - end - c.OnUpdate=self.OnMainConnect - self:create(c) - return c -end -function multi:newStep(start,reset,count,skip) - local c=self:newBase() - think=1 - c.Type='step' - c.pos=start or 1 - c.endAt=reset or math.huge - c.skip=skip or 0 - c.spos=0 - c.count=count or 1*think - c.funcE={} - c.funcS={} - c.start=start or 1 - if start~=nil and reset~=nil then - if start>reset then - think=-1 - end - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self~=nil then - if self.spos==0 then - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - self.spos=self.spos+1 - if self.spos>=self.skip then - self.spos=0 - end - end - c.Reset=c.Resume - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,1,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Update(start,reset,count,skip) - self.start=start or self.start - self.endAt=reset or self.endAt - self.skip=skip or self.skip - self.count=count or self.count - self:Resume() - end - self:create(c) - return c -end -function multi:newTStep(start,reset,count,set) - local c=self:newBase() - think=1 - c.Type='tstep' - c.Priority=self.Priority_Low - c.start=start or 1 - local reset = reset or math.huge - c.endAt=reset - c.pos=start or 1 - c.skip=skip or 0 - c.count=count or 1*think - c.funcE={} - c.timer=self.clock() - c.set=set or 1 - c.funcS={} - function c:Update(start,reset,count,set) - self.start=start or self.start - self.pos=self.start - self.endAt=reset or self.endAt - self.set=set or self.set - self.count=count or self.count or 1 - self.timer=self.clock() - self:Resume() - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:addBlock(self.funcE) - m:addBlock(self.funcS) - m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) - m:addBlock(self.Active) - m:tofile(path) - end - function c:Act() - if self.clock()-self.timer>=self.set then - self:Reset() - if self.pos==self.start then - for fe=1,#self.funcS do - self.funcS[fe](self) - end - end - for i=1,#self.func do - self.func[i](self.pos,self) - end - self.pos=self.pos+self.count - if self.pos-self.count==self.endAt then - self:Pause() - for fe=1,#self.funcE do - self.funcE[fe](self) - end - self.pos=self.start - end - end - end - function c:OnStart(func) - table.insert(self.funcS,func) - end - function c:OnStep(func) - table.insert(self.func,func) - end - function c:OnEnd(func) - table.insert(self.funcE,func) - end - function c:Break() - self.Active=nil - end - function c:Reset(n) - if n then self.set=n end - self.timer=self.clock() - self:Resume() - end - self:create(c) - return c -end -function multi:newWatcher(namespace,name) - local function WatcherObj(ns,n) - if self.Type=='queue' then - print("Cannot create a watcher on a queue! Creating on 'multi' instead!") - self=multi - end - local c=self:newBase() - c.Type='watcher' - c.ns=ns - c.n=n - c.cv=ns[n] - function c:OnValueChanged(func) - table.insert(self.func,func) - end - function c:Act() - if self.cv~=self.ns[self.n] then - for i=1,#self.func do - self.func[i](self,self.cv,self.ns[self.n]) - end - self.cv=self.ns[self.n] - end - end - self:create(c) - return c - end - if type(namespace)~='table' and type(namespace)=='string' then - return WatcherObj(_G,namespace) - elseif type(namespace)=='table' and (type(name)=='string' or 'number') then - return WatcherObj(namespace,name) - else - print('Warning, invalid arguments! Nothing returned!') - end -end --- Constructors [SEMI-ACTORS] -function multi:newThread(name,func) - local c={} - c.ref={} - c.Name=name - c.thread=coroutine.create(func) - c.sleep=1 - c.firstRunDone=false - c.timer=multi.scheduler:newTimer() - c.ref.Globals=self:linkDomain("Globals") - function c.ref:send(name,val) - ret=coroutine.yield({Name=name,Value=val}) - self:syncGlobals(ret) - end - function c.ref:get(name) - return self.Globals[name] - end - function c.ref:kill() - err=coroutine.yield({"_kill_"}) - if err then - error("Failed to kill a thread! Exiting...") - end - end - function c.ref:sleep(n) - if type(n)=="function" then - ret=coroutine.yield({"_hold_",n}) - self:syncGlobals(ret) - elseif type(n)=="number" then - n = tonumber(n) or 0 - ret=coroutine.yield({"_sleep_",n}) - self:syncGlobals(ret) - else - error("Invalid Type for sleep!") - end - end - function c.ref:syncGlobals(v) - self.Globals=v - end - table.insert(self:linkDomain("Threads"),c) - if not multi.scheduler:isActive() then - multi.scheduler:Resume() - end -end -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end - local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end - c.Active=true - c.func={} - c.Id=0 - c.PId=0 - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) - end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS) - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) - end - end - self:Reset(self.Parent.jobUS) - end) - end -end -function multi:newRange() - local selflink=self - local temp={ - getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, - } - setmetatable(temp,{ - __call=function(self,a,b,c) - self.c=c or 1 - self.n=a-self.c - self.a=a - self.b=b - self.Link=selflink--.Parent.Mainloop[selflink.CID] or - self.Link:Pause() - self.Link.held=true - return function() return self:getN() end - end - }) - self:create(temp) - return temp -end -function multi:newCondition(func) - local c={['condition']=func} - self:create(c) - return c -end --- Constructors [NON-ACTORS] -function multi:newFunction(func) - local c={} - c.func=func - mt={ - __index=multi, - __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end - } - c.Parent=self - function c:Pause() - self.Active=false - end - function c:Resume() - self.Active=true - end - setmetatable(c,mt) - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - c.time=0 - c.count=0 - function c:Start() - self.time=os.clock() - end - function c:Get() - return (os.clock()-self.time)+self.count - end - c.Reset=c.Start - function c:Pause() - self.time=self:Get() - end - function c:Resume() - self.time=os.clock()-self.time - end - function c:tofile(path) - local m=bin.new() - self.count=self.count+self:Get() - m:addBlock(self.Type) - m:addBlock(self.count) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newTask(func) - table.insert(self.Tasks,func) -end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(self,...) - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.trigfunc) - m:tofile(path) - end - self:create(c) - return c -end -function multi:newConnection(protect) - local c={} - setmetatable(c,{__call=function(self,...) self:connect(...) end}) - c.Type='connector' - c.func={} - c.ID=0 - c.protect=protect or true - c.connections={} - c.fconnections={} - c.FC=0 - function c:fConnect(func) - local temp=self:connect(func) - table.insert(self.fconnections,temp) - self.FC=self.FC+1 - end - function c:getConnection(name,ingore) - if ingore then - return self.connections[name] or { - Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore - } - else - return self.connections[name] or self - end - end - function c:Fire(...) - local ret={} - for i=#self.func,1,-1 do - if self.protect then - local temp={pcall(self.func[i][1],...)} - if temp[1] then - table.remove(temp,1) - table.insert(ret,temp) - else - print(temp[2]) - end - else - table.insert(ret,{self.func[i][1](...)}) - end - end - return ret - end - function c:bind(t) - self.func=t - end - function c:remove() - self.func={} - end - function c:connect(func,name) - self.ID=self.ID+1 - table.insert(self.func,1,{func,self.ID}) - local temp = { - Link=self.func, - func=func, - ID=self.ID, - Parent=self, - Fire=function(self,...) ---~ if self.Parent.FC>0 then ---~ for i=1,#self.Parent.FC do ---~ self.Parent.FC[i]:Fire(...) ---~ end ---~ end - if self.Parent.protect then - local t=pcall(self.func,...) - if t then - return t - end - else - return self.func(...) - end - end, - remove=function(self) - for i=1,#self.Link do - if self.Link[i][2]~=nil then - if self.Link[i][2]==self.ID then - table.remove(self.Link,i) - self.remove=function() end - self.Link=nil - self.ID=nil - return true - end - end - end - end - } - if name then - self.connections[name]=temp - end - return temp - end - function c:tofile(path) - local m=bin.new() - m:addBlock(self.Type) - m:addBlock(self.func) - m:tofile(path) - end - return c -end -multi.OnObjectCreated=multi:newConnection() -multi.OnObjectDestroyed=multi:newConnection() ---Managers -function multi:mainloop() - for i=1,#self.Tasks do - self.Tasks[i](self) - end - rawset(self,'Start',self.clock()) - while self.Active do - self:Do_Order() - end - print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") -end -function multi._tFunc(self,dt) - for i=1,#self.Tasks do - self.Tasks[i](self) - end - if dt then - self.pump=true - end - self.pumpvar=dt - rawset(self,'Start',self.clock()) -end -function multi:uManager(dt) - if self.Active then - self:oneTime(self._tFunc,self,dt) - function self:uManager(dt) - self:Do_Order() - end - self:Do_Order() - end -end ---Thread Setup Stuff -multi:setDomainName("Threads") -multi:setDomainName("Globals") --- Scheduler -multi.scheduler=multi:newUpdater() -multi.scheduler.Type="scheduler" -function multi.scheduler:setStep(n) - self.skip=tonumber(n) or 24 -end -multi.scheduler.Threads=multi:linkDomain("Threads") -multi.scheduler.Globals=multi:linkDomain("Globals") -multi.scheduler:OnUpdate(function(self) - for i=#self.Threads,1,-1 do - ret={} - if coroutine.status(self.Threads[i].thread)=="dead" then - table.remove(self.Threads,i) - else - if self.Threads[i].timer:Get()>=self.Threads[i].sleep then - if self.Threads[i].firstRunDone==false then - self.Threads[i].firstRunDone=true - self.Threads[i].timer:Start() - _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) - else - _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) - end - if ret==true or ret==false then - print("Thread Ended!!!") - ret={} - end - end - if ret then - if ret[1]=="_kill_" then - table.remove(self.Threads,i) - elseif ret[1]=="_sleep_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=ret[2] - elseif ret[1]=="_hold_" then - self.Threads[i].timer:Reset() - self.Threads[i].sleep=math.huge - local event=multi:newEvent(ret[2]) - event.link=self.Threads[i] - event:OnEvent(function(evnt) - evnt.link.sleep=0 - end) - elseif ret.Name then - self.Globals[ret.Name]=ret.Value - end - end - end - end -end) -multi.scheduler:setStep() -multi.scheduler:Pause() -multi.OnError=multi:newConnection() ----------TESTS -multi:newThread("test",function(ref) - while true do - print(">!") - ref:sleep(1) - end -end) -multi:newThread("test",function(ref) - while true do - print("!<") - ref:sleep(1) - end -end) -multi:mainloop() +if table.unpack then + unpack=table.unpack +end +function table.merge(t1, t2) + for k,v in pairs(t2) do + if type(v) == 'table' then + if type(t1[k] or false) == 'table' then + table.merge(t1[k] or {}, t2[k] or {}) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end +_print=print +function print(...) + if not __SUPPRESSPRINTS then + _print(...) + end +end +multi = {} +multi.Version={'A',4,1}-- History: EventManager,EventManager+,MultiManager <-- Current After 6.3.0 Versioning scheme was altered. A.0.0 +multi.help=[[ +For a list of features do print(multi.Features) +For a list of changes do print(multi.changelog) +For current version do print(multi.Version) +For current stage do print(multi.stage) +For help do print(multi.help) :D +]] +multi.stage='stable' +multi.Features='Current Version: '..multi.Version[1]..'.'..multi.Version[2]..'.'..multi.Version[3]..' '..multi.stage..[[ +MultiManager has 19 Objects: # indicates most commonly used 1-19 1 being the most used by me ++Events #7 ++Alarms #2 ++Loops #3 ++Steps #4 ++TSteps #6 ++Triggers #16 ++Tasks #12 ++Connections #1 -- This is a rather new feature of this library, but has become the most useful for async handling. Knowing this is already 50% of this library ++Timers #14 -- this was tricky because these make up both Alarms and TSteps, but in purly using this standalone is almost non existent ++Jobs #11 ++Process #10 ++Conditions #15 ++Ranges #8 ++Threads #13 ++Functions #5 ++Queuers #17 ++Updaters #9 ++Watchers #18 ++CustomObjects #19 + +Constructors [Runners] +---------------------- Note: multi is the main Processor Obj It cannot be paused or destroyed (kinda) +ProcessObj=multi:newProcess([string: FILE defualt: nil]) +ProcessObj=multi:newQueuer([string: FILE defualt: nil]) + +NOTE: The multi namespace is also a ProcessObj + + +Constructors [ACTORS] +--------------------- Note: everything is a multiObj! +eventObj=multi:newEvent([function: TASK defualt: function() end]) +alarmObj=multi:newAlarm([number: SET defualt: 0]) +loopObj=multi:newLoop([function: FUNC]) +stepObj=multi:newStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SKIP defualt: 0]) +tstepObj=multi:newTStep([number: START defualt: 0],[number: RESET defualt: inf],[number: COUNT defualt: 1],[number: SET defualt: 1]) +updaterObj=multi:newUpdater([number: SKIP defualt: 0]) +watcherObj=multi:newWatcher(table: NAMESPACE,string: NAME) +multiObj=multi:newCustomObject([table: OBJREF],[string: T='process']) + +Constructors [Semi-ACTORS] +-------------------------- +multi:newJob(function: func,[string: name]) +multi:newRange(number: a,number: b,[number: c]) +multi:newCondition(func) +void=multi:newThread(string: name,function: func) + +Constructors [NON-ACTORS] +------------------------- +multi:newTrigger(function: func) +multi:newTask(function: func) +multi:newConnection() +multi:newTimer() +multi:newFunction(function: func) +]] +multi.changelog=[[Changelog starts at Version A.0.0 +New in A.0.0 + Nothing really however a changelog will now be recorded! Feel free to remove this extra strings if space is a requirement + version.major.minor +New in A.1.0 + Changed: multi:newConnection(protect) method + Changed the way you are able to interact with it by adding the __call metamethod + Old usage: + OnUpdate=multi:newConnection() + OnUpdate:connect(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) + New usage: notice that connect is no longer needed! Both ways still work! and always will work :) + OnUpdate=multi:newConnection() + OnUpdate(function(...) + print("Updating",...) + end) + OnUpdate:Fire(1,2,3) +New in A.2.0 (12/31/2016) + Added: + connectionobj.getConnection(name) + returns a list of an instance (or instances) of a single connect made with connectionobj:connect(func,name) or connectionobj(func,name) + if you can orginize data before hand you can route info to certain connections thus saving a lot of cpu time. NOTE: only one name per each connection... you can't have 2 of the same names in a dictonary... the last one will be used + Changed: obj=multi:newConnection() + obj:connect(func,name) and obj(func,name) + Added the name argument to allow indexing specific connection objects... Useful when creating an async library +New in A.3.0 (1/29/2017) + Added: + Load detection! + multi.threshold -- minimum amount of cycles that all mObjs should be allotted before the Manager is considered burdened. Defualt: 256 + multi.threstimed -- amount of time when counting the number of cycles, Greater gives a more accurate view of the load, but takes more time. Defualt: .001 + multi:setThreshold(n) -- method used to set multi.threshold + multi:setThrestimed(n) -- method used to set multi.threstimed + multi:getLoad() -- returns a number between 0 and 100 +New in A.4.0 (3/20/2017) + Added: + multiobj:reallocate(ProcessObj) -- changes the parent process of an object + ProcessObj:getController() -- returns the mThread so you can opperate on it like a multiobj + Example1: + require("multimanager") -- require the library + int1=multi:newProcess() -- create a process + int1.NAME="int1" -- give it a name for example purposes + int2=multi:newProcess() -- create another process to reallocate + int2.NAME="int2" -- name this a different name + step=int1:newTStep(1,10) -- create a TStep so we can slowly see what is going on + step:OnStep(function(p,s) -- connect to the onstep event + print(p,s.Parent.NAME) -- print the position and process name + end) + step:OnEnd(function(s) -- when the step ends lets reallocate it to the other process + if s.Parent.NAME=="int1" then -- lets only do this if it is in the int1 process + s:reallocate(int2) -- send it to int2 + s:Reset() -- reset the object + else + print("We are done!") + os.exit() -- end the program when int2 did its thing + end + end) + int1:Start() -- start process 1 + int2:Start() -- start process 2 + multi:mainloop() -- start the main loop + Fixed/Updated: + queuer=multi:newQueuer([string: file]) + Alarms now preform as they should on a queuer + Example2: + int=multi:newQueuer() + step=int:newTStep(1,10,1,.5) + alarm=int:newAlarm(2) + step2=int:newTStep(1,5,1,.5) + step:OnStep(function(p,s) + print(p) + end) + step2:OnStep(function(p,s) + print(p,"!") + end) + alarm:OnRing(function(a) + print("Ring1!!!") + end) + int:OnQueueCompleted(function(s) + s:Pause() + print("Done!") + os.exit() + end) + int:Start() + multi:mainloop() +New in A.4.1 (4/10/2017) + Change: + small change to the hold method to make it a bit more lightweight + Using a timer instead of an alarm object! + Limits to hold: + cannot hold more than 1 object at a time, and doing so could cause a deadlock! + Upcomming: + Threaded objects wrapped in corutines, so you can hold/sleep without problems! +]] +multi.__index = multi +multi.Mainloop={} +multi.Tasks={} +multi.Tasks2={} +multi.Garbage={} +multi.ender={} +multi.Children={} +multi.Paused={} +multi.Active=true +multi.fps=60 +multi.Id=-1 +multi.Type='mainprocess' +multi.Rest=0 +multi._type=type +multi.Jobs={} +multi.queue={} +multi.jobUS=2 +multi.clock=os.clock +multi.time=os.time +multi.LinkedPath=multi +multi.queuefinal=function(self) + self:Destroy() + if self.Parent.Mainloop[#self.Parent.Mainloop] then + if self.Parent.Mainloop[#self.Parent.Mainloop].Type=="alarm" then + self.Parent.Mainloop[#self.Parent.Mainloop]:Reset() + self.Parent.Mainloop[#self.Parent.Mainloop].Active=true + else + self.Parent.Mainloop[#self.Parent.Mainloop]:Resume() + end + else + for i=1,#self.Parent.funcE do + self.Parent.funcE[i](self) + end + self.Parent:Remove() + end +end +--Do not change these ever...Any other number will not work (Unless you are using enablePriority2() then change can be made. Just ensure that Priority_Idle is the greatest and Priority_Core is 1!) +multi.Priority_Core=1 +multi.Priority_High=4 +multi.Priority_Above_Normal=16 +multi.Priority_Normal=64 +multi.Priority_Below_Normal=256 +multi.Priority_Low=1024 +multi.Priority_Idle=4096 +multi.PList={multi.Priority_Core,multi.Priority_High,multi.Priority_Above_Normal,multi.Priority_Normal,multi.Priority_Below_Normal,multi.Priority_Low,multi.Priority_Idle} +multi.PStep=1 +--^^^^ +multi.PriorityTick=1 -- Between 1 and 4 any greater and problems arise +multi.Priority=multi.Priority_Core +multi.threshold=256 +multi.threstimed=.001 +function multi:setThreshold(n) + self.threshold=n or 120 +end +function multi:setThrestimed(n) + self.threstimed=n or .001 +end +function multi:getLoad() + return multi:newFunction(function(self) + local sample=#multi.Mainloop + local FFloadtest=0 + multi:benchMark(multi.threstimed):OnBench(function(_,l3) FFloadtest=l3*(1/multi.threstimed) end) + self:hold(function() return FFloadtest~=0 end) + local val=FFloadtest/sample + if val>multi.threshold then + return 0 + else + return 100-((val/multi.threshold)*100) + end + end)() +end +function multi:setDomainName(name) + self[name]={} +end +function multi:linkDomain(name) + return self[name] +end +function multi:_Pause() + self.Active=false +end +function multi:setPriority(s) + if type(s)==number then + self.Priority=s + elseif type(s)=='string' then + if s:lower()=='core' or s:lower()=='c' then + self.Priority=self.Priority_Core + elseif s:lower()=='high' or s:lower()=='h' then + self.Priority=self.Priority_High + elseif s:lower()=='above' or s:lower()=='an' then + self.Priority=self.Priority_Above_Normal + elseif s:lower()=='normal' or s:lower()=='n' then + self.Priority=self.Priority_Normal + elseif s:lower()=='below' or s:lower()=='bn' then + self.Priority=self.Priority_Below_Normal + elseif s:lower()=='low' or s:lower()=='l' then + self.Priority=self.Priority_Low + elseif s:lower()=='idle' or s:lower()=='i' then + self.Priority=self.Priority_Idle + end + end +end +-- System +function os.getOS() + if package.config:sub(1,1)=='\\' then + return 'windows' + else + return 'unix' + end +end +if os.getOS()=='windows' then + function os.sleep(n) + if n > 0 then os.execute('ping -n ' .. tonumber(n+1) .. ' localhost > NUL') end + end +else + function os.sleep(n) + os.execute('sleep ' .. tonumber(n)) + end +end +function multi:getParentProcess() + return self.Mainloop[self.CID] +end +function multi:Stop() + self.Active=false +end +function multi:condition(cond) + if not self.CD then + self:Pause() + self.held=true + self.CD=cond.condition + elseif not(cond.condition()) then + self.held=false + self:Resume() + self.CD=nil + return false + end + self.Parent:Do_Order() + return true +end +function multi:isHeld() + return self.held +end +function multi.executeFunction(name,...) + if type(_G[name])=='function' then + _G[name](...) + else + print('Error: Not a function') + end +end +function multi:waitFor(obj) + local value=false + self.__waiting=function() + value=true + end + obj:connectFinal(self.__waiting) + self:hold(function() return value end) +end +function multi:reboot(r) + local before=collectgarbage('count') + self.Mainloop={} + self.Tasks={} + self.Tasks2={} + self.Garbage={} + self.Children={} + self.Paused={} + self.Active=true + self.Id=-1 + if r then + for i,v in pairs(_G) do + if type(i)=='table' then + if i.Parent and i.Id and i.Act then + i={} + end + end + end + end + collectgarbage() + local after=collectgarbage('count') + print([[Before rebooting total Ram used was ]]..before..[[Kb +After rebooting total Ram used is ]]..after..[[ Kb +A total of ]]..(before-after)..[[Kb was cleaned up]]) +end +function multi:getChildren() + return self.Mainloop +end +--Processor +function multi:getError() + if self.error then + return self.error + end +end +function multi:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:enablePriority() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PList[PS.PStep])%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>7 then + PS.PStep=1 + end + end +end +function multi:enablePriority2() + function self:Do_Order() + local Loop=self.Mainloop + _G.ID=0 + local PS=self + for _D=#Loop,1,-1 do + if Loop[_D] then + if (PS.PStep)%Loop[_D].Priority==0 then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end + end + PS.PStep=PS.PStep+1 + if PS.PStep>self.Priority_Idle then + PS.PStep=1 + end + end +end +multi.disablePriority=multi.unProtect +function multi:fromfile(path,int) + int=int or self + local test2={} + local test=bin.load(path) + local tp=test:getBlock('s') + if tp=='event' then + test2=int:newEvent(test:getBlock('f')) + local t=test:getBlock('t') + for i=1,#t do + test2:OnEvent(t[i]) + end + elseif tp=='alarm' then + test2=int:newAlarm(test:getBlock('n')) + elseif tp=='loop' then + test2=int:newLoop(test:getBlock('t')[1]) + elseif tp=='step' or tp=='tstep' then + local func=test:getBlock('t') + local funcE=test:getBlock('t') + local funcS=test:getBlock('t') + local tab=test:getBlock('t') + test2=int:newStep() + table.merge(test2,tab) + test2.funcE=funcE + test2.funcS=funcS + test2.func=func + elseif tp=='trigger' then + test2=int:newTrigger(test:getBlock('f')) + elseif tp=='connector' then + test2=int:newConnection() + test2.func=test:getBlock('t') + elseif tp=='timer' then + test2=int:newTimer() + test2.count=tonumber(test:getBlock('n')) + else + print('Error: The file you selected is not a valid multi file object!') + return false + end + return test2 +end +function multi:benchMark(sec,p) + local temp=self:newLoop(function(t,self) + if self.clock()-self.init>self.sec then + self.tt(self.sec,self.c) + self:Destroy() + else + self.c=self.c+1 + end + end) + temp.Priority=p or 1 + function temp:OnBench(func) + self.tt=func + end + self.tt=function() end + temp.sec=sec + temp.init=self.clock() + temp.c=0 + return temp +end +function multi:tofile(path) + local items=self:getChildren() + io.mkDir(io.getName(path)) + for i=1,#items do + items[i]:tofile(io.getName(path)..'\\item'..item[i]..'.dat') + end + local int=bin.new() + int:addBlock('process') + int:addBlock(io.getName(path)) + int:addBlock(#self.Mainloop) + int:addBlock(self.Active) + int:addBlock(self.Rest) + int:addBlock(self.Jobs) + int:tofile() +end +function multi.startFPSMonitior() + if not multi.runFPS then + multi.doFPS(s) + multi.runFPS=true + end +end +function multi.doFPS(s) + multi:benchMark(1):OnBench(doFPS) + if s then + multi.fps=s + end +end +--Helpers +function multi:isAnActor() + return ({watcher=true,tstep=true,step=true,updater=true,loop=true,alarm=true,event=true})[self.Type] +end +function multi:OnMainConnect(func) + table.insert(self.func,func) +end +function multi:protect() + function self:Do_Order() + local Loop=self.Mainloop + for _D=#Loop,1,-1 do + if Loop[_D]~=nil then + Loop[_D].Id=_D + self.CID=_D + local status, err=pcall(Loop[_D].Act,Loop[_D]) + if err and not(Loop[_D].error) then + Loop[_D].error=err + self.OnError:Fire(err,Loop[_D]) + end + end + end + end +end +function multi:unProtect() + local Loop=self.Mainloop + _G.ID=0 + for _D=#Loop,1,-1 do + if Loop[_D] then + if Loop[_D].Active then + Loop[_D].Id=_D + self.CID=_D + Loop[_D]:Act() + end + end + end +end +function multi:reallocate(o,n) + n=n or #o.Mainloop+1 + local int=self.Parent + self:Destroy() + self.Parent=o + table.insert(o.Mainloop,n,self) + self.Active=true +end +function multi:setJobSpeed(n) + self.jobUS=n +end +function multi:hasJobs() + return #self.Jobs>0,#self.Jobs +end +function multi:getJobs() + return #self.Jobs +end +function multi:removeJob(name) + for i=#self.Jobs,1,-1 do + if self.Jobs[i][2]==name then + table.remove(self.Jobs,i) + end + end +end +function multi:FreeMainEvent() + self.func={} +end +function multi:connectFinal(func) + if self.Type=='event' then + self:OnEvent(func) + elseif self.Type=='alarm' then + self:OnRing(func) + elseif self.Type=='step' or self.Type=='tstep' then + self:OnEnd(func) + else + print("Warning!!! "..self.Type.." doesn't contain a Final Connection State! Use "..self.Type..":Break(function) to trigger it's final event!") + self:OnBreak(func) + end +end +function multi:Break() + self:Pause() + self.Active=nil + for i=1,#self.ender do + if self.ender[i] then + self.ender[i](self) + end + end +end +function multi:OnBreak(func) + table.insert(self.ender,func) +end +function multi:isPaused() + return not(self.Active) +end +function multi:isActive() + return self.Active +end +function multi:getType() + return self.Type +end +function multi:Sleep(n) + self:hold(n) +end +function multi:Pause() + if self.Type=='mainprocess' then + print("You cannot pause the main process. Doing so will stop all methods and freeze your program! However if you still want to use multi:_Pause()") + else + self.Active=false + if self.Parent.Mainloop[self.Id]~=nil then + table.remove(self.Parent.Mainloop,self.Id) + table.insert(self.Parent.Paused,self) + self.PId=#self.Parent.Paused + end + end +end +function multi:Resume() + if self.Type=='process' or self.Type=='mainprocess' then + self.Active=true + local c=self:getChildren() + for i=1,#c do + c[i]:Resume() + end + else + if self:isPaused() then + table.remove(self.Parent.Paused,self.PId) + table.insert(self.Parent.Mainloop,self) + self.Id=#self.Parent.Mainloop + self.Active=true + end + end +end +function multi:resurrect() + table.insert(self.Parent.Mainloop,self) + self.Active=true +end +function multi:Destroy() + if self.Type=='process' or self.Type=='mainprocess' then + local c=self:getChildren() + for i=1,#c do + self.OnObjectDestroyed:Fire(c[i]) + c[i]:Destroy() + end + else + for i=1,#self.Parent.Mainloop do + if self.Parent.Mainloop[i]==self then + self.Parent.OnObjectDestroyed:Fire(self) + table.remove(self.Parent.Mainloop,i) + break + end + end + self.Active=false + end +end + +function multi:hold(task) + self:Pause() + self.held=true + if type(task)=='number' then + local timer=multi:newTimer() + timer:Start() + while timer:Get()=self.set then + self:Pause() + self.Active=false + for i=1,#self.func do + self.func[i](self) + end + end + end + function c:Resume() + self.Parent.Resume(self) + self.timer:Resume() + end + function c:Reset(n) + if n then self.set=n end + self:Resume() + self.timer:Reset() + end + function c:OnRing(func) + table.insert(self.func,func) + end + function c:Pause() + self.timer:Pause() + self.Parent.Pause(self) + end + self:create(c) + return c +end +function multi:newLoop(func) + local c=self:newBase() + c.Type='loop' + c.Start=self.clock() + if func then + c.func={func} + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + for i=1,#self.func do + self.func[i](self.Parent.clock()-self.Start,self) + end + end + function c:OnLoop(func) + table.insert(self.func,func) + end + self:create(c) + return c +end +function multi:newUpdater(skip) + local c=self:newBase() + c.Type='updater' + c.pos=1 + c.skip=skip or 1 + function c:Act() + if self.pos>=self.skip then + self.pos=0 + for i=1,#self.func do + self.func[i](self) + end + end + self.pos=self.pos+1 + end + function c:setSkip(n) + self.skip=n + end + c.OnUpdate=self.OnMainConnect + self:create(c) + return c +end +function multi:newStep(start,reset,count,skip) + local c=self:newBase() + think=1 + c.Type='step' + c.pos=start or 1 + c.endAt=reset or math.huge + c.skip=skip or 0 + c.spos=0 + c.count=count or 1*think + c.funcE={} + c.funcS={} + c.start=start or 1 + if start~=nil and reset~=nil then + if start>reset then + think=-1 + end + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,spos=self.spos,count=self.count,start=self.start}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self~=nil then + if self.spos==0 then + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + self.spos=self.spos+1 + if self.spos>=self.skip then + self.spos=0 + end + end + c.Reset=c.Resume + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,1,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Update(start,reset,count,skip) + self.start=start or self.start + self.endAt=reset or self.endAt + self.skip=skip or self.skip + self.count=count or self.count + self:Resume() + end + self:create(c) + return c +end +function multi:newTStep(start,reset,count,set) + local c=self:newBase() + think=1 + c.Type='tstep' + c.Priority=self.Priority_Low + c.start=start or 1 + local reset = reset or math.huge + c.endAt=reset + c.pos=start or 1 + c.skip=skip or 0 + c.count=count or 1*think + c.funcE={} + c.timer=self.clock() + c.set=set or 1 + c.funcS={} + function c:Update(start,reset,count,set) + self.start=start or self.start + self.pos=self.start + self.endAt=reset or self.endAt + self.set=set or self.set + self.count=count or self.count or 1 + self.timer=self.clock() + self:Resume() + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:addBlock(self.funcE) + m:addBlock(self.funcS) + m:addBlock({pos=self.pos,endAt=self.endAt,skip=self.skip,timer=self.timer,count=self.count,start=self.start,set=self.set}) + m:addBlock(self.Active) + m:tofile(path) + end + function c:Act() + if self.clock()-self.timer>=self.set then + self:Reset() + if self.pos==self.start then + for fe=1,#self.funcS do + self.funcS[fe](self) + end + end + for i=1,#self.func do + self.func[i](self.pos,self) + end + self.pos=self.pos+self.count + if self.pos-self.count==self.endAt then + self:Pause() + for fe=1,#self.funcE do + self.funcE[fe](self) + end + self.pos=self.start + end + end + end + function c:OnStart(func) + table.insert(self.funcS,func) + end + function c:OnStep(func) + table.insert(self.func,func) + end + function c:OnEnd(func) + table.insert(self.funcE,func) + end + function c:Break() + self.Active=nil + end + function c:Reset(n) + if n then self.set=n end + self.timer=self.clock() + self:Resume() + end + self:create(c) + return c +end +function multi:newWatcher(namespace,name) + local function WatcherObj(ns,n) + if self.Type=='queue' then + print("Cannot create a watcher on a queue! Creating on 'multi' instead!") + self=multi + end + local c=self:newBase() + c.Type='watcher' + c.ns=ns + c.n=n + c.cv=ns[n] + function c:OnValueChanged(func) + table.insert(self.func,func) + end + function c:Act() + if self.cv~=self.ns[self.n] then + for i=1,#self.func do + self.func[i](self,self.cv,self.ns[self.n]) + end + self.cv=self.ns[self.n] + end + end + self:create(c) + return c + end + if type(namespace)~='table' and type(namespace)=='string' then + return WatcherObj(_G,namespace) + elseif type(namespace)=='table' and (type(name)=='string' or 'number') then + return WatcherObj(namespace,name) + else + print('Warning, invalid arguments! Nothing returned!') + end +end +-- Constructors [SEMI-ACTORS] +function multi:newThread(name,func) + local c={} + c.ref={} + c.Name=name + c.thread=coroutine.create(func) + c.sleep=1 + c.firstRunDone=false + c.timer=multi.scheduler:newTimer() + c.ref.Globals=self:linkDomain("Globals") + function c.ref:send(name,val) + ret=coroutine.yield({Name=name,Value=val}) + self:syncGlobals(ret) + end + function c.ref:get(name) + return self.Globals[name] + end + function c.ref:kill() + err=coroutine.yield({"_kill_"}) + if err then + error("Failed to kill a thread! Exiting...") + end + end + function c.ref:sleep(n) + if type(n)=="function" then + ret=coroutine.yield({"_hold_",n}) + self:syncGlobals(ret) + elseif type(n)=="number" then + n = tonumber(n) or 0 + ret=coroutine.yield({"_sleep_",n}) + self:syncGlobals(ret) + else + error("Invalid Type for sleep!") + end + end + function c.ref:syncGlobals(v) + self.Globals=v + end + table.insert(self:linkDomain("Threads"),c) + if not multi.scheduler:isActive() then + multi.scheduler:Resume() + end +end +function multi:newJob(func,name) + if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end + local c = {} + if self.Type=='process' then + setmetatable(c, self.Parent) + else + setmetatable(c, self) + end + c.Active=true + c.func={} + c.Id=0 + c.PId=0 + c.Parent=self + c.Type='job' + c.trigfunc=func or function() end + function c:Act() + self:trigfunc(self) + end + table.insert(self.Jobs,{c,name}) + if self.JobRunner==nil then + self.JobRunner=self:newAlarm(self.jobUS) + self.JobRunner:OnRing(function(self) + if #self.Parent.Jobs>0 then + if self.Parent.Jobs[1] then + self.Parent.Jobs[1][1]:Act() + table.remove(self.Parent.Jobs,1) + end + end + self:Reset(self.Parent.jobUS) + end) + end +end +function multi:newRange() + local selflink=self + local temp={ + getN = function(self) selflink:Do_Order() self.n=self.n+self.c if self.n>self.b then self.Link.held=false self.Link:Resume() return nil end return self.n end, + } + setmetatable(temp,{ + __call=function(self,a,b,c) + self.c=c or 1 + self.n=a-self.c + self.a=a + self.b=b + self.Link=selflink--.Parent.Mainloop[selflink.CID] or + self.Link:Pause() + self.Link.held=true + return function() return self:getN() end + end + }) + self:create(temp) + return temp +end +function multi:newCondition(func) + local c={['condition']=func} + self:create(c) + return c +end +-- Constructors [NON-ACTORS] +function multi:newFunction(func) + local c={} + c.func=func + mt={ + __index=multi, + __call=function(self,...) if self.Active then return self:func(...) end local t={...} return "PAUSED" end + } + c.Parent=self + function c:Pause() + self.Active=false + end + function c:Resume() + self.Active=true + end + setmetatable(c,mt) + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + c.time=0 + c.count=0 + function c:Start() + self.time=os.clock() + end + function c:Get() + return (os.clock()-self.time)+self.count + end + c.Reset=c.Start + function c:Pause() + self.time=self:Get() + end + function c:Resume() + self.time=os.clock()-self.time + end + function c:tofile(path) + local m=bin.new() + self.count=self.count+self:Get() + m:addBlock(self.Type) + m:addBlock(self.count) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newTask(func) + table.insert(self.Tasks,func) +end +function multi:newTrigger(func) + local c={} + c.Type='trigger' + c.trigfunc=func or function() end + function c:Fire(...) + self:trigfunc(self,...) + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.trigfunc) + m:tofile(path) + end + self:create(c) + return c +end +function multi:newConnection(protect) + local c={} + setmetatable(c,{__call=function(self,...) self:connect(...) end}) + c.Type='connector' + c.func={} + c.ID=0 + c.protect=protect or true + c.connections={} + c.fconnections={} + c.FC=0 + function c:fConnect(func) + local temp=self:connect(func) + table.insert(self.fconnections,temp) + self.FC=self.FC+1 + end + function c:getConnection(name,ingore) + if ingore then + return self.connections[name] or { + Fire=function() end -- if the connection doesn't exist lets call all of them or silently ingore + } + else + return self.connections[name] or self + end + end + function c:Fire(...) + local ret={} + for i=#self.func,1,-1 do + if self.protect then + local temp={pcall(self.func[i][1],...)} + if temp[1] then + table.remove(temp,1) + table.insert(ret,temp) + else + print(temp[2]) + end + else + table.insert(ret,{self.func[i][1](...)}) + end + end + return ret + end + function c:bind(t) + self.func=t + end + function c:remove() + self.func={} + end + function c:connect(func,name) + self.ID=self.ID+1 + table.insert(self.func,1,{func,self.ID}) + local temp = { + Link=self.func, + func=func, + ID=self.ID, + Parent=self, + Fire=function(self,...) +--~ if self.Parent.FC>0 then +--~ for i=1,#self.Parent.FC do +--~ self.Parent.FC[i]:Fire(...) +--~ end +--~ end + if self.Parent.protect then + local t=pcall(self.func,...) + if t then + return t + end + else + return self.func(...) + end + end, + remove=function(self) + for i=1,#self.Link do + if self.Link[i][2]~=nil then + if self.Link[i][2]==self.ID then + table.remove(self.Link,i) + self.remove=function() end + self.Link=nil + self.ID=nil + return true + end + end + end + end + } + if name then + self.connections[name]=temp + end + return temp + end + function c:tofile(path) + local m=bin.new() + m:addBlock(self.Type) + m:addBlock(self.func) + m:tofile(path) + end + return c +end +multi.OnObjectCreated=multi:newConnection() +multi.OnObjectDestroyed=multi:newConnection() +--Managers +function multi:mainloop() + for i=1,#self.Tasks do + self.Tasks[i](self) + end + rawset(self,'Start',self.clock()) + while self.Active do + self:Do_Order() + end + print("Did you call multi:Stop()? This method should not be used when using multi:mainloop()! You now need to restart the multiManager, by using multi:reboot() and calling multi:mainloop() again or by using multi:uManager()") +end +function multi._tFunc(self,dt) + for i=1,#self.Tasks do + self.Tasks[i](self) + end + if dt then + self.pump=true + end + self.pumpvar=dt + rawset(self,'Start',self.clock()) +end +function multi:uManager(dt) + if self.Active then + self:oneTime(self._tFunc,self,dt) + function self:uManager(dt) + self:Do_Order() + end + self:Do_Order() + end +end +--Thread Setup Stuff +multi:setDomainName("Threads") +multi:setDomainName("Globals") +-- Scheduler +multi.scheduler=multi:newUpdater() +multi.scheduler.Type="scheduler" +function multi.scheduler:setStep(n) + self.skip=tonumber(n) or 24 +end +multi.scheduler.Threads=multi:linkDomain("Threads") +multi.scheduler.Globals=multi:linkDomain("Globals") +multi.scheduler:OnUpdate(function(self) + for i=#self.Threads,1,-1 do + ret={} + if coroutine.status(self.Threads[i].thread)=="dead" then + table.remove(self.Threads,i) + else + if self.Threads[i].timer:Get()>=self.Threads[i].sleep then + if self.Threads[i].firstRunDone==false then + self.Threads[i].firstRunDone=true + self.Threads[i].timer:Start() + _,ret=coroutine.resume(self.Threads[i].thread,self.Threads[i].ref) + else + _,ret=coroutine.resume(self.Threads[i].thread,self.Globals) + end + if ret==true or ret==false then + print("Thread Ended!!!") + ret={} + end + end + if ret then + if ret[1]=="_kill_" then + table.remove(self.Threads,i) + elseif ret[1]=="_sleep_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=ret[2] + elseif ret[1]=="_hold_" then + self.Threads[i].timer:Reset() + self.Threads[i].sleep=math.huge + local event=multi:newEvent(ret[2]) + event.link=self.Threads[i] + event:OnEvent(function(evnt) + evnt.link.sleep=0 + end) + elseif ret.Name then + self.Globals[ret.Name]=ret.Value + end + end + end + end +end) +multi.scheduler:setStep() +multi.scheduler:Pause() +multi.OnError=multi:newConnection() +---------TESTS +multi:newThread("test",function(ref) + while true do + print(">!") + ref:sleep(1) + end +end) +multi:newThread("test",function(ref) + while true do + print("!<") + ref:sleep(1) + end +end) +multi:mainloop() diff --git a/rockspecs/multi-1.10-0.rockspec b/rockspecs/multi-1.10-0.rockspec index ab3bce0..565c181 100644 --- a/rockspecs/multi-1.10-0.rockspec +++ b/rockspecs/multi-1.10-0.rockspec @@ -1,33 +1,33 @@ -package = "multi" -version = "1.10.0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.10.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.10.0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.10.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.11-0.rockspec b/rockspecs/multi-1.11-0.rockspec index c7c9f3d..03b62fa 100644 --- a/rockspecs/multi-1.11-0.rockspec +++ b/rockspecs/multi-1.11-0.rockspec @@ -1,32 +1,32 @@ -package = "multi" -version = "1.11.0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.11.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes" -} -build = { - type = "builtin", - modules = { - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.11.0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.11.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes" +} +build = { + type = "builtin", + modules = { + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.8-2.rockspec b/rockspecs/multi-1.8-2.rockspec index 6ffbdf9..21fc602 100644 --- a/rockspecs/multi-1.8-2.rockspec +++ b/rockspecs/multi-1.8-2.rockspec @@ -1,30 +1,30 @@ -package = "multi" -version = "1.8-2" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.8.2", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" - } +package = "multi" +version = "1.8-2" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.8.2", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.8-3.rockspec b/rockspecs/multi-1.8-3.rockspec index e3e152f..e110d3c 100644 --- a/rockspecs/multi-1.8-3.rockspec +++ b/rockspecs/multi-1.8-3.rockspec @@ -1,30 +1,30 @@ -package = "multi" -version = "1.8-3" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.8.3", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" - } +package = "multi" +version = "1.8-3" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.8.3", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.8-4.rockspec b/rockspecs/multi-1.8-4.rockspec index 07d2b5a..3ed7265 100644 --- a/rockspecs/multi-1.8-4.rockspec +++ b/rockspecs/multi-1.8-4.rockspec @@ -1,30 +1,30 @@ -package = "multi" -version = "1.8-4" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.8.4", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" - } +package = "multi" +version = "1.8-4" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.8.4", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.8-5.rockspec b/rockspecs/multi-1.8-5.rockspec index 808cd24..dce590d 100644 --- a/rockspecs/multi-1.8-5.rockspec +++ b/rockspecs/multi-1.8-5.rockspec @@ -1,30 +1,30 @@ -package = "multi" -version = "1.8-5" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.8.5", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" - } +package = "multi" +version = "1.8-5" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.8.5", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.shared.shared"] = "multi/integration/shared/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.8-6.rockspec b/rockspecs/multi-1.8-6.rockspec index 1c64eda..cfb996d 100644 --- a/rockspecs/multi-1.8-6.rockspec +++ b/rockspecs/multi-1.8-6.rockspec @@ -1,30 +1,30 @@ -package = "multi" -version = "1.8-6" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.8.6", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes being lua 5.1 only! - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.8-6" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.8.6", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes being lua 5.1 only! + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.9-1.rockspec b/rockspecs/multi-1.9-1.rockspec index f637e5f..82d769b 100644 --- a/rockspecs/multi-1.9-1.rockspec +++ b/rockspecs/multi-1.9-1.rockspec @@ -1,31 +1,31 @@ -package = "multi" -version = "1.9-1" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.9.1", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1, < 5.2" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.9-1" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.9.1", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1, < 5.2" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.9-2.rockspec b/rockspecs/multi-1.9-2.rockspec index 57866b5..f4d4bfe 100644 --- a/rockspecs/multi-1.9-2.rockspec +++ b/rockspecs/multi-1.9-2.rockspec @@ -1,33 +1,33 @@ -package = "multi" -version = "1.9-2" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.9.2", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.9-2" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.9.2", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-1.9-3.rockspec b/rockspecs/multi-1.9-3.rockspec index 46b3517..c94780d 100644 --- a/rockspecs/multi-1.9-3.rockspec +++ b/rockspecs/multi-1.9-3.rockspec @@ -1,33 +1,33 @@ -package = "multi" -version = "1.9-3" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v1.9.3", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes" -} -build = { - type = "builtin", - modules = { - -- Note the required Lua syntax when listing submodules as keys - ["multi.init"] = "multi/init.lua", - ["multi.all"] = "multi/all.lua", - ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "1.9-3" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v1.9.3", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multiobjs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d. Optional) The core of the library works on lua 5.1+ however the systemthreading features are limited to 5.1 due to love2d and lua lanes and now luvit (See ReadMe on gotchas) being lua 5.1 only! + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes" +} +build = { + type = "builtin", + modules = { + -- Note the required Lua syntax when listing submodules as keys + ["multi.init"] = "multi/init.lua", + ["multi.all"] = "multi/all.lua", + ["multi.compat.backwards[1,5,0]"] = "multi/compat/backwards[1,5,0].lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-12.0-0.rockspec b/rockspecs/multi-12.0-0.rockspec index e792886..4472114 100644 --- a/rockspecs/multi-12.0-0.rockspec +++ b/rockspecs/multi-12.0-0.rockspec @@ -1,32 +1,32 @@ -package = "multi" -version = "12.0-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v12.0.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", - "lua-net" -} -build = { - type = "builtin", - modules = { - ["multi.init"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "12.0-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v12.0.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", + "lua-net" +} +build = { + type = "builtin", + modules = { + ["multi.init"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-12.2-0.rockspec b/rockspecs/multi-12.2-0.rockspec index 8f190e2..d6ebff1 100644 --- a/rockspecs/multi-12.2-0.rockspec +++ b/rockspecs/multi-12.2-0.rockspec @@ -1,32 +1,32 @@ -package = "multi" -version = "12.2-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v12.2.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", - "lua-net" -} -build = { - type = "builtin", - modules = { - ["multi.init"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "12.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v12.2.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", + "lua-net" +} +build = { + type = "builtin", + modules = { + ["multi.init"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-12.2-1.rockspec b/rockspecs/multi-12.2-1.rockspec index c110b89..fbe40a6 100644 --- a/rockspecs/multi-12.2-1.rockspec +++ b/rockspecs/multi-12.2-1.rockspec @@ -1,31 +1,31 @@ -package = "multi" -version = "12.2-1" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v12.2.1", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", -} -build = { - type = "builtin", - modules = { - ["multi.init"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "12.2-1" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v12.2.1", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi.init"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-13.0-0.rockspec b/rockspecs/multi-13.0-0.rockspec index 9737df5..bbb7dbd 100644 --- a/rockspecs/multi-13.0-0.rockspec +++ b/rockspecs/multi-13.0-0.rockspec @@ -1,31 +1,31 @@ -package = "multi" -version = "13.0-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v13.0.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", -} -build = { - type = "builtin", - modules = { - ["multi"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "13.0-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v13.0.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-13.1-0.rockspec b/rockspecs/multi-13.1-0.rockspec index ac6cacc..042362a 100644 --- a/rockspecs/multi-13.1-0.rockspec +++ b/rockspecs/multi-13.1-0.rockspec @@ -1,31 +1,31 @@ -package = "multi" -version = "13.1-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v13.1.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", -} -build = { - type = "builtin", - modules = { - ["multi"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.lua", - ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", - ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", - ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", - ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "13.1-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v13.1.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-14.0-0.rockspec b/rockspecs/multi-14.0-0.rockspec index fd85aff..3a2ab08 100644 --- a/rockspecs/multi-14.0-0.rockspec +++ b/rockspecs/multi-14.0-0.rockspec @@ -1,35 +1,35 @@ -package = "multi" -version = "14.0-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v14.0.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) - ]], - homepage = "https://github.com/rayaman/multi", - license = "MIT" -} -dependencies = { - "lua >= 5.1", - "bin", - "lanes", -} -build = { - type = "builtin", - modules = { - ["multi"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.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.luvitManager"] = "multi/integration/luvitManager.lua", - --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - ["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "14.0-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v14.0.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. From simple side by side code using multi-objs, to using coroutine based Threads and System threads(When you have lua lanes installed or are using love2d) + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1", + "bin", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.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.luvitManager"] = "multi/integration/luvitManager.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + ["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-14.1-0.rockspec b/rockspecs/multi-14.1-0.rockspec index 030e81a..4f0a357 100644 --- a/rockspecs/multi-14.1-0.rockspec +++ b/rockspecs/multi-14.1-0.rockspec @@ -1,35 +1,35 @@ -package = "multi" -version = "14.1-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v14.1.0", -} -description = { - summary = "Lua Multi tasking library", - detailed = [[ - This library contains many methods for multi tasking. Features non coroutine based multitasking, 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", - "bin", - "lanes", -} -build = { - type = "builtin", - modules = { - ["multi"] = "multi/init.lua", - ["multi.compat.love2d"] = "multi/compat/love2d.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.luvitManager"] = "multi/integration/luvitManager.lua", - --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - --["multi.integration.shared"] = "multi/integration/shared.lua" - } +package = "multi" +version = "14.1-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v14.1.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multitasking, 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", + "bin", + "lanes", +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.compat.love2d"] = "multi/compat/love2d.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.luvitManager"] = "multi/integration/luvitManager.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + --["multi.integration.shared"] = "multi/integration/shared.lua" + } } \ No newline at end of file diff --git a/rockspecs/multi-14.2-0.rockspec b/rockspecs/multi-14.2-0.rockspec index 313a79b..0463f36 100644 --- a/rockspecs/multi-14.2-0.rockspec +++ b/rockspecs/multi-14.2-0.rockspec @@ -1,34 +1,34 @@ -package = "multi" -version = "14.2-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v14.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 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.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.luvitManager"] = "multi/integration/luvitManager.lua", - --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", - } +package = "multi" +version = "14.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v14.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 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.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.luvitManager"] = "multi/integration/luvitManager.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.0-0.rockspec b/rockspecs/multi-15.0-0.rockspec index 1f3d52c..f3767c2 100644 --- a/rockspecs/multi-15.0-0.rockspec +++ b/rockspecs/multi-15.0-0.rockspec @@ -1,38 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.1-0.rockspec b/rockspecs/multi-15.1-0.rockspec index 68ec605..4dcefe7 100644 --- a/rockspecs/multi-15.1-0.rockspec +++ b/rockspecs/multi-15.1-0.rockspec @@ -1,42 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.2-0.rockspec b/rockspecs/multi-15.2-0.rockspec index ac62aa9..406d21e 100644 --- a/rockspecs/multi-15.2-0.rockspec +++ b/rockspecs/multi-15.2-0.rockspec @@ -1,39 +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", - } +package = "multi" +version = "15.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.0", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.2-1.rockspec b/rockspecs/multi-15.2-1.rockspec index 12c0c0c..7f09a75 100644 --- a/rockspecs/multi-15.2-1.rockspec +++ b/rockspecs/multi-15.2-1.rockspec @@ -1,39 +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", - } +package = "multi" +version = "15.2-1" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v15.2.1", +} +description = { + summary = "Lua Multi tasking library", + detailed = [[ + This library contains many methods for multi tasking. Features non coroutine based multi-tasking, coroutine based multi-tasking, and system threading (Requires use of an integration). + Check github for documentation. + ]], + homepage = "https://github.com/rayaman/multi", + license = "MIT" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["multi"] = "multi/init.lua", + ["multi.integration.lanesManager"] = "multi/integration/lanesManager/init.lua", + ["multi.integration.lanesManager.extensions"] = "multi/integration/lanesManager/extensions.lua", + ["multi.integration.lanesManager.threads"] = "multi/integration/lanesManager/threads.lua", + ["multi.integration.loveManager"] = "multi/integration/loveManager/init.lua", + ["multi.integration.loveManager.extensions"] = "multi/integration/loveManager/extensions.lua", + ["multi.integration.loveManager.threads"] = "multi/integration/loveManager/threads.lua", + --["multi.integration.lovrManager"] = "multi/integration/lovrManager/init.lua", + --["multi.integration.lovrManager.extensions"] = "multi/integration/lovrManager/extensions.lua", + --["multi.integration.lovrManager.threads"] = "multi/integration/lovrManager/threads.lua", + ["multi.integration.pesudoManager"] = "multi/integration/pesudoManager/init.lua", + ["multi.integration.pesudoManager.extensions"] = "multi/integration/pesudoManager/extensions.lua", + ["multi.integration.pesudoManager.threads"] = "multi/integration/pesudoManager/threads.lua", + ["multi.integration.luvitManager"] = "multi/integration/luvitManager.lua", + ["multi.integration.threading"] = "multi/integration/threading.lua", + --["multi.integration.networkManager"] = "multi/integration/networkManager.lua", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.3-0.rockspec b/rockspecs/multi-15.3-0.rockspec index 2cc71ba..44a3812 100644 --- a/rockspecs/multi-15.3-0.rockspec +++ b/rockspecs/multi-15.3-0.rockspec @@ -1,39 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-15.3-1.rockspec b/rockspecs/multi-15.3-1.rockspec index d811a82..2883428 100644 --- a/rockspecs/multi-15.3-1.rockspec +++ b/rockspecs/multi-15.3-1.rockspec @@ -1,39 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-16.0-0.rockspec b/rockspecs/multi-16.0-0.rockspec index b44f1f9..32eda23 100644 --- a/rockspecs/multi-16.0-0.rockspec +++ b/rockspecs/multi-16.0-0.rockspec @@ -1,42 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-16.0-1.rockspec b/rockspecs/multi-16.0-1.rockspec index 4ddb730..f014dcd 100644 --- a/rockspecs/multi-16.0-1.rockspec +++ b/rockspecs/multi-16.0-1.rockspec @@ -1,42 +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", - } +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-16.1-0.rockspec b/rockspecs/multi-16.1-0.rockspec index 87d0e08..4dd8821 100644 --- a/rockspecs/multi-16.1-0.rockspec +++ b/rockspecs/multi-16.1-0.rockspec @@ -1,42 +1,42 @@ -package = "multi" -version = "16.1-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v16.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" -} -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", - } +package = "multi" +version = "16.1-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v16.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" +} +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", + } } \ No newline at end of file diff --git a/rockspecs/multi-16.2-0.rockspec b/rockspecs/multi-16.2-0.rockspec index a7477ac..bca580e 100644 --- a/rockspecs/multi-16.2-0.rockspec +++ b/rockspecs/multi-16.2-0.rockspec @@ -1,42 +1,42 @@ -package = "multi" -version = "16.2-0" -source = { - url = "git://github.com/rayaman/multi.git", - tag = "v16.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"] = "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", - } +package = "multi" +version = "16.2-0" +source = { + url = "git://github.com/rayaman/multi.git", + tag = "v16.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"] = "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", + } } \ No newline at end of file diff --git a/tests/units/connections.lua b/tests/units/connections.lua index 70780c7..bfbf8f8 100644 --- a/tests/units/connections.lua +++ b/tests/units/connections.lua @@ -1,71 +1,71 @@ -function Test(multi, thread, t) - multi.print("Testing Connection operators") - - do - multi.print("Testing: conn1 + conn2") - local conn1 = multi:newConnection() - local conn2 = multi:newConnection() - local conn3 = conn1 + conn2 - local count = 0 - - conn3(function() - count = count + 1 - end) - - conn1:Fire() - t.Equal(1, count) - conn2:Fire() - t.Equal(2, count) - end - - do - multi.print("Testing: conn1 * conn2") - local conn1 = multi:newConnection() - local conn2 = multi:newConnection() - local conn3 = conn1 * conn2 - local count = 0 - - conn3(function() - count = count + 1 - end) - - conn1:Fire() - t.Equal(0, count) - conn2:Fire() - t.Equal(1, count) - end - - do - multi.print("Testing: conn .. function") - local called = false - local conn1 = multi:newConnection() - local conn2 = conn1 .. function() called = true end - - conn1(function() - t.False(called) - end) - - conn2:Fire() - t.True(called) - end - - do - multi.print("function .. conn") - local status = false - local conn1 = multi:newConnection() - local conn2 = function(test) return test end .. conn1 - - conn1(function() - status = true - end) - - conn2:Fire(false) - t.False(status) - conn2:Fire(true) - t.True(status) - end -end - -return { - Test = Test +function Test(multi, thread, t) + multi.print("Testing Connection operators") + + do + multi.print("Testing: conn1 + conn2") + local conn1 = multi:newConnection() + local conn2 = multi:newConnection() + local conn3 = conn1 + conn2 + local count = 0 + + conn3(function() + count = count + 1 + end) + + conn1:Fire() + t.Equal(1, count) + conn2:Fire() + t.Equal(2, count) + end + + do + multi.print("Testing: conn1 * conn2") + local conn1 = multi:newConnection() + local conn2 = multi:newConnection() + local conn3 = conn1 * conn2 + local count = 0 + + conn3(function() + count = count + 1 + end) + + conn1:Fire() + t.Equal(0, count) + conn2:Fire() + t.Equal(1, count) + end + + do + multi.print("Testing: conn .. function") + local called = false + local conn1 = multi:newConnection() + local conn2 = conn1 .. function() called = true end + + conn1(function() + t.False(called) + end) + + conn2:Fire() + t.True(called) + end + + do + multi.print("function .. conn") + local status = false + local conn1 = multi:newConnection() + local conn2 = function(test) return test end .. conn1 + + conn1(function() + status = true + end) + + conn2:Fire(false) + t.False(status) + conn2:Fire(true) + t.True(status) + end +end + +return { + Test = Test } \ No newline at end of file diff --git a/tests/vscode-debuggee.lua b/tests/vscode-debuggee.lua index 61b47fb..9b76276 100644 --- a/tests/vscode-debuggee.lua +++ b/tests/vscode-debuggee.lua @@ -1,1102 +1,1102 @@ -local debuggee = {} - -local socket = require 'socket.core' -local json -local handlers = {} -local sock -local directorySeperator = package.config:sub(1,1) -local sourceBasePath = '.' -local storedVariables = {} -local nextVarRef = 1 -local baseDepth -local breaker -local sendEvent -local dumpCommunication = false -local ignoreFirstFrameInC = false -local debugTargetCo = nil -local redirectedPrintFunction = nil - -local onError = nil -local addUserdataVar = nil - -local function defaultOnError(e) - print('****************************************************') - print(e) - print('****************************************************') -end - -local function valueToString(value, depth) - local str = '' - depth = depth or 0 - local t = type(value) - if t == 'table' then - str = str .. '{\n' - for k, v in pairs(value) do - str = str .. string.rep(' ', depth + 1) .. '[' .. valueToString(k) ..']' .. ' = ' .. valueToString(v, depth + 1) .. ',\n' - end - str = str .. string.rep(' ', depth) .. '}' - elseif t == 'string' then - str = str .. '"' .. tostring(value) .. '"' - else - str = str .. tostring(value) - end - return str -end - -------------------------------------------------------------------------------- -local sethook = debug.sethook -debug.sethook = nil - -local cocreate = coroutine.create -coroutine.create = function(f) - local c = cocreate(f) - debuggee.addCoroutine(c) - return c -end - -------------------------------------------------------------------------------- -local function debug_getinfo(depth, what) - if debugTargetCo then - return debug.getinfo(debugTargetCo, depth, what) - else - return debug.getinfo(depth + 1, what) - end -end - -------------------------------------------------------------------------------- -local function debug_getlocal(depth, i) - if debugTargetCo then - return debug.getlocal(debugTargetCo, depth, i) - else - return debug.getlocal(depth + 1, i) - end -end - -------------------------------------------------------------------------------- -local DO_TEST = false - -------------------------------------------------------------------------------- --- chunkname matching {{{ -local function getMatchCount(a, b) - local n = math.min(#a, #b) - for i = 0, n - 1 do - if a[#a - i] == b[#b - i] then - -- pass - else - return i - end - end - return n -end -if DO_TEST then - assert(getMatchCount({'a','b','c'}, {'a','b','c'}) == 3) - assert(getMatchCount({'b','c'}, {'a','b','c'}) == 2) - assert(getMatchCount({'a','b','c'}, {'b','c'}) == 2) - assert(getMatchCount({}, {'a','b','c'}) == 0) - assert(getMatchCount({'a','b','c'}, {}) == 0) - assert(getMatchCount({'a','b','c'}, {'a','b','c','d'}) == 0) -end - -local function splitChunkName(s) - if string.sub(s, 1, 1) == '@' then - s = string.sub(s, 2) - end - - local a = {} - for word in string.gmatch(s, '[^/\\]+') do - a[#a + 1] = string.lower(word) - end - return a -end -if DO_TEST then - local a = splitChunkName('@.\\vscode-debuggee.lua') - assert(#a == 2) - assert(a[1] == '.') - assert(a[2] == 'vscode-debuggee.lua') - - local a = splitChunkName('@C:\\dev\\VSCodeLuaDebug\\debuggee/lua\\socket.lua') - assert(#a == 6) - assert(a[1] == 'c:') - assert(a[2] == 'dev') - assert(a[3] == 'vscodeluadebug') - assert(a[4] == 'debuggee') - assert(a[5] == 'lua') - assert(a[6] == 'socket.lua') - - local a = splitChunkName('@main.lua') - assert(#a == 1) - assert(a[1] == 'main.lua') -end --- chunkname matching }}} - --- path control {{{ -local Path = {} - -function Path.isAbsolute(a) - local firstChar = string.sub(a, 1, 1) - if firstChar == '/' or firstChar == '\\' then - return true - end - - if string.match(a, '^%a%:[/\\]') then - return true - end - - return false -end - -local np_pat1, np_pat2 = ('[^SEP:]+SEP%.%.SEP?'):gsub('SEP', directorySeperator), ('SEP+%.?SEP'):gsub('SEP', directorySeperator) -function Path.normpath(path) - path = path:gsub('[/\\]', directorySeperator) - - if directorySeperator == '\\' then - local unc = ('SEPSEP'):gsub('SEP', directorySeperator) -- UNC - if path:match('^'..unc) then - return unc..Path.normpath(path:sub(3)) - end - end - - local k - repeat -- /./ -> / - path,k = path:gsub(np_pat2, directorySeperator) - until k == 0 - repeat -- A/../ -> (empty) - path,k = path:gsub(np_pat1, '', 1) - until k == 0 - if path == '' then - path = '.' - end - return path -end - -function Path.concat(a, b) - -- normalize a - local lastChar = string.sub(a, #a, #a) - if not (lastChar == '/' or lastChar == '\\') then - a = a .. directorySeperator - end - - -- normalize b - if string.match(b, '^%.%\\') or string.match(b, '^%.%/') then - b = string.sub(b, 3) - end - - return a .. b -end - -function Path.toAbsolute(base, sub) - if Path.isAbsolute(sub) then - return Path.normpath(sub) - else - return Path.normpath(Path.concat(base, sub)) - end -end - -if DO_TEST then - assert(Path.isAbsolute('c:\\asdf\\afsd')) - assert(Path.isAbsolute('c:/asdf/afsd')) - if directorySeperator == '\\' then - assert(Path.toAbsolute('c:\\asdf', 'fdsf') == 'c:\\asdf\\fdsf') - assert(Path.toAbsolute('c:\\asdf', '.\\fdsf') == 'c:\\asdf\\fdsf') - assert(Path.toAbsolute('c:\\asdf', '..\\fdsf') == 'c:\\fdsf') - assert(Path.toAbsolute('c:\\asdf', 'c:\\fdsf') == 'c:\\fdsf') - assert(Path.toAbsolute('c:/asdf', '../fdsf') == 'c:\\fdsf') - assert(Path.toAbsolute('\\\\HOST\\asdf', '..\\fdsf') == '\\\\HOST\\fdsf') - elseif directorySeperator == '/' then - assert(Path.toAbsolute('/usr/bin/asdf', 'fdsf') == '/usr/bin/asdf/fdsf') - assert(Path.toAbsolute('/usr/bin/asdf', './fdsf') == '/usr/bin/asdf/fdsf') - assert(Path.toAbsolute('/usr/bin/asdf', '../fdsf') == '/usr/bin/fdsf') - assert(Path.toAbsolute('/usr/bin/asdf', '/usr/bin/fdsf') == '/usr/bin/fdsf') - assert(Path.toAbsolute('\\usr\\bin\\asdf', '..\\fdsf') == '/usr/bin/fdsf') - end -end --- path control }}} - -local coroutineSet = {} -setmetatable(coroutineSet, { __mode = 'v' }) - -------------------------------------------------------------------------------- --- network utility {{{ -local function sendFully(str) - local first = 1 - while first <= #str do - local sent = sock:send(str, first) - if sent and sent > 0 then - first = first + sent; - else - error('sock:send() returned < 0') - end - end -end - --- send log to debug console -local function logToDebugConsole(output, category) - local dumpMsg = { - event = 'output', - type = 'event', - body = { - category = category or 'console', - output = output - } - } - local dumpBody = json.encode(dumpMsg) - sendFully('#' .. #dumpBody .. '\n' .. dumpBody) -end - --- pure mode {{{ -local function createHaltBreaker() - -- chunkname matching { - local loadedChunkNameMap = {} - for chunkname, _ in pairs(debug.getchunknames()) do - loadedChunkNameMap[chunkname] = splitChunkName(chunkname) - end - - local function findMostSimilarChunkName(path) - local splitedReqPath = splitChunkName(path) - local maxMatchCount = 0 - local foundChunkName = nil - for chunkName, splitted in pairs(loadedChunkNameMap) do - local count = getMatchCount(splitedReqPath, splitted) - if (count > maxMatchCount) then - maxMatchCount = count - foundChunkName = chunkName - end - end - return foundChunkName - end - -- chunkname matching } - - local lineBreakCallback = nil - local function updateCoroutineHook(c) - if lineBreakCallback then - sethook(c, lineBreakCallback, 'l') - else - sethook(c) - end - end - local function sethalt(cname, ln) - for i = ln, ln + 10 do - if debug.sethalt(cname, i) then - return i - end - end - return nil - end - return { - setBreakpoints = function(path, lines) - local foundChunkName = findMostSimilarChunkName(path) - local verifiedLines = {} - - if foundChunkName then - debug.clearhalt(foundChunkName) - for _, ln in ipairs(lines) do - verifiedLines[ln] = sethalt(foundChunkName, ln) - end - end - - return verifiedLines - end, - - setLineBreak = function(callback) - if callback then - sethook(callback, 'l') - else - sethook() - end - - lineBreakCallback = callback - for cid, c in pairs(coroutineSet) do - updateCoroutineHook(c) - end - end, - - coroutineAdded = function(c) - updateCoroutineHook(c) - end, - - stackOffset = - { - enterDebugLoop = 6, - halt = 6, - step = 4, - stepDebugLoop = 6 - } - } -end - -local function createPureBreaker() - local lineBreakCallback = nil - local breakpointsPerPath = {} - local chunknameToPathCache = {} - - local function chunkNameToPath(chunkname) - local cached = chunknameToPathCache[chunkname] - if cached then - return cached - end - - local splitedReqPath = splitChunkName(chunkname) - local maxMatchCount = 0 - local foundPath = nil - for path, _ in pairs(breakpointsPerPath) do - local splitted = splitChunkName(path) - local count = getMatchCount(splitedReqPath, splitted) - if (count > maxMatchCount) then - maxMatchCount = count - foundPath = path - end - end - - if foundPath then - chunknameToPathCache[chunkname] = foundPath - end - return foundPath - end - - local entered = false - local function hookfunc() - if entered then return false end - entered = true - - if lineBreakCallback then - lineBreakCallback() - end - - local info = debug_getinfo(2, 'Sl') - if info then - local path = chunkNameToPath(info.source) - if path then - path = string.lower(path) - end - local bpSet = breakpointsPerPath[path] - if bpSet and bpSet[info.currentline] then - _G.__halt__() - end - end - - entered = false - end - sethook(hookfunc, 'l') - - return { - setBreakpoints = function(path, lines) - local t = {} - local verifiedLines = {} - for _, ln in ipairs(lines) do - t[ln] = true - verifiedLines[ln] = ln - end - if path then - path = string.lower(path) - end - breakpointsPerPath[path] = t - return verifiedLines - end, - - setLineBreak = function(callback) - lineBreakCallback = callback - end, - - coroutineAdded = function(c) - sethook(c, hookfunc, 'l') - end, - - stackOffset = - { - enterDebugLoop = 6, - halt = 7, - step = 4, - stepDebugLoop = 7 - } - } -end --- pure mode }}} - - --- 센드는 블럭이어도 됨. -local function sendMessage(msg) - local body = json.encode(msg) - - if dumpCommunication then - logToDebugConsole('[SENDING] ' .. valueToString(msg)) - end - - sendFully('#' .. #body .. '\n' .. body) -end - --- 리시브는 블럭이 아니어야 할 거 같은데... 음... 블럭이어도 괜찮나? -local function recvMessage() - local header = sock:receive('*l') - if (header == nil) then - -- 디버거가 떨어진 상황 - return nil - end - if (string.sub(header, 1, 1) ~= '#') then - error('헤더 이상함:' .. header) - end - - local bodySize = tonumber(header:sub(2)) - local body = sock:receive(bodySize) - - return json.decode(body) -end --- network utility }}} - -------------------------------------------------------------------------------- -local function debugLoop() - storedVariables = {} - nextVarRef = 1 - while true do - local msg = recvMessage() - if msg then - if dumpCommunication then - logToDebugConsole('[RECEIVED] ' .. valueToString(msg), 'stderr') - end - - local fn = handlers[msg.command] - if fn then - local rv = fn(msg) - - -- continue인데 break하는 게 역설적으로 느껴지지만 - -- 디버그 루프를 탈출(break)해야 정상 실행 흐름을 계속(continue)할 수 있지.. - if (rv == 'CONTINUE') then - break; - end - else - --print('UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) - end - else - -- 디버그 중에 디버거가 떨어졌다. - -- print펑션을 리다이렉트 한경우에는 원래대로 돌려놓는다 - if redirectedPrintFunction then - _G.print = redirectedPrintFunction - end - break - end - end - storedVariables = {} - nextVarRef = 1 -end - -------------------------------------------------------------------------------- -local sockArray = {} -function debuggee.start(jsonLib, config) - json = jsonLib - assert(jsonLib) - - config = config or {} - local connectTimeout = config.connectTimeout or 5.0 - local controllerHost = config.controllerHost or 'localhost' - local controllerPort = config.controllerPort or 56789 - onError = config.onError or defaultOnError - addUserdataVar = config.addUserdataVar or function() return end - local redirectPrint = config.redirectPrint or false - dumpCommunication = config.dumpCommunication or false - ignoreFirstFrameInC = config.ignoreFirstFrameInC or false - if not config.luaStyleLog then - valueToString = function(value) return json.encode(value) end - end - - local breakerType - if debug.sethalt then - breaker = createHaltBreaker() - breakerType = 'halt' - else - breaker = createPureBreaker() - breakerType = 'pure' - end - - local err - sock, err = socket.tcp() - if not sock then error(err) end - sockArray = { sock } - if sock.settimeout then sock:settimeout(connectTimeout) end - local res, err = sock:connect(controllerHost, tostring(controllerPort)) - if not res then - sock:close() - sock = nil - return false, breakerType - end - - if sock.settimeout then sock:settimeout() end - sock:setoption('tcp-nodelay', true) - - local initMessage = recvMessage() - assert(initMessage and initMessage.command == 'welcome') - sourceBasePath = initMessage.sourceBasePath - directorySeperator = initMessage.directorySeperator - - if redirectPrint then - redirectedPrintFunction = _G.print -- 디버거가 떨어질때를 대비해서 보관한다 - _G.print = function(...) - local t = { n = select("#", ...), ... } - for i = 1, #t do - t[i] = tostring(t[i]) - end - sendEvent( - 'output', - { - category = 'stdout', - output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. - }) - end - end - - debugLoop() - return true, breakerType -end - -------------------------------------------------------------------------------- -function debuggee.poll() - if not sock then return end - - -- Processes commands in the queue. - -- Immediately returns when the queue is/became empty. - while true do - local r, w, e = socket.select(sockArray, nil, 0) - if e == 'timeout' then break end - - local msg = recvMessage() - if msg then - if dumpCommunication then - logToDebugConsole('[POLL-RECEIVED] ' .. valueToString(msg), 'stderr') - end - - if msg.command == 'pause' then - debuggee.enterDebugLoop(1) - return - end - - local fn = handlers[msg.command] - if fn then - local rv = fn(msg) - -- Ignores rv, because this loop never blocks except explicit pause command. - else - --print('POLL-UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) - end - else - break - end - end -end - -------------------------------------------------------------------------------- -local function getCoroutineId(c) - -- 'thread: 011DD5B0' - -- 12345678^ - local threadIdHex = string.sub(tostring(c), 9) - return tonumber(threadIdHex, 16) -end - -------------------------------------------------------------------------------- -function debuggee.addCoroutine(c) - local cid = getCoroutineId(c) - coroutineSet[cid] = c - breaker.coroutineAdded(c) -end - -------------------------------------------------------------------------------- -local function sendSuccess(req, body) - sendMessage({ - command = req.command, - success = true, - request_seq = req.seq, - type = "response", - body = body - }) -end - -------------------------------------------------------------------------------- -local function sendFailure(req, msg) - sendMessage({ - command = req.command, - success = false, - request_seq = req.seq, - type = "response", - message = msg - }) -end - -------------------------------------------------------------------------------- -sendEvent = function(eventName, body) - sendMessage({ - event = eventName, - type = "event", - body = body - }) -end - -------------------------------------------------------------------------------- -local function currentThreadId() ---[[ - local threadId = 0 - if coroutine.running() then - end - return threadId -]] - return 0 -end - -------------------------------------------------------------------------------- -local function startDebugLoop() - sendEvent( - 'stopped', - { - reason = 'breakpoint', - threadId = currentThreadId(), - allThreadsStopped = true - }) - - local status, err = pcall(debugLoop) - if not status then - onError(err) - end -end - -------------------------------------------------------------------------------- -_G.__halt__ = function() - baseDepth = breaker.stackOffset.halt - startDebugLoop() -end - -------------------------------------------------------------------------------- -function debuggee.enterDebugLoop(depthOrCo, what) - if sock == nil then - return false - end - - if what then - sendEvent( - 'output', - { - category = 'stderr', - output = what, - }) - end - - if type(depthOrCo) == 'thread' then - baseDepth = 0 - debugTargetCo = depthOrCo - elseif type(depthOrCo) == 'table' then - baseDepth = (depthOrCo.depth or 0) - debugTargetCo = depthOrCo.co - else - baseDepth = (depthOrCo or 0) + breaker.stackOffset.enterDebugLoop - debugTargetCo = nil - end - startDebugLoop() - return true -end - -------------------------------------------------------------------------------- --- Function for printing on vscode debug console --- First parameter 'category' can colorizes print text -function debuggee.print(category, ...) - if sock == nil then - return false - end - local t = { ... } - for i = 1, #t do - t[i] = tostring(t[i]) - end - - local categoryVscodeConsole = 'stdout' - if category == 'warning' then - categoryVscodeConsole = 'console' -- yellow - elseif category == 'error' then - categoryVscodeConsole = 'stderr' -- red - elseif category == 'log' then - categoryVscodeConsole = 'stdout' -- white - end - - sendEvent( - 'output', - { - category = categoryVscodeConsole, - output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. - }) -end - -------------------------------------------------------------------------------- --- ★★★ https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts -------------------------------------------------------------------------------- - -------------------------------------------------------------------------------- -function handlers.setBreakpoints(req) - local bpLines = {} - for _, bp in ipairs(req.arguments.breakpoints) do - bpLines[#bpLines + 1] = bp.line - end - - local verifiedLines = breaker.setBreakpoints( - req.arguments.source.path, - bpLines) - - local breakpoints = {} - for i, ln in ipairs(bpLines) do - breakpoints[i] = { - verified = (verifiedLines[ln] ~= nil), - line = verifiedLines[ln] - } - end - - sendSuccess(req, { - breakpoints = breakpoints - }) -end - -------------------------------------------------------------------------------- -function handlers.configurationDone(req) - sendSuccess(req, {}) - return 'CONTINUE' -end - -------------------------------------------------------------------------------- -function handlers.threads(req) - local c = coroutine.running() - - local mainThread = { - id = currentThreadId(), - name = (c and tostring(c)) or "main" - } - - sendSuccess(req, { - threads = { mainThread } - }) -end - -------------------------------------------------------------------------------- -function handlers.stackTrace(req) - assert(req.arguments.threadId == 0) - - local stackFrames = {} - local firstFrame = (req.arguments.startFrame or 0) + baseDepth - local lastFrame = (req.arguments.levels and (req.arguments.levels ~= 0)) - and (firstFrame + req.arguments.levels - 1) - or (9999) - - -- if firstframe function of stack is C function, ignore it. - if ignoreFirstFrameInC then - local info = debug_getinfo(firstFrame, 'lnS') - if info and info.what == "C" then - firstFrame = firstFrame + 1 - end - end - - for i = firstFrame, lastFrame do - local info = debug_getinfo(i, 'lnS') - if (info == nil) then break end - --print(json.encode(info)) - - local src = info.source - if string.sub(src, 1, 1) == '@' then - src = string.sub(src, 2) -- 앞의 '@' 떼어내기 - end - - local name - if info.name then - name = info.name .. ' (' .. (info.namewhat or '?') .. ')' - else - name = '?' - end - - local sframe = { - name = name, - source = { - name = nil, - path = Path.toAbsolute(sourceBasePath, src) - }, - column = 1, - line = info.currentline or 1, - id = i, - } - stackFrames[#stackFrames + 1] = sframe - end - - sendSuccess(req, { - stackFrames = stackFrames - }) -end - -------------------------------------------------------------------------------- -local scopeTypes = { - Locals = 1, - Upvalues = 2, - Globals = 3, -} -function handlers.scopes(req) - local depth = req.arguments.frameId - - local scopes = {} - local function addScope(name) - scopes[#scopes + 1] = { - name = name, - expensive = false, - variablesReference = depth * 1000000 + scopeTypes[name] - } - end - - addScope('Locals') - addScope('Upvalues') - addScope('Globals') - - sendSuccess(req, { - scopes = scopes - }) -end - -------------------------------------------------------------------------------- -local function registerVar(varNameCount, name_, value, noQuote) - local ty = type(value) - local name - if type(name_) == 'number' then - name = '[' .. name_ .. ']' - else - name = tostring(name_) - end - if varNameCount[name] then - varNameCount[name] = varNameCount[name] + 1 - name = name .. ' (' .. varNameCount[name] .. ')' - else - varNameCount[name] = 1 - end - - local item = { - name = name, - type = ty - } - - if (ty == 'string' and (not noQuote)) then - item.value = '"' .. value .. '"' - else - item.value = tostring(value) - end - - if (ty == 'table') or - (ty == 'function') or - (ty == 'userdata') then - storedVariables[nextVarRef] = value - item.variablesReference = nextVarRef - nextVarRef = nextVarRef + 1 - else - item.variablesReference = -1 - end - - return item -end - -------------------------------------------------------------------------------- -function handlers.variables(req) - local varRef = req.arguments.variablesReference - local variables = {} - local varNameCount = {} - local function addVar(name, value, noQuote) - variables[#variables + 1] = registerVar(varNameCount, name, value, noQuote) - end - - if (varRef >= 1000000) then - -- Scope. - local depth = math.floor(varRef / 1000000) - local scopeType = varRef % 1000000 - if scopeType == scopeTypes.Locals then - for i = 1, 9999 do - local name, value = debug_getlocal(depth, i) - if name == nil then break end - addVar(name, value, nil) - end - elseif scopeType == scopeTypes.Upvalues then - local info = debug_getinfo(depth, 'f') - if info and info.func then - for i = 1, 9999 do - local name, value = debug.getupvalue(info.func, i) - if name == nil then break end - addVar(name, value, nil) - end - end - elseif scopeType == scopeTypes.Globals then - for name, value in pairs(_G) do - addVar(name, value) - end - table.sort(variables, function(a, b) return a.name < b.name end) - end - else - -- Expansion. - local var = storedVariables[varRef] - if type(var) == 'table' then - for k, v in pairs(var) do - addVar(k, v) - end - table.sort(variables, function(a, b) - local aNum, aMatched = string.gsub(a.name, '^%[(%d+)%]$', '%1') - local bNum, bMatched = string.gsub(b.name, '^%[(%d+)%]$', '%1') - - if (aMatched == 1) and (bMatched == 1) then - -- both are numbers. compare numerically. - return tonumber(aNum) < tonumber(bNum) - elseif aMatched == bMatched then - -- both are strings. compare alphabetically. - return a.name < b.name - else - -- string comes first. - return aMatched < bMatched - end - end) - elseif type(var) == 'function' then - local info = debug.getinfo(var, 'S') - addVar('(source)', tostring(info.short_src), true) - addVar('(line)', info.linedefined) - - for i = 1, 9999 do - local name, value = debug.getupvalue(var, i) - if name == nil then break end - addVar(name, value) - end - elseif type(var) == 'userdata' then - addUserdataVar(var, addVar) - end - - local mt = getmetatable(var) - if mt then - addVar("(metatable)", mt) - end - end - - sendSuccess(req, { - variables = variables - }) -end - -------------------------------------------------------------------------------- -function handlers.continue(req) - sendSuccess(req, {}) - return 'CONTINUE' -end - -------------------------------------------------------------------------------- -local function stackHeight() - for i = 1, 9999999 do - if (debug_getinfo(i, '') == nil) then - return i - end - end -end - -------------------------------------------------------------------------------- -local stepTargetHeight = nil -local function step() - if (stepTargetHeight == nil) or (stackHeight() <= stepTargetHeight) then - breaker.setLineBreak(nil) - baseDepth = breaker.stackOffset.stepDebugLoop - startDebugLoop() - end -end - -------------------------------------------------------------------------------- -function handlers.next(req) - stepTargetHeight = stackHeight() - breaker.stackOffset.step - breaker.setLineBreak(step) - sendSuccess(req, {}) - return 'CONTINUE' -end - -------------------------------------------------------------------------------- -function handlers.stepIn(req) - stepTargetHeight = nil - breaker.setLineBreak(step) - sendSuccess(req, {}) - return 'CONTINUE' -end - -------------------------------------------------------------------------------- -function handlers.stepOut(req) - stepTargetHeight = stackHeight() - (breaker.stackOffset.step + 1) - breaker.setLineBreak(step) - sendSuccess(req, {}) - return 'CONTINUE' -end - -------------------------------------------------------------------------------- -function handlers.evaluate(req) - -- 실행할 소스 코드 준비 - local sourceCode = req.arguments.expression - if string.sub(sourceCode, 1, 1) == '!' then - sourceCode = string.sub(sourceCode, 2) - else - sourceCode = 'return (' .. sourceCode .. ')' - end - - -- 환경 준비. - -- 뭘 요구할지 모르니까 로컬, 업밸류, 글로벌을 죄다 복사해둔다. - -- 우선순위는 글로벌-업밸류-로컬 순서니까 - -- 그 반대로 갖다놓아서 나중 것이 앞의 것을 덮어쓰게 한다. - local depth = req.arguments.frameId - local tempG = {} - local declared = {} - local function set(k, v) - tempG[k] = v - declared[k] = true - end - - for name, value in pairs(_G) do - set(name, value) - end - - if depth then - local info = debug_getinfo(depth, 'f') - if info and info.func then - for i = 1, 9999 do - local name, value = debug.getupvalue(info.func, i) - if name == nil then break end - set(name, value) - end - end - - for i = 1, 9999 do - local name, value = debug_getlocal(depth, i) - if name == nil then break end - set(name, value) - end - else - -- VSCode가 depth를 안 보낼 수도 있다. - -- 특정 스택 프레임을 선택하지 않은, 전역 이름만 조회하는 경우이다. - end - local mt = { - __newindex = function() error('assignment not allowed', 2) end, - __index = function(t, k) if not declared[k] then error('not declared', 2) end end - } - setmetatable(tempG, mt) - - -- 파싱 - -- loadstring for Lua 5.1 - -- load for Lua 5.2 and 5.3(supports the private environment's load function) - local fn, err = (loadstring or load)(sourceCode, 'X', nil, tempG) - if fn == nil then - sendFailure(req, string.gsub(err, '^%[string %"X%"%]%:%d+%: ', '')) - return - end - - -- 실행하고 결과 송신 - if setfenv ~= nil then - -- Only for Lua 5.1 - setfenv(fn, tempG) - end - - local success, aux = pcall(fn) - if not success then - aux = aux or '' -- Execution of 'error()' returns nil as aux - sendFailure(req, string.gsub(aux, '^%[string %"X%"%]%:%d+%: ', '')) - return - end - - local varNameCount = {} - local item = registerVar(varNameCount, '', aux) - - sendSuccess(req, { - result = item.value, - type = item.type, - variablesReference = item.variablesReference - }) -end - -------------------------------------------------------------------------------- -return debuggee +local debuggee = {} + +local socket = require 'socket.core' +local json +local handlers = {} +local sock +local directorySeperator = package.config:sub(1,1) +local sourceBasePath = '.' +local storedVariables = {} +local nextVarRef = 1 +local baseDepth +local breaker +local sendEvent +local dumpCommunication = false +local ignoreFirstFrameInC = false +local debugTargetCo = nil +local redirectedPrintFunction = nil + +local onError = nil +local addUserdataVar = nil + +local function defaultOnError(e) + print('****************************************************') + print(e) + print('****************************************************') +end + +local function valueToString(value, depth) + local str = '' + depth = depth or 0 + local t = type(value) + if t == 'table' then + str = str .. '{\n' + for k, v in pairs(value) do + str = str .. string.rep(' ', depth + 1) .. '[' .. valueToString(k) ..']' .. ' = ' .. valueToString(v, depth + 1) .. ',\n' + end + str = str .. string.rep(' ', depth) .. '}' + elseif t == 'string' then + str = str .. '"' .. tostring(value) .. '"' + else + str = str .. tostring(value) + end + return str +end + +------------------------------------------------------------------------------- +local sethook = debug.sethook +debug.sethook = nil + +local cocreate = coroutine.create +coroutine.create = function(f) + local c = cocreate(f) + debuggee.addCoroutine(c) + return c +end + +------------------------------------------------------------------------------- +local function debug_getinfo(depth, what) + if debugTargetCo then + return debug.getinfo(debugTargetCo, depth, what) + else + return debug.getinfo(depth + 1, what) + end +end + +------------------------------------------------------------------------------- +local function debug_getlocal(depth, i) + if debugTargetCo then + return debug.getlocal(debugTargetCo, depth, i) + else + return debug.getlocal(depth + 1, i) + end +end + +------------------------------------------------------------------------------- +local DO_TEST = false + +------------------------------------------------------------------------------- +-- chunkname matching {{{ +local function getMatchCount(a, b) + local n = math.min(#a, #b) + for i = 0, n - 1 do + if a[#a - i] == b[#b - i] then + -- pass + else + return i + end + end + return n +end +if DO_TEST then + assert(getMatchCount({'a','b','c'}, {'a','b','c'}) == 3) + assert(getMatchCount({'b','c'}, {'a','b','c'}) == 2) + assert(getMatchCount({'a','b','c'}, {'b','c'}) == 2) + assert(getMatchCount({}, {'a','b','c'}) == 0) + assert(getMatchCount({'a','b','c'}, {}) == 0) + assert(getMatchCount({'a','b','c'}, {'a','b','c','d'}) == 0) +end + +local function splitChunkName(s) + if string.sub(s, 1, 1) == '@' then + s = string.sub(s, 2) + end + + local a = {} + for word in string.gmatch(s, '[^/\\]+') do + a[#a + 1] = string.lower(word) + end + return a +end +if DO_TEST then + local a = splitChunkName('@.\\vscode-debuggee.lua') + assert(#a == 2) + assert(a[1] == '.') + assert(a[2] == 'vscode-debuggee.lua') + + local a = splitChunkName('@C:\\dev\\VSCodeLuaDebug\\debuggee/lua\\socket.lua') + assert(#a == 6) + assert(a[1] == 'c:') + assert(a[2] == 'dev') + assert(a[3] == 'vscodeluadebug') + assert(a[4] == 'debuggee') + assert(a[5] == 'lua') + assert(a[6] == 'socket.lua') + + local a = splitChunkName('@main.lua') + assert(#a == 1) + assert(a[1] == 'main.lua') +end +-- chunkname matching }}} + +-- path control {{{ +local Path = {} + +function Path.isAbsolute(a) + local firstChar = string.sub(a, 1, 1) + if firstChar == '/' or firstChar == '\\' then + return true + end + + if string.match(a, '^%a%:[/\\]') then + return true + end + + return false +end + +local np_pat1, np_pat2 = ('[^SEP:]+SEP%.%.SEP?'):gsub('SEP', directorySeperator), ('SEP+%.?SEP'):gsub('SEP', directorySeperator) +function Path.normpath(path) + path = path:gsub('[/\\]', directorySeperator) + + if directorySeperator == '\\' then + local unc = ('SEPSEP'):gsub('SEP', directorySeperator) -- UNC + if path:match('^'..unc) then + return unc..Path.normpath(path:sub(3)) + end + end + + local k + repeat -- /./ -> / + path,k = path:gsub(np_pat2, directorySeperator) + until k == 0 + repeat -- A/../ -> (empty) + path,k = path:gsub(np_pat1, '', 1) + until k == 0 + if path == '' then + path = '.' + end + return path +end + +function Path.concat(a, b) + -- normalize a + local lastChar = string.sub(a, #a, #a) + if not (lastChar == '/' or lastChar == '\\') then + a = a .. directorySeperator + end + + -- normalize b + if string.match(b, '^%.%\\') or string.match(b, '^%.%/') then + b = string.sub(b, 3) + end + + return a .. b +end + +function Path.toAbsolute(base, sub) + if Path.isAbsolute(sub) then + return Path.normpath(sub) + else + return Path.normpath(Path.concat(base, sub)) + end +end + +if DO_TEST then + assert(Path.isAbsolute('c:\\asdf\\afsd')) + assert(Path.isAbsolute('c:/asdf/afsd')) + if directorySeperator == '\\' then + assert(Path.toAbsolute('c:\\asdf', 'fdsf') == 'c:\\asdf\\fdsf') + assert(Path.toAbsolute('c:\\asdf', '.\\fdsf') == 'c:\\asdf\\fdsf') + assert(Path.toAbsolute('c:\\asdf', '..\\fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('c:\\asdf', 'c:\\fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('c:/asdf', '../fdsf') == 'c:\\fdsf') + assert(Path.toAbsolute('\\\\HOST\\asdf', '..\\fdsf') == '\\\\HOST\\fdsf') + elseif directorySeperator == '/' then + assert(Path.toAbsolute('/usr/bin/asdf', 'fdsf') == '/usr/bin/asdf/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', './fdsf') == '/usr/bin/asdf/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', '../fdsf') == '/usr/bin/fdsf') + assert(Path.toAbsolute('/usr/bin/asdf', '/usr/bin/fdsf') == '/usr/bin/fdsf') + assert(Path.toAbsolute('\\usr\\bin\\asdf', '..\\fdsf') == '/usr/bin/fdsf') + end +end +-- path control }}} + +local coroutineSet = {} +setmetatable(coroutineSet, { __mode = 'v' }) + +------------------------------------------------------------------------------- +-- network utility {{{ +local function sendFully(str) + local first = 1 + while first <= #str do + local sent = sock:send(str, first) + if sent and sent > 0 then + first = first + sent; + else + error('sock:send() returned < 0') + end + end +end + +-- send log to debug console +local function logToDebugConsole(output, category) + local dumpMsg = { + event = 'output', + type = 'event', + body = { + category = category or 'console', + output = output + } + } + local dumpBody = json.encode(dumpMsg) + sendFully('#' .. #dumpBody .. '\n' .. dumpBody) +end + +-- pure mode {{{ +local function createHaltBreaker() + -- chunkname matching { + local loadedChunkNameMap = {} + for chunkname, _ in pairs(debug.getchunknames()) do + loadedChunkNameMap[chunkname] = splitChunkName(chunkname) + end + + local function findMostSimilarChunkName(path) + local splitedReqPath = splitChunkName(path) + local maxMatchCount = 0 + local foundChunkName = nil + for chunkName, splitted in pairs(loadedChunkNameMap) do + local count = getMatchCount(splitedReqPath, splitted) + if (count > maxMatchCount) then + maxMatchCount = count + foundChunkName = chunkName + end + end + return foundChunkName + end + -- chunkname matching } + + local lineBreakCallback = nil + local function updateCoroutineHook(c) + if lineBreakCallback then + sethook(c, lineBreakCallback, 'l') + else + sethook(c) + end + end + local function sethalt(cname, ln) + for i = ln, ln + 10 do + if debug.sethalt(cname, i) then + return i + end + end + return nil + end + return { + setBreakpoints = function(path, lines) + local foundChunkName = findMostSimilarChunkName(path) + local verifiedLines = {} + + if foundChunkName then + debug.clearhalt(foundChunkName) + for _, ln in ipairs(lines) do + verifiedLines[ln] = sethalt(foundChunkName, ln) + end + end + + return verifiedLines + end, + + setLineBreak = function(callback) + if callback then + sethook(callback, 'l') + else + sethook() + end + + lineBreakCallback = callback + for cid, c in pairs(coroutineSet) do + updateCoroutineHook(c) + end + end, + + coroutineAdded = function(c) + updateCoroutineHook(c) + end, + + stackOffset = + { + enterDebugLoop = 6, + halt = 6, + step = 4, + stepDebugLoop = 6 + } + } +end + +local function createPureBreaker() + local lineBreakCallback = nil + local breakpointsPerPath = {} + local chunknameToPathCache = {} + + local function chunkNameToPath(chunkname) + local cached = chunknameToPathCache[chunkname] + if cached then + return cached + end + + local splitedReqPath = splitChunkName(chunkname) + local maxMatchCount = 0 + local foundPath = nil + for path, _ in pairs(breakpointsPerPath) do + local splitted = splitChunkName(path) + local count = getMatchCount(splitedReqPath, splitted) + if (count > maxMatchCount) then + maxMatchCount = count + foundPath = path + end + end + + if foundPath then + chunknameToPathCache[chunkname] = foundPath + end + return foundPath + end + + local entered = false + local function hookfunc() + if entered then return false end + entered = true + + if lineBreakCallback then + lineBreakCallback() + end + + local info = debug_getinfo(2, 'Sl') + if info then + local path = chunkNameToPath(info.source) + if path then + path = string.lower(path) + end + local bpSet = breakpointsPerPath[path] + if bpSet and bpSet[info.currentline] then + _G.__halt__() + end + end + + entered = false + end + sethook(hookfunc, 'l') + + return { + setBreakpoints = function(path, lines) + local t = {} + local verifiedLines = {} + for _, ln in ipairs(lines) do + t[ln] = true + verifiedLines[ln] = ln + end + if path then + path = string.lower(path) + end + breakpointsPerPath[path] = t + return verifiedLines + end, + + setLineBreak = function(callback) + lineBreakCallback = callback + end, + + coroutineAdded = function(c) + sethook(c, hookfunc, 'l') + end, + + stackOffset = + { + enterDebugLoop = 6, + halt = 7, + step = 4, + stepDebugLoop = 7 + } + } +end +-- pure mode }}} + + +-- 센드는 블럭이어도 됨. +local function sendMessage(msg) + local body = json.encode(msg) + + if dumpCommunication then + logToDebugConsole('[SENDING] ' .. valueToString(msg)) + end + + sendFully('#' .. #body .. '\n' .. body) +end + +-- 리시브는 블럭이 아니어야 할 거 같은데... 음... 블럭이어도 괜찮나? +local function recvMessage() + local header = sock:receive('*l') + if (header == nil) then + -- 디버거가 떨어진 상황 + return nil + end + if (string.sub(header, 1, 1) ~= '#') then + error('헤더 이상함:' .. header) + end + + local bodySize = tonumber(header:sub(2)) + local body = sock:receive(bodySize) + + return json.decode(body) +end +-- network utility }}} + +------------------------------------------------------------------------------- +local function debugLoop() + storedVariables = {} + nextVarRef = 1 + while true do + local msg = recvMessage() + if msg then + if dumpCommunication then + logToDebugConsole('[RECEIVED] ' .. valueToString(msg), 'stderr') + end + + local fn = handlers[msg.command] + if fn then + local rv = fn(msg) + + -- continue인데 break하는 게 역설적으로 느껴지지만 + -- 디버그 루프를 탈출(break)해야 정상 실행 흐름을 계속(continue)할 수 있지.. + if (rv == 'CONTINUE') then + break; + end + else + --print('UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) + end + else + -- 디버그 중에 디버거가 떨어졌다. + -- print펑션을 리다이렉트 한경우에는 원래대로 돌려놓는다 + if redirectedPrintFunction then + _G.print = redirectedPrintFunction + end + break + end + end + storedVariables = {} + nextVarRef = 1 +end + +------------------------------------------------------------------------------- +local sockArray = {} +function debuggee.start(jsonLib, config) + json = jsonLib + assert(jsonLib) + + config = config or {} + local connectTimeout = config.connectTimeout or 5.0 + local controllerHost = config.controllerHost or 'localhost' + local controllerPort = config.controllerPort or 56789 + onError = config.onError or defaultOnError + addUserdataVar = config.addUserdataVar or function() return end + local redirectPrint = config.redirectPrint or false + dumpCommunication = config.dumpCommunication or false + ignoreFirstFrameInC = config.ignoreFirstFrameInC or false + if not config.luaStyleLog then + valueToString = function(value) return json.encode(value) end + end + + local breakerType + if debug.sethalt then + breaker = createHaltBreaker() + breakerType = 'halt' + else + breaker = createPureBreaker() + breakerType = 'pure' + end + + local err + sock, err = socket.tcp() + if not sock then error(err) end + sockArray = { sock } + if sock.settimeout then sock:settimeout(connectTimeout) end + local res, err = sock:connect(controllerHost, tostring(controllerPort)) + if not res then + sock:close() + sock = nil + return false, breakerType + end + + if sock.settimeout then sock:settimeout() end + sock:setoption('tcp-nodelay', true) + + local initMessage = recvMessage() + assert(initMessage and initMessage.command == 'welcome') + sourceBasePath = initMessage.sourceBasePath + directorySeperator = initMessage.directorySeperator + + if redirectPrint then + redirectedPrintFunction = _G.print -- 디버거가 떨어질때를 대비해서 보관한다 + _G.print = function(...) + local t = { n = select("#", ...), ... } + for i = 1, #t do + t[i] = tostring(t[i]) + end + sendEvent( + 'output', + { + category = 'stdout', + output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. + }) + end + end + + debugLoop() + return true, breakerType +end + +------------------------------------------------------------------------------- +function debuggee.poll() + if not sock then return end + + -- Processes commands in the queue. + -- Immediately returns when the queue is/became empty. + while true do + local r, w, e = socket.select(sockArray, nil, 0) + if e == 'timeout' then break end + + local msg = recvMessage() + if msg then + if dumpCommunication then + logToDebugConsole('[POLL-RECEIVED] ' .. valueToString(msg), 'stderr') + end + + if msg.command == 'pause' then + debuggee.enterDebugLoop(1) + return + end + + local fn = handlers[msg.command] + if fn then + local rv = fn(msg) + -- Ignores rv, because this loop never blocks except explicit pause command. + else + --print('POLL-UNKNOWN DEBUG COMMAND: ' .. tostring(msg.command)) + end + else + break + end + end +end + +------------------------------------------------------------------------------- +local function getCoroutineId(c) + -- 'thread: 011DD5B0' + -- 12345678^ + local threadIdHex = string.sub(tostring(c), 9) + return tonumber(threadIdHex, 16) +end + +------------------------------------------------------------------------------- +function debuggee.addCoroutine(c) + local cid = getCoroutineId(c) + coroutineSet[cid] = c + breaker.coroutineAdded(c) +end + +------------------------------------------------------------------------------- +local function sendSuccess(req, body) + sendMessage({ + command = req.command, + success = true, + request_seq = req.seq, + type = "response", + body = body + }) +end + +------------------------------------------------------------------------------- +local function sendFailure(req, msg) + sendMessage({ + command = req.command, + success = false, + request_seq = req.seq, + type = "response", + message = msg + }) +end + +------------------------------------------------------------------------------- +sendEvent = function(eventName, body) + sendMessage({ + event = eventName, + type = "event", + body = body + }) +end + +------------------------------------------------------------------------------- +local function currentThreadId() +--[[ + local threadId = 0 + if coroutine.running() then + end + return threadId +]] + return 0 +end + +------------------------------------------------------------------------------- +local function startDebugLoop() + sendEvent( + 'stopped', + { + reason = 'breakpoint', + threadId = currentThreadId(), + allThreadsStopped = true + }) + + local status, err = pcall(debugLoop) + if not status then + onError(err) + end +end + +------------------------------------------------------------------------------- +_G.__halt__ = function() + baseDepth = breaker.stackOffset.halt + startDebugLoop() +end + +------------------------------------------------------------------------------- +function debuggee.enterDebugLoop(depthOrCo, what) + if sock == nil then + return false + end + + if what then + sendEvent( + 'output', + { + category = 'stderr', + output = what, + }) + end + + if type(depthOrCo) == 'thread' then + baseDepth = 0 + debugTargetCo = depthOrCo + elseif type(depthOrCo) == 'table' then + baseDepth = (depthOrCo.depth or 0) + debugTargetCo = depthOrCo.co + else + baseDepth = (depthOrCo or 0) + breaker.stackOffset.enterDebugLoop + debugTargetCo = nil + end + startDebugLoop() + return true +end + +------------------------------------------------------------------------------- +-- Function for printing on vscode debug console +-- First parameter 'category' can colorizes print text +function debuggee.print(category, ...) + if sock == nil then + return false + end + local t = { ... } + for i = 1, #t do + t[i] = tostring(t[i]) + end + + local categoryVscodeConsole = 'stdout' + if category == 'warning' then + categoryVscodeConsole = 'console' -- yellow + elseif category == 'error' then + categoryVscodeConsole = 'stderr' -- red + elseif category == 'log' then + categoryVscodeConsole = 'stdout' -- white + end + + sendEvent( + 'output', + { + category = categoryVscodeConsole, + output = table.concat(t, '\t') .. '\n' -- Same as default "print" output end new line. + }) +end + +------------------------------------------------------------------------------- +-- ★★★ https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +function handlers.setBreakpoints(req) + local bpLines = {} + for _, bp in ipairs(req.arguments.breakpoints) do + bpLines[#bpLines + 1] = bp.line + end + + local verifiedLines = breaker.setBreakpoints( + req.arguments.source.path, + bpLines) + + local breakpoints = {} + for i, ln in ipairs(bpLines) do + breakpoints[i] = { + verified = (verifiedLines[ln] ~= nil), + line = verifiedLines[ln] + } + end + + sendSuccess(req, { + breakpoints = breakpoints + }) +end + +------------------------------------------------------------------------------- +function handlers.configurationDone(req) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.threads(req) + local c = coroutine.running() + + local mainThread = { + id = currentThreadId(), + name = (c and tostring(c)) or "main" + } + + sendSuccess(req, { + threads = { mainThread } + }) +end + +------------------------------------------------------------------------------- +function handlers.stackTrace(req) + assert(req.arguments.threadId == 0) + + local stackFrames = {} + local firstFrame = (req.arguments.startFrame or 0) + baseDepth + local lastFrame = (req.arguments.levels and (req.arguments.levels ~= 0)) + and (firstFrame + req.arguments.levels - 1) + or (9999) + + -- if firstframe function of stack is C function, ignore it. + if ignoreFirstFrameInC then + local info = debug_getinfo(firstFrame, 'lnS') + if info and info.what == "C" then + firstFrame = firstFrame + 1 + end + end + + for i = firstFrame, lastFrame do + local info = debug_getinfo(i, 'lnS') + if (info == nil) then break end + --print(json.encode(info)) + + local src = info.source + if string.sub(src, 1, 1) == '@' then + src = string.sub(src, 2) -- 앞의 '@' 떼어내기 + end + + local name + if info.name then + name = info.name .. ' (' .. (info.namewhat or '?') .. ')' + else + name = '?' + end + + local sframe = { + name = name, + source = { + name = nil, + path = Path.toAbsolute(sourceBasePath, src) + }, + column = 1, + line = info.currentline or 1, + id = i, + } + stackFrames[#stackFrames + 1] = sframe + end + + sendSuccess(req, { + stackFrames = stackFrames + }) +end + +------------------------------------------------------------------------------- +local scopeTypes = { + Locals = 1, + Upvalues = 2, + Globals = 3, +} +function handlers.scopes(req) + local depth = req.arguments.frameId + + local scopes = {} + local function addScope(name) + scopes[#scopes + 1] = { + name = name, + expensive = false, + variablesReference = depth * 1000000 + scopeTypes[name] + } + end + + addScope('Locals') + addScope('Upvalues') + addScope('Globals') + + sendSuccess(req, { + scopes = scopes + }) +end + +------------------------------------------------------------------------------- +local function registerVar(varNameCount, name_, value, noQuote) + local ty = type(value) + local name + if type(name_) == 'number' then + name = '[' .. name_ .. ']' + else + name = tostring(name_) + end + if varNameCount[name] then + varNameCount[name] = varNameCount[name] + 1 + name = name .. ' (' .. varNameCount[name] .. ')' + else + varNameCount[name] = 1 + end + + local item = { + name = name, + type = ty + } + + if (ty == 'string' and (not noQuote)) then + item.value = '"' .. value .. '"' + else + item.value = tostring(value) + end + + if (ty == 'table') or + (ty == 'function') or + (ty == 'userdata') then + storedVariables[nextVarRef] = value + item.variablesReference = nextVarRef + nextVarRef = nextVarRef + 1 + else + item.variablesReference = -1 + end + + return item +end + +------------------------------------------------------------------------------- +function handlers.variables(req) + local varRef = req.arguments.variablesReference + local variables = {} + local varNameCount = {} + local function addVar(name, value, noQuote) + variables[#variables + 1] = registerVar(varNameCount, name, value, noQuote) + end + + if (varRef >= 1000000) then + -- Scope. + local depth = math.floor(varRef / 1000000) + local scopeType = varRef % 1000000 + if scopeType == scopeTypes.Locals then + for i = 1, 9999 do + local name, value = debug_getlocal(depth, i) + if name == nil then break end + addVar(name, value, nil) + end + elseif scopeType == scopeTypes.Upvalues then + local info = debug_getinfo(depth, 'f') + if info and info.func then + for i = 1, 9999 do + local name, value = debug.getupvalue(info.func, i) + if name == nil then break end + addVar(name, value, nil) + end + end + elseif scopeType == scopeTypes.Globals then + for name, value in pairs(_G) do + addVar(name, value) + end + table.sort(variables, function(a, b) return a.name < b.name end) + end + else + -- Expansion. + local var = storedVariables[varRef] + if type(var) == 'table' then + for k, v in pairs(var) do + addVar(k, v) + end + table.sort(variables, function(a, b) + local aNum, aMatched = string.gsub(a.name, '^%[(%d+)%]$', '%1') + local bNum, bMatched = string.gsub(b.name, '^%[(%d+)%]$', '%1') + + if (aMatched == 1) and (bMatched == 1) then + -- both are numbers. compare numerically. + return tonumber(aNum) < tonumber(bNum) + elseif aMatched == bMatched then + -- both are strings. compare alphabetically. + return a.name < b.name + else + -- string comes first. + return aMatched < bMatched + end + end) + elseif type(var) == 'function' then + local info = debug.getinfo(var, 'S') + addVar('(source)', tostring(info.short_src), true) + addVar('(line)', info.linedefined) + + for i = 1, 9999 do + local name, value = debug.getupvalue(var, i) + if name == nil then break end + addVar(name, value) + end + elseif type(var) == 'userdata' then + addUserdataVar(var, addVar) + end + + local mt = getmetatable(var) + if mt then + addVar("(metatable)", mt) + end + end + + sendSuccess(req, { + variables = variables + }) +end + +------------------------------------------------------------------------------- +function handlers.continue(req) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +local function stackHeight() + for i = 1, 9999999 do + if (debug_getinfo(i, '') == nil) then + return i + end + end +end + +------------------------------------------------------------------------------- +local stepTargetHeight = nil +local function step() + if (stepTargetHeight == nil) or (stackHeight() <= stepTargetHeight) then + breaker.setLineBreak(nil) + baseDepth = breaker.stackOffset.stepDebugLoop + startDebugLoop() + end +end + +------------------------------------------------------------------------------- +function handlers.next(req) + stepTargetHeight = stackHeight() - breaker.stackOffset.step + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.stepIn(req) + stepTargetHeight = nil + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.stepOut(req) + stepTargetHeight = stackHeight() - (breaker.stackOffset.step + 1) + breaker.setLineBreak(step) + sendSuccess(req, {}) + return 'CONTINUE' +end + +------------------------------------------------------------------------------- +function handlers.evaluate(req) + -- 실행할 소스 코드 준비 + local sourceCode = req.arguments.expression + if string.sub(sourceCode, 1, 1) == '!' then + sourceCode = string.sub(sourceCode, 2) + else + sourceCode = 'return (' .. sourceCode .. ')' + end + + -- 환경 준비. + -- 뭘 요구할지 모르니까 로컬, 업밸류, 글로벌을 죄다 복사해둔다. + -- 우선순위는 글로벌-업밸류-로컬 순서니까 + -- 그 반대로 갖다놓아서 나중 것이 앞의 것을 덮어쓰게 한다. + local depth = req.arguments.frameId + local tempG = {} + local declared = {} + local function set(k, v) + tempG[k] = v + declared[k] = true + end + + for name, value in pairs(_G) do + set(name, value) + end + + if depth then + local info = debug_getinfo(depth, 'f') + if info and info.func then + for i = 1, 9999 do + local name, value = debug.getupvalue(info.func, i) + if name == nil then break end + set(name, value) + end + end + + for i = 1, 9999 do + local name, value = debug_getlocal(depth, i) + if name == nil then break end + set(name, value) + end + else + -- VSCode가 depth를 안 보낼 수도 있다. + -- 특정 스택 프레임을 선택하지 않은, 전역 이름만 조회하는 경우이다. + end + local mt = { + __newindex = function() error('assignment not allowed', 2) end, + __index = function(t, k) if not declared[k] then error('not declared', 2) end end + } + setmetatable(tempG, mt) + + -- 파싱 + -- loadstring for Lua 5.1 + -- load for Lua 5.2 and 5.3(supports the private environment's load function) + local fn, err = (loadstring or load)(sourceCode, 'X', nil, tempG) + if fn == nil then + sendFailure(req, string.gsub(err, '^%[string %"X%"%]%:%d+%: ', '')) + return + end + + -- 실행하고 결과 송신 + if setfenv ~= nil then + -- Only for Lua 5.1 + setfenv(fn, tempG) + end + + local success, aux = pcall(fn) + if not success then + aux = aux or '' -- Execution of 'error()' returns nil as aux + sendFailure(req, string.gsub(aux, '^%[string %"X%"%]%:%d+%: ', '')) + return + end + + local varNameCount = {} + local item = registerVar(varNameCount, '', aux) + + sendSuccess(req, { + result = item.value, + type = item.type, + variablesReference = item.variablesReference + }) +end + +------------------------------------------------------------------------------- +return debuggee