This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Konlab's profile picture

Multitasking - rewritten!

Started by Konlab, 14 November 2015 - 04:57 PM
Konlab #1
Posted 14 November 2015 - 05:57 PM
Download:
http://pastebin.com/9N7Jdhk0

I rewrote this and now it's a lot more cleaner and modular. (Not sure about performance) and has a working windowed object so you can actually use it for multitasking shells!

Examples (Usage):
Spoiler
os.loadAPI("multitask") --#load it

thread = multitask.constructLayered(multitask.threadLayers) --#this creates an empty thread (think of it like a container for now)

f = function() while true do print(os.pullEvent()) end end --#function to put inside the thread

thread.init(f) --#init thread, now it has the function inside
thread.resume() --#Always resume after initing! This will execute code to the first yield

--#here it's the same but for other function:
thread2 = multitask.constructLayered(multitask.threadLayers) --#don't use the same thread because tables are pointer based

f2 = function() os.pullEvent() print("2") os.pullEvent() print("1") end

thread2.init(f2)
thread2.resume()

--#so here comes the interesting part
system = multitask.constructLayered(multitask.threadingSystemLayers) --#this is a system, a container for multiple threads
system.init() --#always init everything!

system.add(thread)
system.add(thread2)

for i=1,10 do
	system.resume(os.pullEvent()) --#resume ten times
