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.