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

Multitasking Buffering

Started by PrinzJuliano, 03 September 2013 - 06:26 AM
PrinzJuliano #1
Posted 03 September 2013 - 08:26 AM
Hello Community,

I've seen some nice "OS"s with multitasking and multi buffering.

My question is: Can someone show me how multitasking & screen buffering works? So like run two programs (e.g. start Program shell)

the first is not rendering, only buffering, the 2nd program is directly rendering and buffering and if i press e.g. TAB i like to set the first program in buffer & render mode and the 2nd program only to buffer.

Can you help me?

Thanks,
your highly interested PrinzJuliano
Zudo #2
Posted 03 September 2013 - 08:28 AM
I want to know too.
Engineer #3
Posted 03 September 2013 - 09:05 AM
This most likely uses coroutines and environment manipulation. As I will not get into coroutines for this post, you might want to look that up.

So, environment manipulating, how would we do that?
It is very simple with setfenv function, it takes as arguments the function and the environment you set it to.
So, you basically have something like this for the buffering coroutine:

local bufferStack = {} -- The bufferstack
local bufferTerm = {} -- Every term api call but puts that in a stack

-- You would want to create a good system for storing your buffers, since 3 or more functions could buffer. This code is mostly pseudo code, since I dont want to create an OS :P/>/>/>

local func, err = loadfile( file )
--thats the function of  a program
-- set the fenv accordingly if the program is on the screen or not.

if programOnScreen then
	 setfenv( func, { __index = _G })
	 --run the program
end

if screenChanges then
	setfenv( func, setmetatable( { term = bufferTerm }, { __index = _G }))
	-- continue the program
end


Of course, this is not fully working etc, but you hopefully you get the point. Implementing this is a bigger task then you can expect it to be.

Edit: i should have tested this before I posted this, turns out I did not speak the truth. Sorry!
ElvishJerricco #4
Posted 03 September 2013 - 09:19 AM
This most likely uses coroutines and environment manipulation. As I will not get into coroutines for this post, you might want to look that up.

So, environment manipulating, how would we do that?
It is very simple with setfenv function, it takes as arguments the function and the environment you set it to.
So, you basically have something like this for the buffering coroutine:

local bufferStack = {} -- The bufferstack
local bufferTerm = {} -- Every term api call but puts that in a stack

-- You would want to create a good system for storing your buffers, since 3 or more functions could buffer. This code is mostly pseudo code, since I dont want to create an OS :P/>/>/>

local func, err = loadfile( file )
--thats the function of  a program
-- set the fenv accordingly if the program is on the screen or not.

if programOnScreen then
	 setfenv( func, { __index = _G })
	 --run the program
end

if screenChanges then
	setfenv( func, setmetatable( { term = bufferTerm }, { __index = _G }))
	-- continue the program
end

Of course, this is not fully working etc, but you hopefully you get the point. Implementing this is a bigger task then you can expect it to be.

Won't work. You're changing what term is for the file, but not for functions like write(), so things will be printed to the screen anyway. For this reason most people use term.redirect to redirect a coroutines term traffic to a buffer. Then whenever that coroutine is brought to the foreground, its term buffer is displayed, and whenever it gets updated, the updates are still redirected to the buffer, but get displayed anyway. Because if you didn't put anything the coroutine outputs in the buffer, it wouldn't show up next time you wanted to see it.
Engineer #5
Posted 03 September 2013 - 09:41 AM
This most likely uses coroutines and environment manipulation. As I will not get into coroutines for this post, you might want to look that up.

So, environment manipulating, how would we do that?
It is very simple with setfenv function, it takes as arguments the function and the environment you set it to.
So, you basically have something like this for the buffering coroutine:

local bufferStack = {} -- The bufferstack
local bufferTerm = {} -- Every term api call but puts that in a stack

-- You would want to create a good system for storing your buffers, since 3 or more functions could buffer. This code is mostly pseudo code, since I dont want to create an OS :P/>/>/>/>/>

local func, err = loadfile( file )
--thats the function of  a program
-- set the fenv accordingly if the program is on the screen or not.

if programOnScreen then
	 setfenv( func, { __index = _G })
	 --run the program
end

if screenChanges then
	setfenv( func, setmetatable( { term = bufferTerm }, { __index = _G }))
	-- continue the program
end

Of course, this is not fully working etc, but you hopefully you get the point. Implementing this is a bigger task then you can expect it to be.

Won't work. You're changing what term is for the file, but not for functions like write()

Take a closer look at print and write:
Spoilerwrite:

function write( sText )
	local w,h = term.getSize()		
	local x,y = term.getCursorPos()
	
	local nLinesPrinted = 0
	local function newLine()
		if y + 1 <= h then
			term.setCursorPos(1, y + 1)
		else
			term.setCursorPos(1, h)
			term.scroll(1)
		end
		x, y = term.getCursorPos()
		nLinesPrinted = nLinesPrinted + 1
	end
	
	-- Print the line with proper word wrapping
	while string.len(sText) > 0 do
		local whitespace = string.match( sText, "^[ \t]+" )
		if whitespace then
			-- Print whitespace
			term.write( whitespace )
			x,y = term.getCursorPos()
			sText = string.sub( sText, string.len(whitespace) + 1 )
		end
		
		local newline = string.match( sText, "^\n" )
		if newline then
			-- Print newlines
			newLine()
			sText = string.sub( sText, 2 )
		end
		
		local text = string.match( sText, "^[^ \t\n]+" )
		if text then
			sText = string.sub( sText, string.len(text) + 1 )
			if string.len(text) > w then
				-- Print a multiline word				
				while string.len( text ) > 0 do
					if x > w then
						newLine()
					end
					term.write( text ) -- huh?
					text = string.sub( text, (w-x) + 2 )
					x,y = term.getCursorPos()
				end
			else
				-- Print a word normally
				if x + string.len(text) - 1 > w then
					newLine()
				end
				term.write( text ) -- Oh, whats here?
				x,y = term.getCursorPos()
			end
		end
	end
	
	return nLinesPrinted