end
Documentation:
(All functions use dot notation: system.init() etc.)
Threads:
SpoilerCreating a thread: (f is function)
thread = multitask.constructLayered(multitask.threadLayers) --#this creates an empty thread (think of it like a container for now)
thread.init(f) --#init thread, now it has the function inside
thread.resume() --#Always resume after initing! This will execute code to the first yield
Creating a thread with window: (f is function, w is window)
thread = multitask.constructLayered(multitask.threadLayers) --#this creates an empty thread (think of it like a container for now)
thread.init(f) --#init thread, now it has the function inside
thread.assignWindow(w) --#Do it before first resuming
thread.resume() --#Always resume after initing! This will execute code to the first yield
Resuming a thread with event:
thread.resume("key",28)
Get thread window if assigned:
thread.getWindow()
all functions: see code and notes in the bottom of system documentation
Systems:
Spoilerinit - must be called before usage, no output or arguments
add(thread) -adds thread to the system, returns nothing
resume(event, par1, …) -resumes all (unpaused) threads
kill(index) - kills thread with index of the first arg, index
count() - returns how many alive threads exist (well, dead threads live one extra step after they died because of how it's implemented but you won't notice)
getID(index) - returns identifier for index (because indexes change when a thread is killed but identifiers don't)
getDataForIndex(index, key) - returns data assigned with index with key
setDataForIndex(index, key, value) - self explanatory
… (documentation is WIP)
also you can read the code (because function names are self explanatory) just remember:
obj and child (sometimes _) are passed by the layer construction, not you
functions are packed into layers that you add when calling constructLayered
there's threads and thread systems

Changlelog
Spoiler13. 6. 2016
Fixed some things, not that much but myshell (see in comments) actually works and added experimental multi window showed support
201606idk:
http://pastebin.com/h3akBGna


Old post:
SpoilerSo how this works?
My multitasking library is layered, so you have to change minimal functionality to e.g. switch from the coroutine to a new one (like states).

Function list (documentation)
SpoilerPlease note: my namings are not really good, where I say 'thread' or 'thread table' I probably mean the table containing the coroutine and other data.
Thread functions (You will probably not want to use these):
SpoilerEach thread object has a resume function and a status function which will be always the most advanced layer. You can leave out layers (From 20151123), you'll want it when you want to make a pausable non-CC thread table.

These functions (except newThread) accept a function or any table that has resume and status functions (thread tables generated by these will all have it) so you can stack your own functionality and make custom layers and re-order them (From 20151123). When you give them a function, however, they will add all layers that are below them (e.g. advthread will add thread, ccthread and pausable, too)
newThread(function)
SpoilerReturns a thread table with these:
-fields:
co - coroutine
-functions:
cresume - resumes thread with args (args: self, event)
cstatus - gets coroutine status (args: self)
newCCThread(function or thread)
SpoilerReturned thread table has all functions and fields of Basic Thread, has extra ones:
-ccresume(self,event) - only resumes thread if its filter is equal to event, automatically sets new filter returned by the coroutine
-filter - string or nil, thread's filter
newPausable - similar to newCCThread, has pausing feature, so you can mute threads
SpoilerThis layer adds pausing functionality to the thread table generated:
Functions:
presume(self,event) - calls the below layer's (less intelligent = below) resume only when not paused, when paused queues it in his own event queue
clearEvents(self) - clears event queue, might take a while when the event queue has lots of items
setPause(self,pause) pause is boolean indicating the new paused status (true = paused), when unpausing event queue is automatically cleared
Fields:
events - table, the thread's event queue
paused - boolean, paused status
newAdvThread - has extra data and support for inter process messanging
SpoilerThis is a very simple layer when talking about single thread tables (not very simple when about its system):
Adds 2 fields:
msgs - table, systems use this for inter-proccess messanging
data - table, systems use this for adding support for the user to enter its custom values (like thread's name)
And these are the functions that you will probably use:
SpoilerAll thread systems listed here will have these functions:
kill(self,n) - kills thread with index of n - use INDEX
resume(self,event) - resumes all threads with event by calling resume (always the top layer)
add(self,function or thread table) - adds a new thread to the system
ended(self,id) - checks for thread status of ID, returns true if ID is killed
onKill - delegate, called when a thread is killed
onAdd - delegate, called when a thread is added
getID(self,index) - converts index to id
findIndex(self,id) - converts id to index, please note this is VERY SLOW compared to getting id, so
threads - table with all thread tables
anyended - boolean, true if a thread already ended
allended - boolean, true if all threads ended
count - count of threads
running - id of running thread, nil if none

Index vs id - (From version 20151122) indexes change when a thread is killed but ids don't so use ids for identification.
What you should probably do is having a custom index to id table created with the findIndex function and recalculate when onAdd or onKill is called.

These two have only the universal functions:
newCThreadSystem - don't use this. I implemented this because it was just one line, use it only in non-CC lua programs
newCCThreadSystem - use this for parallel (see example)

These have more functions than the universal:
newPausableSystem - system of pausables
Spoilermore functions:
setPause(self,index,bool) - self explantory, use INDEX
getPause(self,index) - use INDEX
When a thread is paused he will receive all events in his event queue, the event queue will be emptied when unpaused
queueEventAsync(self,index,eventdata) - sends event to his queue async, so next unpausing he will receive it. eventdata is a table, use INDEX
queueEventSync(self,index,eventdata) - sends event async and unpauses thread if not paused and returns true, if paused it will queue it async (nov 22 2015 - whoops and return nothing)
onPauseChange - delegate, args: index, bool (new pause)

newAdvancedSystem - system of advthreads
SpoilerThis thread system has all functions of pausable but has extra support for the user implementing inter-thread messanging
addMessage = function(self,i,msg) - i is INDEX, msg is table
viewMessage = function(self,i) - returns the oldest message but doesn't delete it - i is INDEX
getMessage = function(self,i) - returns the oldest message and deletes it - i is INDEX
viewMessages = function(self,i) - returns table of all messages, i is INDEX
getMessages = function(self,i) - returns table of all messages and clears messages of i, i is INDEX

This thread system also has a data table attached to each thread table where you can save your own variables
setData = function(self,i,key,value) - i is INDEX
getData = function(self,i,key) - i is INDEX

onDataChange - delegate, args: index, key, value

newWinowedSystem - system of advthreads with windows - This one has very custom functions, and is pretty buggy (nov 22 2015, it is a WIP) so please don't use it
Misc functions (used by this API, but not localized because it can be useful for you):
delegate() and event(), for more information on these please check out their own separate thread found in APIs and Utilities.
newLayer(table or function, converter) - does things you need when making custom threads: give it a table or function as first argument and a converter function as second argument and it will convert the first arg if it's a function and return thread table and parent interface (has resume and status functions), does type checking and erroring for you, when types are invalid it will error at both 2 and 3 levels (the place where you called newLayer and that place's caller)

Please note: when you see a date in brackets it means that there is something that will be changed or something that is only actual for that date, the documentation is not done yet, and it will change. Function names will maybe change

Example:
Parallel API rewrite:
os.loadAPI("multitask")
function waitForAny(...)
local system = newCCThreadSystem(...)
   while not system.anyended do
		system:resume({os.pullEvent()})
   end
end
function waitForAll(...)
local system = newCCThreadSystem(...)
	while system.count > 0 do
		system:resume({os.pullEvent()})
   end
end

Please note, if you published system you could call system:add(function) to add a function and next resuming you runned that extra function.

Another example: adding custom layer on top of Advanced layer without the CC layer
--#f is a function, multitask lib. is loaded by running it (in default env)
local th = newThread(f) --#First we create a thread from f
local th = newPausable(th) --#We are doing this one by one because we don't want the CC Thread layer.
local th = newAdvThread(th)
--#Now we add our custom layer on top of advanced layer(we could add it below, too) that when our coroutine dies it logs it.
--#If we had a logging API loaded in Log with function Debug that takes one param: string:
local function newLogging(t,conv) --#t can be a function and a table
	t,parent = newLayer(t,conv or newAdvThread) --#Use the new function to get parent and auto convert if neccessary
	t.resume = function(self,...)
		parent.resume(self,...)
		if parent.status(self) == "dead" then
			Log.Debug("This thread is dead :(/>/>/>/>/>")
		end
	end
	--#You can modify everything, but always return a table with a resume and status functions (can be inherited or custom)
	return t
end
th = newLogging(th) --#Done!

Changelog:
Spoilerversion 20151128:
http://pastebin.com/EVE61iuL
http://pastie.org/10586854
Error messages now are more intelligent
Now almost all functions in this library have type checking
Added config option at the top of the code to disable type checking (speeds up in average 50% now)
Fixed 1 bug and 1 wrong design decision leading to another bug
Added 2 extra delegates (more coming in future, maybe with a special auto-delegate generating system)

version 20151124:
http://pastebin.com/YbYQVA08
http://pastie.org/10578463
!!!Renamed in Pausables setPause to setPaused!!!
!!!Thread tables now need an event table not unpacked event!!!
Thread systems now are threads too so you can have thread systems of thread systems too
Optimized by 15% (Yes, I measured it), more optimizations will come because I figured out what's the slowest part
I also modularized the code, so you now have a newLayer function that does interesting things (check the documentation)

version 20151123:
http://pastebin.com/2FCAC7qU
http://pastie.org/10576407
Added support for leaving out threads, finally tested newXThread accepting lower (and higher) layer threads, added ability to have lower layer threads in higher layer systems so it won't crash (it will return false or nil like if you enter invalid index)

version 20151122:
http://pastebin.com/cEdQ4DM6
Or if above fails try:
http://pastie.org/10574286
Added new index mechanisms, fixed some bugs (with count, kill, ended), added alive function to systems
Original release:
http://pastebin.com/V6pfZAcf

Please report any bugs or unneccessary features found and please share your opinion!
Edited on 13 June 2016 - 05:10 PM
Creator #2
Posted 20 November 2015 - 03:10 PM
Don't be discouraged. Some people say that a thread doesn't get many replies when the program is not buggy. Atleast that's what they tell me to console me no one replies to OmniOS. :P/>/>
LDDestroier #3
Posted 20 November 2015 - 06:14 PM
Looks interesting, but I'm no OS designer. O and CraftOS works just fine for me.

I own Space Engineers too, and the planets are awesome! It does have some performance issues though, but not too bad (unless you have a not-fast computer). Also, good luck making spaceships on planets, what with gravity and all.
Konlab #4
Posted 21 November 2015 - 09:18 AM
Don't be discouraged. Some people say that a thread doesn't get many replies when the program is not buggy. Atleast that's what they tell me to console me no one replies to OmniOS. :P/>/>/>
Everything that uses windows is completly untested and will probably not work, so it's buggy

I am working on the documentation so it's atleast a little bit useable.

About planets: they are not working. My game crashes every time I place a planet
Edited on 23 November 2015 - 05:05 PM
Konlab #5
Posted 22 November 2015 - 06:07 PM
NEW UPDATE
new version: 20151122
Download:
http://pastebin.com/cEdQ4DM6
http://pastie.org/10574286
Details in main post, added documentation for pausable and advanced systems
Edited on 23 November 2015 - 05:30 PM
Konlab #6
Posted 23 November 2015 - 11:12 AM
New unstable version: http://pastie.org/10575736
Edit: version 20151122 is now on Backspace! (Identifier: "Multitask")
http://backspace.cf/...ippet/Multitask
http://www.computerc...r-an-app-store/

NEW UPDATE:
version 20151123 is here:
http://pastebin.com/2FCAC7qU
http://pastie.org/10576407
Details in main post!
I will also update multitask on backspace when it becomes possible ;)/>
Edited on 23 November 2015 - 05:04 PM
Konlab #7
Posted 24 November 2015 - 05:46 PM
NEW UPDATE:
version 20151124 is here and this is my record: update in three days in a row!
Includes optimization and more interesting features but has no backwards compatibility, please check the changelog, the changed function signatures are there.
http://pastebin.com/YbYQVA08
http://pastie.org/10578463
Please post your suggestions, opinion, bugs etc. below!
Edited on 24 November 2015 - 07:26 PM
Konlab #8
Posted 28 November 2015 - 06:19 PM
New update:
version 20151128
Please check the changelog section in the main post for more info

