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

Goroutines in ComputerCraft (Easier concurrency)

Started by 1lann, 25 July 2015 - 04:19 PM
1lann #1
Posted 25 July 2015 - 06:19 PM
Well, it's been a long time since I've ever made post. I got really bored today, so I decided to make something cool. Currently, I'm really into programing with Go (https://golang.org/), and one of the features I really liked about it were Goroutines. They basically allow you to make a function call in a new thread, essentially calling the function asynchronously. They also had things called channels, which allowed you to communicate between goroutines and synchronize events. I thought it would be cool to port this to ComputerCraft, and so I have.

This library also includes an additional feature, which is stacktraces. If any error occurs with your program that is running under the goroutine dispatcher, a stack trace will be printed out to as much detail as the program can. (Does not include code-snippet display or function resolution).
Example of a strack trace error:


So far, it's a really basic implementation which I'm sure there are ways I could have done better, but it fulfills its purpose. This is subject to change, the behavior of the library may change drastically between updates.

The library is available here: http://pastebin.com/kDRRavmy

Usage:
Load the library either with executing it (dofile or shell.run), or os.loadAPI.

It exposes 2 functions upon loading it, runDispatcher and quitDispatcher, which runs or quits the dispatcher. The dispatcher is what makes everything work, handling the goroutines (custom coroutine manager) over and loads the environment where the rest of the library is.

runDispatcher(function)
Takes in a function, and applies the goroutine environment to it, and runs it. You cannot run multiple dispatchers at once.

quitDispatcher()
Quits all dispatcher instances (you should only have 1 running anyway).

termCompatibility()
Removes terminal handles and disables term_events. Terminal contexts and persistence across goroutines will no longer work correctly (becomes undefined behaviour). This is here for compatibility purposes if you're doing something weird with the terminal. It is not recommend to use any terminal functions after running this, or at the very least you will need to be careful about doing so.

The following functions become available within the function that you've called the dispatcher on:
id = go(function, arguments…)
Runs the function in a new goroutine (coroutine), running it asynchronously. The rest of the current goroutine will run first until it hits a coroutine.yield, before executing the new goroutine. Returns the id of the newly spawned goroutine.

Note that goroutines store basic terminal states, meaning when you create a new goroutine, it will preserve the text color, background color, and cursor position independently to whether they have changed in other goroutines. They will also inherit these properties from the parent goroutine. This usually makes some forms of concurrent coding for CC easier, but can also cause unexpected behaviour if you're not aware of it. For example if you create two new goroutines in the same function, and they both print something, they will actually print on top of each other because of their independent cursor positions.

emitChannel(string channel, data [cannot be nil], bool wait)
Sends data to the channel to any other goroutines which are waiting on the channel. Set the wait flag to be true if you want this function call to block until a goroutine receives the data. Note that only 1 goroutine will receive the data if multiple goroutines are waiting on the same channel. Which means, don't have multiple goroutines waiting on the same channel. Note that this is subject to change. In the future, I may add support for multiple goroutines waiting on a single channel.

data, origin = waitChannel(string channel, bool allowPrevious, number timeout)
Blocks until it receives data on the channel. If allowPrevious is set to true, any pre-existing "unclaimed" channel data will unblock instantly (Ex: Where you call emitChannel but the waitChannel hasn't been called yet). If a timeout is set and no data is sent to the channel after timeout seconds, the function will return with false. The goroutine ID of the invoking channel (goroutine which called emitChannel) is also returned as a number under origin. See goroutineId().

The "term_events" channel
Has the function name pushed to it whenever a term.write, term.clearLine, term.clear, or term.scroll occurs. Ex: "clearLine" == waitChannel("term_events")

id = goroutineId()
Returns the current's goroutine unique identifier (their ID) as a number.

suspend(id)
Suspends a goroutine using its ID (stays in it its state and doesn't continue running). Suspending a goroutine can cause issues especially when dealing with os.pullEvent with a filter, as it drops all events that occured between the goroutine being suspended and resumed.

resume(id)
Resumes a suspended goroutine with its ID. Upon resuming, the goroutine will forcefully run (os.pullEvent/coroutine.yield will return despite its state or filter) upon the next yield.

wg = WaitGroup.new()
Creates a new WaitGroup object. Wait groups are used where multiple goroutines are spawned, and you wish to wait until every goroutines finishes its task before continuing. Based on: http://golang.org/pkg/sync/#WaitGroup

wg:setZero()
Sets the wait group counter to zero, (called automatically upon WaitGroup.new())

wg:add(number amount)
Adds the number to the wait group counter. Can be negative, however the counter will not go below 0.

wg:done()
Decrements the counter by 1, will not go below 0.

wg:wait()
Blocks until the counter is 0.

wg:value()
Returns the current value stored in the counter.

quitDispatcher()
Does the same thing as the other external quitDispatcher, but internally they're different. Handy to use for quitting the program.

Enough of the documentation, here are some demos!
Clock demo (A clock that stays at the top right, buggy in graphical intensive applications, I recommend only using this in the default shell):
Spoiler

shell.run("go.lua")

local function main()
	local function clock()
		local w = term.getSize()
		term.setTextColor(colors.gray)

		while true do
			term.setCursorPos(w - 8, 1)
			term.setBackgroundColor(colors.black)
			term.write("		 ")
			term.setCursorPos(w - 8, 1)
			term.setBackgroundColor(colors.white)
			term.write(textutils.formatTime(os.time()))
			waitChannel("term_events", false, 0.2)
		end
	end

	go(clock)
	go(function() shell.run("/rom/programs/shell") end)
end

runDispatcher(main)

A simple rednet chat application: http://pastebin.com/XNtef2Bv
Comes with the goroutines library included. The actual application starts at line 448. Simply pastebin get it, and run it! Type: "exit" or "/exit" in chat to exit, and "error" to invoke and error if you would like to see what a stack trace error looks like for yourself.

This is more or less a work in progress. Leave any feedback, criticism, and/or suggestions below. Thanks.
If you need any help, or have any questions, I'll be happy to answer them.
Edited on 31 July 2015 - 05:34 PM
98Games_YT #2
Posted 31 July 2015 - 10:54 AM
Nice work 1lann. Once again, you have given the CC Community a simplifier.
Waitdev_ #3
Posted 31 July 2015 - 12:26 PM
nice name ;)/>
H4X0RZ #4
Posted 31 July 2015 - 02:21 PM
When I read the title I immediately thought of Gopher's API(/whateverItWas) thread xD
1lann #5
Posted 31 July 2015 - 07:29 PM
When I read the title I immediately thought of Gopher's API(/whateverItWas) thread xD
Oh wow, I didn't realise his APIs had "goroutines" too. Although I got the name from the Go programming language (as they're called goroutines too) and the API is inspired by them. By the looks of it, mine and GopherAtl's have similarities, but his provides different goals than mine. Such as his lets the user determine how to handle terminal redirection between goroutines, mine instead stores the state of the terminal for each goroutine, and restores them between goroutines automatically.