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

Attempting Coroutine Management

Started by DannySMc, 25 March 2015 - 09:55 AM
DannySMc #1
Posted 25 March 2015 - 10:55 AM
Okay so I know I have been posting many posts and getting very agitated with this coroutine system… So I am gonna make an API and add all my code here and as I go on I shall ask for help here, so here is my first bit of code:


processes = {}
current = {}

function create(name, method)
	processes[name] = {
		co = coroutine.create(method),
		win = window.create(term.current(), 1, 1, 51, 19, false)
	}
end

function switch(procname, arguments)
	print(tostring(processes[name][2]))
	term.redirect(processes[name][2])
end

function list()
	output = {}
	for k,v in ipairs(processes) do
		table.insert(output, v[1])
	end
	return output
end

function kill()
	term.redirect(term.native())
end

function root()
end

function gotoRoot()
end

function debug()
	for _,v in ipairs(processes) do
	  print(v)
	end
end

And my code for the test program:

os.loadAPI("mt") --it is called mt
function home()
	print("Hello")
end

mt.create("main", home)
print("> Hello")
mt.create("test", home)

print("Hello test process")
sleep(1)

mt.switch("test")

This errors and doesn't print anything, help would be appreciated..

Thanks
Edited on 25 March 2015 - 12:44 PM
KingofGamesYami #2
Posted 25 March 2015 - 12:43 PM
Change true on line 4 to false. I don't think it's actually erroring, blame the window api for rendering everything offscreen.

Edit: More specifically, the window api moves the cursor to 0,0 when creating a window, because that window won't have cursorBlink set to true (to "hide" the cursor). Therefor, when you try to print something on the parent term (like you were doing) it'll print it at 0, 0 (offscreen). You can check this by simply adding "\n " to the beginning of your print statements.
Edited on 25 March 2015 - 12:12 PM
DannySMc #3
Posted 25 March 2015 - 01:01 PM
Change true on line 4 to false. I don't think it's actually erroring, blame the window api for rendering everything offscreen.
Oh okay, will do that and test it, thanks!
DannySMc #4
Posted 25 March 2015 - 01:41 PM
This my friend doesn't change anything I can not switch though :/ although the processes are created.. I think.
MKlegoman357 #5
Posted 25 March 2015 - 01:41 PM
Whenever you get an error message please post it here, it really helps to hunt down the problem. Anyway, there are a few code and design issues.