IMPORTANT!
If you are using the new version if you can please make sure to turn off type checking (change line 5 to local doTypeChecks = false) to speed up the library in average of 50%, only leave it on, if really neccessary!
I don't know why I didn't set it to false as default.

About speeds, it's completly unmeasurable, only sometimes with type checking on I can get results around ~0.1 second when having 100 threads in a CCThreadSystem
Creeper9207 #9
Posted 08 February 2016 - 03:58 PM
is index like the index in the thread table?
Konlab #10
Posted 23 March 2016 - 07:30 PM
is index like the index in the thread table?
Index is the index of the thread in the list of the threads.
Konlab #11
Posted 10 June 2016 - 11:12 AM
I rewrote it and it's now a lot more cleaner i think: http://pastebin.com/h3akBGna
also see here: http://pastebin.com/pvQuK1B2 is an example shell using the rewritten multitasking (nothing fancy but I tried to write understandable and not clean code (can't write clean code anyways) while writing this shell)
Konlab #12
Posted 13 June 2016 - 07:13 PM
Update! See main post!

Example usages (I am using these for debugging:)
http://pastebin.com/Vw4xtWfa - I updated myshell too
http://pastebin.com/un0NnRVm - Experimental multi window shell still very buggy and wip
Lyqyd #13
Posted 14 June 2016 - 12:19 AM
Don't beg for reputation points.
unnamedcoder #14
Posted 16 June 2016 - 06:43 PM
Isn't this why the parallel api exists?
KingofGamesYami #15
Posted 16 June 2016 - 06:51 PM
Isn't this why the parallel api exists?

This does a ton of stuff parallel doesn't. For example, assigning windows, killing threads, or adding more threads while running.
RedWolfie #16
Posted 19 June 2016 - 04:33 PM
I dont see any yields or sleeps in the code, how do you keep it from just running away?
Bomb Bloke #17
Posted 20 June 2016 - 01:36 AM
The idea is that you use it in much the same manner as you would when calling the coroutine API functions directly. That means you need to yield for event data yourself, and you need to resume your coroutines yourself.

Best I can make out, what it does handle for you is window-switching. At least, I think that's the point of it.
Konlab #18
Posted 20 June 2016 - 03:31 PM
You yield and you continue it with the event,
example usage:
Runs two functions at the same time
function runTwo(f1, f2)
--#create a system and two threads, will just leave out this because you can see how it works in the main post

while true do
  system.resume(os.pullEvent())
end
end
Edited on 20 June 2016 - 01:31 PM