end
print:

function print( ... )
	local nLinesPrinted = 0
	for n,v in ipairs( { ... } ) do
		nLinesPrinted = nLinesPrinted + write( tostring( v ) ) -- uses write! what a surprise!
	end
	nLinesPrinted = nLinesPrinted + write( "\n" )
	return nLinesPrinted
end

And I realized it will not work afterwards, which is why I edited it. I didnt see your post when I did that.
theoriginalbit #6
Posted 03 September 2013 - 09:51 AM
I didnt see your post when I did that.
His post wasn't there.
PrinzJuliano #7
Posted 03 September 2013 - 10:30 AM
Ehh, sorry Engineer but your posts ain't helped me…

I still know how the theory is but not how you script it.
Lyqyd #8
Posted 03 September 2013 - 10:52 AM
If you want to see an example of a buffering redirect, you could take a look at my framebuffer API or check out Gopher's redirect API here on the forums. It would probably be easier to answer questions you have about the way an existing API works than to try to explain the whole process. Read through either one of those and ask any questions you have.
PrinzJuliano #9
Posted 03 September 2013 - 11:28 AM
Ok thanks: now i know that i can do code like this:


os.loadAPI("framebuffer")
w,h = term.getSize()
buffer = framebuffer.new(w,h,colors.black)
term.redirect(buffer)
print("Hello")
term.restore()

But how do i redirect a running program to buffer and how do i display the buffer?

with buffer.render() ?
Lyqyd #10
Posted 03 September 2013 - 11:52 AM
Well, you got the usage slightly wrong. _color should be true or false, depending on whether the buffer should use all sixteen colors or just black and white. To redirect a program to use it, you either redirect before resuming its coroutine and restore after it yields, or you could create a program (similar to the monitor program) that sets up a redirect and redirects the terminal before starting to use it.

Displaying the buffer is implemented elsewhere in LyqydOS, since there's also window compositing to do. The actual drawing occurs in the compositor, specifically in the draw function, the second for i = 1, self.y loop. You'll note that there's some fancy stuff with string.sub and string.match, which finds the largest contiguous string of identical text/background color to optimize screen drawing. A much simpler version that went character by character to set the text and background colors and draw to the screen would be fairly simple to implement. I believe gopher's redirect API uses two-dimensional tables, so each character on the screen is its own index in the table and is drawn individually.
PrinzJuliano #11
Posted 03 September 2013 - 12:33 PM
And how do i use the compositor api?
Lyqyd #12
Posted 03 September 2013 - 12:53 PM
Well, if you wanted to use that, it will actually handle the frame buffers itself. All you have to do is get a new compositor instance:

local myCompositor = compositor.new()

From there, you can create a new redirect to use, which uses the framebuffer API for you:

newRedirect = myCompositor:newBuffer()

Then redirect your program with term.redirect(newRedirect) and it will draw into its layer. The compositor API will automatically draw to the screen. There are quite a few options you can use to sort of tweak the way it acts, which I can explain further if necessary.
PrinzJuliano #13
Posted 04 September 2013 - 11:22 AM
Can you just show me how i create a multitasking program running two programs?
theoriginalbit #14
Posted 04 September 2013 - 11:25 AM
Can you just show me how i create a multitasking program running two programs?
Lyqyd said:
Here in Ask a Pro, the effort people are willing to expend helping you is usually about equal to the effort you are putting in.
PrinzJuliano #15
Posted 04 September 2013 - 03:30 PM
I experimented my own on try to succeed making a multitasking programm with the help lyqyd gave me but I failed hardly. So i wanna look at an example code.
theoriginalbit #16
Posted 04 September 2013 - 03:57 PM
Post what you did, and we can help you fix it and make it work. That way you understand the code much better.
Kingdaro #17
Posted 04 September 2013 - 04:13 PM
I think OP is trying to tackle too many concepts at once.

What you should understand first, Prinz, is what coroutines are, how they work, and how to use them with ComputerCraft. Coroutines are the foundations of all multitasking and windowing systems of computercraft.

A coroutine is essentially a lua function that can be paused and then resumed later by another function. There truly is no such thing as multitasking, just a bunch of different functions "passing the ball" per-say.

An example of single coroutine usage:

function hello()
   print "Hello"
   coroutine.yield()
   print "World!"
end

co = coroutine.create(hello)

Here, we have a single coroutine, created from a simple function. To "run" this coroutine, we resume it.

coroutine.resume(co)

But if you run this in a lua prompt, you'll notice that it only prints "Hello". This is because of the coroutine.yield() call in the hello() function.

The function coroutine.yield() stops the current coroutine that is running at that very instance in time. In this example, we resumed the coroutine "co" created from hello(). It printed "Hello", then "paused" the function.

If you want it to print "World!", you need to resume it again:

coroutine.resume(co)

After resuming it a second time, the function prints "World!". If we try to resume it again, we get an error:

failed to resume a dead coroutine
Or something along these lines. Once a coroutine is done, it's done, and can never be resumed again.

In a real-world example, a multitasking system is just a bunch of ^those that are yielding themselves and being resumed by a loop of events. The function os.pullEvent() is essentially a wrapper for coroutine.yield().

If you want to know more about coroutines and how to use them, here are some resources:
ComputerCraft tutorial on Coroutines
Coroutines Tutorial on lua-users
Coroutine Function Reference (only for reference, not for learning)