Code first. You define your processes as a table with two keys: co and win, but you are trying to access indexes 1 and 2. (Note that keys and indexes in Lua are the same thing, it's just that in other languages a numerically indexed table's item can be accessed with a number (index) and for something like a Dictionary it's a key (usually a string))

Design: you redirect to a window only in the switch function. That means that after you 'switch' to another task every other task will be drawing to that task's window. You should actually be redirecting before you resume the coroutine with event data and optionally redirect back to something else after you resumed the coroutine. In your 'switch' function you should probably be setting the current program's window's focus and hiding the previously ran program's window. Also, the 'killing' of a coroutine should not just be a redirect, but rather just throwing the coroutine out of the process table, thus never resuming it again.

EDIT: the last argument to window.create is 'isVisible'. That means if it is set to true the window will start drawing everything onto the screen. You should onlt do that if you are intending to make the newly created process visible from the start of it's creation.
Edited on 25 March 2015 - 12:43 PM
DannySMc #6
Posted 25 March 2015 - 01:44 PM
Whenever you get an error message please post it here, it really helps to hunt down the problem. Anyway, there are a few code and design issues.

Code first. You define your processes as a table with two keys: co and win, but you are trying to access indexes 1 and 2. (Note that keys and indexes in Lua are the same thing, it's just that in other languages a numerically indexed table's item can be accessed with a number (index) and for something like a Dictionary it's a key (usually a string))

Design: you redirect to a window only in the switch function. That means that after you 'switch' to another task every other task will be drawing to that task's window. You should actually be redirecting before you resume the coroutine with event data and optionally redirect back to something else after you resumed the coroutine. In your 'switch' function you should probably be setting the current program's window's focus and hiding the previously ran program's window. Also, the 'killing' of a coroutine should not just be a redirect, but rather just throwing the coroutine out of the process table, thus never resuming it again.

I have edited the code now, and given the new code I get the code: attempt to index a nil value.
Creator #7
Posted 25 March 2015 - 02:12 PM
I would not load it as an API, since then the main program is the caller. I would make it like this: The programs accepts arguments. You run it with some arguments(let's say desktop app), then it passes these arguments to the new() function. Afterwards, the desktop can initialize new threads.

Creator
DannySMc #8
Posted 25 March 2015 - 02:22 PM
I would not load it as an API, since then the main program is the caller. I would make it like this: The programs accepts arguments. You run it with some arguments(let's say desktop app), then it passes these arguments to the new() function. Afterwards, the desktop can initialize new threads.

Creator

You are really helpful, but I didn't get that because I AM A NOOB AT THIS. hahaha I hate not knowing how things work >.<
KingofGamesYami #9
Posted 25 March 2015 - 02:24 PM
I would not load it as an API, since then the main program is the caller. I would make it like this: The programs accepts arguments. You run it with some arguments(let's say desktop app), then it passes these arguments to the new() function. Afterwards, the desktop can initialize new threads.

Creator
Um, what? Loading it as an API is perfectly fine. It's loaded into the global table, so any future programs could access it. The one problem with loading it as an API is it cannot access the shell.
DannySMc #10
Posted 25 March 2015 - 02:25 PM
Still so confused haha

I just want a coroutine manager and buffer, so when I do like: thread.create(<name_of_task>)
and then I can do like: thread.switch(<name_of_task>) and it will switch to that coroutine and buffer… >.<
KingofGamesYami #11
Posted 25 March 2015 - 02:34 PM
The problem is, things don't work that nicely. You can just cut off a program from events entirely, you have to cut them off from only user input. For example, if a program starts a timer, then you switch, and the timer goes off, it'll 'eat' the timer unless you give the timer to that coroutine.

In addition, you have to make sure the coroutine isn't dead.

I'm currently messing with making a "thread" object, if you want to use it here's the code:
Spoiler

local routine = {
	resume = function( self, ... )
		local cur = term.current()
		term.redirect( self.window )
		local stuff = {coroutine.resume( self.routine, ... )}
		term.redirect( cur )
		return unpack( stuff )
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
local function init( func, x, y, maxx, maxy )
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end

Just ask for instructions if you don't understand what my metatable is doing :)/>.
Edited on 25 March 2015 - 01:54 PM
DannySMc #12
Posted 25 March 2015 - 02:36 PM
The problem is, things don't work that nicely. You can just cut off a program from events entirely, you have to cut them off from only user input. For example, if a program starts a timer, then you switch, and the timer goes off, it'll 'eat' the timer unless you give the timer to that coroutine.

In addition, you have to make sure the coroutine isn't dead.

I'm currently messing with making a "thread" object, if you wanna use it here's the code:
Spoiler

local routine = {
	resume = function( self, ... )
		local cur = term.current()
		term.redirect( self.window )
		return coroutine.resume( self.routine, ... ), term.redirect( cur )
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
local function init( func, x, y, maxx, maxy )
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end

Just ask for instructions if you don't understand what my metatable is doing :)/>.

Can I have a usage for this? xD I just need to be able to switch back and forth when the program uses os.pullEvent() and I need to be able to get a list of names of the processes that are currently running, but thank you!
KingofGamesYami #13
Posted 25 March 2015 - 02:42 PM
My thread object doesn't do everything for you, it's not a manager. However, it does make things easier for you.


local obj = init( function() print( "Hi" ) end, 1, 1, term.getSize() )
--#creates and returns my object
local sStatus = obj:status()
--#returns the status of the coroutine of the object
local values = {obj:resume( ... )}
--#redirects to the internal window, runs the coroutine, returns results, redirects to the original term
obj.write( "Hi" )
--#writes to the internal window
obj.clear()
--#clears the internal window
obj.redraw
--#redraws the internal window
--#etc. for  all window functions

Also, I just noticed a bug… I'll be fixing it shortly. Fixed it.
Edited on 25 March 2015 - 01:44 PM
DannySMc #14
Posted 25 March 2015 - 03:14 PM
My thread object doesn't do everything for you, it's not a manager. However, it does make things easier for you.


local obj = init( function() print( "Hi" ) end, 1, 1, term.getSize() )
--#creates and returns my object
local sStatus = obj:status()
--#returns the status of the coroutine of the object
local values = {obj:resume( ... )}
--#redirects to the internal window, runs the coroutine, returns results, redirects to the original term
obj.write( "Hi" )
--#writes to the internal window
obj.clear()
--#clears the internal window
obj.redraw
--#redraws the internal window
--#etc. for  all window functions

Also, I just noticed a bug… I'll be fixing it shortly. Fixed it.

Also I have to ask, does it redirect to the window? As all my draw functions work using term, not a custom screen object? :s
KingofGamesYami #15
Posted 25 March 2015 - 03:41 PM
Yes. The coroutine will draw to it's own window, which you can set the visibility of and redraw as appropriate by use of obj.setVisibe and obj.redraw

It's made so that the function running inside of it doesn't know it's running there.

Edit: I've made a little manager, I don't know how well it works, but it's heavily commented. I based it on my knowledge of multishell and parallel.
Spoiler
local routine = {
	resume = function( self, ... ) --#custom resume thing
		local cur = term.current() --#get the current term
		term.redirect( self.window ) --#redirect to the thread's window
		local stuff = {coroutine.resume( self.routine, ... )} --#run the thread's coroutine, catching any returns
		term.redirect( cur ) --#redirect back to the term
		return unpack( stuff ) --#return anything returned by the coroutine
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
local function init( func, x, y, maxx, maxy ) --#create a thread
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end

local tUserEvents = {char=true,key=true,mouse_click=true,monitor_touch=true,paste=true,mouse_scroll=true,mouse_drag=true} --#events generated by the user interfacing with a program

local tRunning = {}
local tFilters = {}
local sCurrent

function launch( sName, thing ) --#creates a new coroutine given a) the function or B)/> the file location
	if type( thing ) == "function" then
		tRunning[ sName ] = init( thing, 1, 1, term.getSize() )
	elseif type( thing ) == "string" and fs.exists( thing ) then
		tRunning[ sName ] = init( loadfile( thing ), 1, 1, term.getSize() )
	else
		error( "Expected function/string program, got " .. type( sName ), 2 )
	end
	return true
end

function setCurrent( sName )
	if not tRunning[ sName ] then
		error( "No such process", 2 )
	end
	sCurrent = sName
	return true
end

function run() --#runs all routines
	local tEventData = {} --#start an empty table for events
	while true do --#infinately loop
		for k, co in pairs( tRunning ) do --#iterate through the running routines
			if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then --#if the coroutine is the current routing and the event was equal to the filter or there was not a filter for that coroutine...
				tFilters[ k ] = co:resume( unpack( tEventData ) ) --#resume the coroutine with event data
			end
			if co:status() == "dead" then --#if the coroutine is finished, remove it
				tRunning[ k ] = nil
			end
		end
		if #tRunning == 0 then --#if there's no routines left, exit to program that called this
			break
		end
		tEventData = {coroutine.yield()} --#grab some event data, we don't want to be terminated though so we'll use yield directly.
	end
end
Edited on 25 March 2015 - 02:46 PM
DannySMc #16
Posted 25 March 2015 - 04:08 PM
-snip-

At some point I will thank you massively some how.. Will test it out when I get a spare moment :)/>
Creator #17
Posted 25 March 2015 - 06:31 PM
I would not load it as an API, since then the main program is the caller. I would make it like this: The programs accepts arguments. You run it with some arguments(let's say desktop app), then it passes these arguments to the new() function. Afterwards, the desktop can initialize new threads.

Creator

You are really helpful, but I didn't get that because I AM A NOOB AT THIS. hahaha I hate not knowing how things work >.<

The idea is the following:

You have your kernel file. Then you make this code:


local func = loadfile(pathToKernel)
local desktop = loadfile(pathToDesktop)
func(desktop)
--The end of the kernel file looks like this:
local args = {...}
new(unpack(args))
run()

new() is the func that initializes a new thread. And run beggins the multitasking.
Edited on 25 March 2015 - 05:32 PM
DannySMc #18
Posted 25 March 2015 - 08:38 PM
Yes. The coroutine will draw to it's own window, which you can set the visibility of and redraw as appropriate by use of obj.setVisibe and obj.redraw

It's made so that the function running inside of it doesn't know it's running there.

Edit: I've made a little manager, I don't know how well it works, but it's heavily commented. I based it on my knowledge of multishell and parallel.
Spoiler
local routine = {
	resume = function( self, ... ) --#custom resume thing
		local cur = term.current() --#get the current term
		term.redirect( self.window ) --#redirect to the thread's window
		local stuff = {coroutine.resume( self.routine, ... )} --#run the thread's coroutine, catching any returns
		term.redirect( cur ) --#redirect back to the term
		return unpack( stuff ) --#return anything returned by the coroutine
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
local function init( func, x, y, maxx, maxy ) --#create a thread
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end

local tUserEvents = {char=true,key=true,mouse_click=true,monitor_touch=true,paste=true,mouse_scroll=true,mouse_drag=true} --#events generated by the user interfacing with a program

local tRunning = {}
local tFilters = {}
local sCurrent

function launch( sName, thing ) --#creates a new coroutine given a) the function or B)/>/>/> the file location
	if type( thing ) == "function" then
		tRunning[ sName ] = init( thing, 1, 1, term.getSize() )
	elseif type( thing ) == "string" and fs.exists( thing ) then
		tRunning[ sName ] = init( loadfile( thing ), 1, 1, term.getSize() )
	else
		error( "Expected function/string program, got " .. type( sName ), 2 )
	end
	return true
end

function setCurrent( sName )
	if not tRunning[ sName ] then
		error( "No such process", 2 )
	end
	sCurrent = sName
	return true
end

function run() --#runs all routines
	local tEventData = {} --#start an empty table for events
	while true do --#infinately loop
		for k, co in pairs( tRunning ) do --#iterate through the running routines
			if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then --#if the coroutine is the current routing and the event was equal to the filter or there was not a filter for that coroutine...
				tFilters[ k ] = co:resume( unpack( tEventData ) ) --#resume the coroutine with event data
			end
			if co:status() == "dead" then --#if the coroutine is finished, remove it
				tRunning[ k ] = nil
			end
		end
		if #tRunning == 0 then --#if there's no routines left, exit to program that called this
			break
		end
		tEventData = {coroutine.yield()} --#grab some event data, we don't want to be terminated though so we'll use yield directly.
	end
end

This is amazing, but how do I capture input now…? I used to use os.pullEvent() for everything but now it doesn't work?

Code:
Spoiler

function loadapi()
		if fs.exists("progutils") then
		os.loadAPI("progutils")
				return true
		end
		aa = aa or {}
		local a = http.get("https://vault.dannysmc.com/lua/api/dannysmcapi.lua")
		a = a.readAll()
		local env = {}
		a = loadstring(a)
		local env = getfenv()
		setfenv(a,env)
		local status, err = pcall(a, unpack(aa))
		if (not status) and err then
				printError("Error loading api")
				return false
		end
		local returned = err
		env = env
		_G["progutils"] = env
end
loadapi()

local routine = {
	resume = function( self, ... ) --#custom resume thing
		local cur = term.current() --#get the current term
		term.redirect( self.window ) --#redirect to the thread's window
		local stuff = {coroutine.resume( self.routine, ... )} --#run the thread's coroutine, catching any returns
		term.redirect( cur ) --#redirect back to the term
		return unpack( stuff ) --#return anything returned by the coroutine
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
local function init( func, x, y, maxx, maxy ) --#create a thread
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end

local tUserEvents = {char=true,key=true,mouse_click=true,monitor_touch=true,paste=true,mouse_scroll=true,mouse_drag=true} --#events generated by the user interfacing with a program

local tRunning = {}
local tFilters = {}
local sCurrent

function launch( sName, thing ) --#creates a new coroutine given a) the function or B)/>/>/> the file location
	if type( thing ) == "function" then
		tRunning[ sName ] = init( thing, 1, 1, term.getSize() )
	elseif type( thing ) == "string" and fs.exists( thing ) then
		tRunning[ sName ] = init( loadfile( thing ), 1, 1, term.getSize() )
	else
		error( "Expected function/string program, got " .. type( sName ), 2 )
	end
	return true
end

function setCurrent( sName )
	if not tRunning[ sName ] then
		error( "No such process", 2 )
	end
	sCurrent = sName
	return true
end

function run() --#runs all routines
	local tEventData = {} --#start an empty table for events
	while true do --#infinately loop
		for k, co in pairs( tRunning ) do --#iterate through the running routines
			if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then --#if the coroutine is the current routing and the event was equal to the filter or there was not a filter for that coroutine...
								tFilters[ k ] = co:resume( unpack( tEventData ) ) --#resume the coroutine with event data
			end
			if co:status() == "dead" then --#if the coroutine is finished, remove it
				tRunning[ k ] = nil
			end
		end
		if #tRunning == 0 then --#if there's no routines left, exit to program that called this
			break
		end
		tEventData = {coroutine.yield()} --#grab some event data, we don't want to be terminated though so we'll use yield directly.
	end
end

function main()
	for i = 1, 10 do
		print(i)
	end
	while true do
		local _, char = os.pullEvent()
		if char == "h" then
			break
		end
	end
end

function test()
	print("> Create checksum of string:")
	strin = read()
	print(crypt.sha256(strin))
	sleep(1)
end

parent = init(main, 1, 1, 51, 19)
child1 = init(test, 1, 1, 51, 19)

print("Menu:")
print("1. Main")
print("2. Test")
while true do
	local args = { os.pullEvent() }
	if args[1] == "char" then
		if args[2] == "1" then
			-- Run main
			if parent:status() == "dead" then
				parent:redraw()
			else
				parent:resume()
			end
		elseif args[2] == "2" then
			-- Run test
			if child1:status() == "dead" then
				child1:redraw()
			else
				child1:resume()
			end
		end
	end
end

Thanks, if possible could I have an example? that would be awesome, thank you so much! I have not lost hope yet haha :P/>
Edited on 25 March 2015 - 07:39 PM
KingofGamesYami #19
Posted 25 March 2015 - 09:46 PM
You are using it incorrectly, read through the code of the run() function - it makes events work. You have to give your co routines the event data, like run() does.

Edit: Also, the run() function will not pass any routine mouse_click, key, char, etc. unless the routine is set as currently running. This is so you can run multiple programs at once.
Edited on 25 March 2015 - 08:47 PM
DannySMc #20
Posted 25 March 2015 - 10:34 PM
You are using it incorrectly, read through the code of the run() function - it makes events work. You have to give your co routines the event data, like run() does.

Edit: Also, the run() function will not pass any routine mouse_click, key, char, etc. unless the routine is set as currently running. This is so you can run multiple programs at once.

Little confused on what you mean…:s could you make an example please? Thanks
KingofGamesYami #21
Posted 26 March 2015 - 01:17 AM

os.loadAPI( "thread" ) --#let's assume it's called thread
thread.launch( "desktop", "desktop" ) --#load the "desktop" program
thread.launch( "shell", "rom/programs/shell" ) --#load the default shell program
thread.setCurrent( "shell" ) --#shell is now the only program that can receive user input
thread.run() --#runs everything

If you want something other than the currently running program to receive all the events, I'd say run it in parallel with thread.run, ei:

parallel.waitForAny( thread.run, function()
   --#do things here
end )
DannySMc #22
Posted 26 March 2015 - 09:08 AM

os.loadAPI( "thread" ) --#let's assume it's called thread
thread.launch( "desktop", "desktop" ) --#load the "desktop" program
thread.launch( "shell", "rom/programs/shell" ) --#load the default shell program
thread.setCurrent( "shell" ) --#shell is now the only program that can receive user input
thread.run() --#runs everything

If you want something other than the currently running program to receive all the events, I'd say run it in parallel with thread.run, ei:

parallel.waitForAny( thread.run, function()
   --#do things here
end )

I don't get how to actually run, say just the one coroutine? How would I run a coroutine? Because the script I used (which is above) works? but no input? so how do I actually run the script?:S

How do I redraw it as well? There is no handle? so how do I like redraw it? using the 'launch' method? As the init method was able to redraw and stuff, but now I can't do anything with it?:S
Edited on 26 March 2015 - 08:09 AM
KingofGamesYami #23
Posted 26 March 2015 - 12:04 PM
I don't get how to actually run, say just the one coroutine?

You can't. Running a single coroutine is bad, you will be eating events the other coroutine(s) are waiting for, causing them to break.

How do I redraw it as well? There is no handle? so how do I like redraw it? using the 'launch' method?
It's not perfect, I can modify it for this sort of stuff, I just hadn't thought about it.
DannySMc #24
Posted 26 March 2015 - 12:45 PM
I don't get how to actually run, say just the one coroutine?

You can't. Running a single coroutine is bad, you will be eating events the other coroutine(s) are waiting for, causing them to break.

How do I redraw it as well? There is no handle? so how do I like redraw it? using the 'launch' method?
It's not perfect, I can modify it for this sort of stuff, I just hadn't thought about it.

Ahh well the plan was to run the main coroutine and that would run the menu, then each program will be run in another coroutine? and if you could add a way of redrawing that would be amazing thank you!
KingofGamesYami #25
Posted 26 March 2015 - 02:46 PM
What a coroutine manager does is emulates the 'top layer' of the CraftOS event system.

os.pullEventRaw( filter ) is the same as coroutine.yield( filter )

When I'm resuming a coroutine that pulls an event, it returns the filter. Now, on our end, we have to account for that filter ourselves - it isn't handled by os.pullEvent.

Programs are generally designed as if they are the only thing running. If you were playing a game, and had something using read() running in the background, would you want the program to receive wwwwwwaaassssddasassawwdawwsdawwadawws or just the game? That's the other part I'm doing here.

However, if a program uses sleep(1), it will yield repeatedly until it gets a timer event with a matching id. If, for example, we didn't give it any events, that timer event may get lost. That effectively turns sleep(1) into an infinite wait, which is obviously not something we want to do.

Now, we also don't want our coroutines drawing to the same window - and the coroutines not 'active' shouldn't be drawn either. Therefore, we use different windows for each routine.

I'll explain my code line by line to help.

local routine = {
	resume = function( self, ... ) 
		local cur = term.current()
		term.redirect( self.window )
		local stuff = {coroutine.resume( self.routine, ... )}
		term.redirect( cur )
		return unpack( stuff )
	end,
	status = function( self )
		return coroutine.status( self.routine )
	end
}
Here we have a table of things that we'll need to know - resume does the standard coroutine.resume, but with additional redirection. status returns the status of the coroutine.


local function init( func, x, y, maxx, maxy ) --#create a thread
	return setmetatable({routine=coroutine.create( func ),window=window.create( term.current(), x, y, maxx, maxy, false ) },{__index=function( t, k )return routine[ k ] or t.window[ k ] or rawget( t, k );end})
end
This bit is weird, basically I'm making a table with a coroutine and a window, then creating a function for __index that redirects to either the routine table defined above, the window inside the table, or the table itself. This is just for ease-of-use, not really necessary.


local tUserEvents = {char=true,key=true,mouse_click=true,monitor_touch=true,paste=true,mouse_scroll=true,mouse_drag=true}
Here I've defined a table of 'user' events, events fired by the user that we don't want programs not currently running to get.


local tRunning = {}
local tFilters = {}
local sCurrent
tRunning will be a table of all coroutines
tFilters will be a table of things coroutine.resume caught (from inside the program, what you gave os.pullEvent[Raw]/coroutine.yield)
sCurrent is the currently running routine.


function launch( sName, thing )
	if type( thing ) == "function" then
		tRunning[ sName ] = init( thing, 1, 1, term.getSize() )
	elseif type( thing ) == "string" and fs.exists( thing ) then
		tRunning[ sName ] = init( loadfile( thing ), 1, 1, term.getSize() )
	else
		error( "Expected function/string program, got " .. type( sName ), 2 )
	end
	return true
end
A function that adds coroutines to the tRunning table, at the specified key. I've written it to test what is given, and either load a program from a path and create a coroutine from that, or create a coroutine from a function. Either way works.


function setCurrent( sName )
	if not tRunning[ sName ] then
		error( "No such process", 2 )
	end
	if tRunning[ sCurrent ] then
		tRunning[ sCurrent ].setVisible( false )
	end
	sCurrent = sName
	tRunning[ sName ].setVisible( true )
	tRunning[ sName ].redraw()
	return true
end
This part sets the routine specified as the currently running routine, and sets the visibility/redraws as appropriate.


function run() --#runs all routines
	local tEventData = {} --#start an empty table for events
	while true do --#infinately loop
		for k, co in pairs( tRunning ) do --#iterate through the running routines
			if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then --#if the coroutine is the current routing and the event was equal to the filter or there was not a filter for that coroutine...
				tFilters[ k ] = co:resume( unpack( tEventData ) ) --#resume the coroutine with event data
			end
			if co:status() == "dead" then --#if the coroutine is finished, remove it
				tRunning[ k ] = nil
			end
		end
		if #tRunning == 0 then --#if there's no routines left, exit to program that called this
			break
		end
		tEventData = {coroutine.yield()} --#grab some event data, we don't want to be terminated though so we'll use yield directly.
	end
end

This part is running the coroutines. I'm not really sure how to explain it, other than the comments I've already applied.

Given this information, you should be able to modify my code to do whatever you like, if you have questions on something, feel free to ask.
DannySMc #26
Posted 26 March 2015 - 03:03 PM
- snip -

Okay I get all of this, but how would I actually go about getting a users input? Now in there you may have said it but I am not quite sure on how to actually get the data… Does this mean I need some kind of new read function? Or some kind of new mouse_click event? Because if so I probably won't be able to do it :/ I only really know how to use os.pullEvent() -> I KNOW ITS THE SAME AS COROUTINE.YIELD(), but I am really unsure on how this all works :(/> I got hopeful when testing your code yesterday as I was able to switch back and forth using the init() functions you made, but I couldn't actually get any input.. So this is where I am stuck as I don't know which one I am supposed to be using:
launch() or init()?

How do I use (say init as this worked and made me hopeful) and get some kind of input from it?

Thanks sorry I am not very good, it's just I get quite a bit with CC, it's just a few things, one being the coroutines… :/
Thanks for your help though, really appreciated!!
Edited on 26 March 2015 - 02:04 PM
KingofGamesYami #27
Posted 26 March 2015 - 03:09 PM
You have to GIVE the user input to the coroutine For this, I'll go back to using just plain code (without any of the api stuff)

Here's how you'd run a single, lone coroutine until it's finished:


local co = coroutine.create( function() print( "hello" ) end )
local filter
repeat
  filter = coroutine.resume( co, coroutine.yield( filter ) )
until coroutine.status( co ) == "dead"
DannySMc #28
Posted 26 March 2015 - 03:15 PM
You have to GIVE the user input to the coroutine For this, I'll go back to using just plain code (without any of the api stuff)

Here's how you'd run a single, lone coroutine until it's finished:


local co = coroutine.create( function() print( "hello" ) end )
local filter
repeat
  filter = coroutine.resume( co, coroutine.yield( filter ) )
until coroutine.status( co ) == "dead"

How would you give the user input the the code? :s I plan to run many coroutines, but I want to be able to switch between them, so I need to figure out how to make it so co routines can be accessed with inputs? So how would I give them access? This is so confusing >.<
KingofGamesYami #29
Posted 26 March 2015 - 03:42 PM
What I posted gives the coroutine access to *everything*.

This line in my api:

if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then
…is filtering events.


(k == sCurrent or not tUserEvents[ tEventData[1] ])
this routine is the current or the event data isn't on the blacklist


(tFilters[ k ] == tEventData[1] or not tFilters[ k ])
the filter for this routine is the event or there is no filter for this routine

Read what I've written about the setCurrent function, it's the part that deals with this (sort of).
Edited on 26 March 2015 - 02:42 PM
DannySMc #30
Posted 26 March 2015 - 04:13 PM
What I posted gives the coroutine access to *everything*.

This line in my api:

if (k == sCurrent or not tUserEvents[ tEventData[1] ]) and (tFilters[ k ] == tEventData[1] or not tFilters[ k ]) then
…is filtering events.


(k == sCurrent or not tUserEvents[ tEventData[1] ])
this routine is the current or the event data isn't on the blacklist


(tFilters[ k ] == tEventData[1] or not tFilters[ k ])
the filter for this routine is the event or there is no filter for this routine

Read what I've written about the setCurrent function, it's the part that deals with this (sort of).

But when I used it, it wouldn't let me use the read function?
KingofGamesYami #31
Posted 26 March 2015 - 05:10 PM
It *should* let you use the read() function, as long as you used
thread.setCurrent( "Name_of_functin")
before
thread.run()