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

[Solved] Buffer Techniques

Started by hbomb79, 20 September 2015 - 04:21 AM
hbomb79 #1
Posted 20 September 2015 - 06:21 AM
Hey guys, I am currently developing DynaCode which is a GUI framework similar to Flare but with XML support.

I have hit a sort of snag when it comes to rendering. Each window has a buffer which is a table created with code similar to this:


for y = 1, height do
	buffer[y] = {}
	for x = 1, width do
		buffer[y][x]={ char = " ", textColor = "color", backgroundColor = "color"}
	end
end

The problem with this, is that each window has one of these, and each element (button, label, input etc..) also has a loop doing something like this:


tText = {}
for cIndex = 1, (settings.text):len() do
tText[cIndex] = (settings.text):sub(cIndex, cIndex)
end
local tc, bg = util.getColor( settings.textColor ), util.getColor( settings.backgroundColor )
for x = 1, getWidth() do
if x <= nWidth and settings.y <= nHeight then
  tBuffer[settings.y][x+settings.x-1] = {
   textColor = tc,
   backgroundColor = bg,
   char = tText[x] or " ",
   nDefined = true
  }
end
end

and because of this, lag is present while spamming keys into inputs or when a large amount of nodes are present. No flickering is though ( see DynaCode source line 672 )

Flare, on the other hand seems to catch up (event appears to print multiple chars at the same time) and I attribute this to a better buffer technique.

I have looked at framebuffer and quickbuffer and can't find a buffer table like mine (table per y, containing a 'pixel' per x). This is the first time I have dabbled with buffers so any and all help would be great, I am desperate to learn how they work.

If you want to see DynaCodes source code so you can see what I mean by the buffer method then heres the link:

Line numbers relevant: https://github.com/h...master/DynaCode

621-700 for main program buffer merging
1280 for window drawing
1502 for buffer creation
1529 for windowBuffer and node merging (where the nodes are placed on the stages canvas)
2695 example of node (button) buffer creation

Thanks for reading, I look forward to learning how these darn things work.
- Harry (Skype: harrywfelton1).
Edited on 26 September 2015 - 07:34 AM
Bomb Bloke #2
Posted 20 September 2015 - 06:33 AM
You could be saving a lot of time here by using term.blit() - you'd be able to output entire lines with a single call.

That said, even without use of that function you shouldn't really have much of a delay when outputting single characters. You aren't redundantly redrawing the entire buffer contents when you only need to output parts, right?
hbomb79 #3
Posted 20 September 2015 - 06:38 AM
That, is my problem. It is infact calling the redraw on every element, changed or not. If I don't it will think they are no longer there and they will be invisible. I need a way to tell the program they are there without calling the entire buffer redraw.

If you can teach me how to do that, then the problem will be solved and I can go on my merry way
Edited on 20 September 2015 - 04:39 AM
Bomb Bloke #4
Posted 20 September 2015 - 06:44 AM
If I don't it will think they are no longer there and they will be invisible.

Sorry, I'm not sure I can make sense of that.

Once you've drawn something to the screen, it'll stay there until you draw over it. You therefore do not need to redraw your buffer until such time as whatever was drawn over it - eg another buffer - is to be "removed".

If one buffer is partially covering another, then when the lower buffer has something drawn to it, you should at that time figure out whether it's a visible part of the buffer (that should result in a screen draw on the spot) or an obscured part of the buffer (that shouldn't result in a screen draw until that part of the buffer becomes visible). That is to say, if one particular buffer is "on top", then at no time should another buffer be rendering over its screen real-estate.
Edited on 20 September 2015 - 04:46 AM
hbomb79 #5
Posted 20 September 2015 - 06:48 AM
If I don't order DynaCode to redraw every node, then it will think they aren't there. For instance when animating a node.

If you look at the source code, you can see that the canvas is reset every time because if I don't any animating nodes will be everywhere on the screen (smudged kind of)

E.G: If I animate a node to the right, it will still be visible where it started, and everywhere in between. to combat this I made it reset every pixel and redraw them, thus resulting in them becoming invisible if I don't tell DynaCode to redraw them.

I also don't know how to detect if a window is overlapping. I would have to check for every pixel and then somehow tell DynaCode what bit of the window needs to be redrawn…
Edited on 20 September 2015 - 04:51 AM
hbomb79 #6
Posted 20 September 2015 - 07:32 AM
I simply don't know how to make buffer. I do not understand how to only draw a node if it needs to be, because if I do that then any animated nodes will glitch because they appear at every animation step because where they just were has not been cleared.

Someone on the forums knows how to do this, your help in explaining it to me would be great
Grim Reaper #7
Posted 20 September 2015 - 07:46 AM
Depending on how complex your graphics are going to be and how many graphical objects will need to be drawn each drawing cycle, you should be fine by redrawing the entire screen each time. I tested the speed with this buffer, and on my computer it renders a buffer full of completely random background/text color mixtures 1000 times in ~0.4 seconds. This means the render rate is about 2500 renders/second, so you can redraw the 51 x 19 screen at about 42 frames per-second.

Of course, though, this doesn't take into account calculations and stuff that need to happen between renders, but you can rest assured, that if you do your calculations before the drawing, you have a reasonable amount of time to draw what you need.

As for checking if windows are overlapping, you could have a function for each buffer object that checks whether or not it's occupying a given point. Then, with an established z-order, you can determine what's occluding what.


function Window:isOccupyingPoint(x, y)
	return x >= self.x and x <= self.x + self.width - 1 and
               y >= self.y and y <= self.y + self.height - 1
end
Edited on 20 September 2015 - 05:47 AM
hbomb79 #8
Posted 20 September 2015 - 07:51 AM
Well, it is lagging for me while typing and even more so for my testers.

If you look at the source code you can see several buffers are being merged.
With the overlap check function, wouldn't that need to be called on every pixel in the window causing lag?

In a window you have a scene, a scene contains nodes. The nodes need to be redrawn. If you look at the source code you can how I am drawing it (using a for loop). If I spam the keys it takes long time to catch up.

If someone that has done this before can please explain how I would make it so that only the effected node is redrawn that would be great. I am at a complete loss of what to do.

Edit: Your link you provided is obviously a lot better than my method. However it doesn't help me because I am too dim-witted to understand the code.
Edited on 20 September 2015 - 05:55 AM
Bomb Bloke #9
Posted 20 September 2015 - 07:59 AM
If I don't order DynaCode to redraw every node, then it will think they aren't there. For instance when animating a node.

If you look at the source code, you can see that the canvas is reset every time because if I don't any animating nodes will be everywhere on the screen (smudged kind of)

E.G: If I animate a node to the right, it will still be visible where it started, and everywhere in between. to combat this I made it reset every pixel and redraw them, thus resulting in them becoming invisible if I don't tell DynaCode to redraw them.

Let's assume I'm not going to do much more than skim when it comes to thousands of lines of source, and that you need to provide a decent level of context if you wish to be understood. Why is it a problem if a node is invisible, if it's obscured by another node anyway? Why would you want to see or otherwise interact with something that's obscured?

If one window is covering another, and you want to move the top window aside, then you have your excuse to redraw the previously obscured part of the lower window (or else the top window will indeed leave a trail of ghost images). There's absolutely no need to do it if the top window is still covering the lower one, however. If a user is simply typing into the top window, then the lower windows aren't rendering over the top of it, and so nothing needs to be redrawn.

(And again, if you ARE allowing obscured buffers to render over the top of the buffers that are obscuring them, then ur doin it rong).

If you want to move nodes/buttons/whatever around within windows (potentially allowing them to overlap and so on), then you consider the window as a sub-display and the nodes as being more buffers that can render to that sub-display… and then proceed to follow the exact same rules as above.

I also don't know how to detect if a window is overlapping. I would have to check for every pixel and then somehow tell DynaCode what bit of the window needs to be redrawn…

Well, your framework would know where all the windows its handling are positioned, right? And it'd likewise know the order they're stacked on-screen, and what their dimensions are - using this information to figure out what window a given x/y co-ord belongs to is fairly trivial. You only need to recalculate that information when a window open/closes/moves.

Your basic line of thinking should be: Draw element to buffer => Buffer records the drawed information, and decides whether to update the actual screen with it. The buffer then doesn't consider whether to draw that information again until given a reason, such as something being moved over it and then that something being removed to re-reveal the relevant area.

Note that the buffer is doing work during this process. Your button-generating code shouldn't be attempting to alter the buffer's contents and determining whether to do a screen draw; it should be simply passing the information it wants to slot into the buffer to a buffer-dedicated function that makes that decision.

Silly questions, but have you played with the window API at all, and are you at all familiar with how terminal redirection works and how you might create a custom terminal object?
hbomb79 #10
Posted 20 September 2015 - 08:07 AM
You are misunderstanding my situation, it maybe my poor understanding of the subject. The nodes are going invisible… its a problem, they are not overlapped by any other nodes.

What is happening is every redraw the canvas is wiped, then it redraws the nodes. If I only tell it to redraw nodes that have changed, then unchanged nodes won't be redraw causing them to be invisible. I have messed with the windowAPI and DynaCode has its own terminal redirect. I have tried and tried again with no success, I am telling the node to be drawn to the buffer this ( I think ) is whats causing the lag, because every node is redrawing itself.

The reason the canvas is cleared is to solve the animation problem mentioned earlier.

I appreciate your help, but it just isn't making any sense for me.

Each node draws the a stage buffer. Each stage buffer draws to a program buffer which is then drawn to the terminal. I feel like the merging of the stage buffers is causing lag issues (around line 621)
Edited on 20 September 2015 - 06:07 AM
Grim Reaper #11
Posted 20 September 2015 - 08:14 AM
Could you please explain the relationships between program buffers, stage buffers, window buffers, and their elements/nodes? Likewise, if they're not actually the same thing, could you please explain the relationships between programs, stages, windows, and nodes?
hbomb79 #12
Posted 20 September 2015 - 08:21 AM
Right, so a program is a DynaCode instance. It manages the drawing of the windows. It has one buffer the size of the screen (term.getSize())

Everytime an event is caught by the main event manager, it queues a redraw. DynaCode asks each stage (window) for its buffer.

The stages buffer has 2 buffers, the scene buffer and the stageCanvas. The stage canvas is reset (the stage canvas is the topBar, close button and the box below it) Then, each node from the scene is ordered to draw itself into the scene buffer.

Then, a loop iterates over the entire scene buffer and if a node is there, it will add it to the stageCanvas. Once completed the stageCanvas is returned to the program.

Finally the program reorders the table of buffers in relation to the z-index of the windows, it then loops each one and puts its buffer on the main programs buffer to be drawn.

I feel like more or less every step of this process is wrong but I have no idea why or how to fix it… This is my first time at using buffers and its turned out pretty well up until this point. The line numbers in the OP post will point you to places in the source where these steps occur (so you can see how I am drawing the nodes)
Grim Reaper #13
Posted 20 September 2015 - 08:27 AM
a loop iterates over the entire scene buffer and if a node is there, it will add it to the stageCanvas

What exactly is a node, and what does it mean to be "added to the stageCanvas?"

I think the problem's you're running into are likely a result of all of the iterating and merging that you're doing. Whether or not this is the wrong way to do it, I don't know. It's also likely that there is a better way to do it, but it's more important for those of us helping you to understand how your system is supposed to work first.
hbomb79 #14
Posted 20 September 2015 - 08:30 AM
I think the problem's you're running into are likely a result of all of the iterating and merging that you're doing. Whether or not this is the wrong way to do it, I don't know. It's also likely that there is a better way to do it, but it's more important for those of us helping you to understand how your system is supposed to work first.
I couldn't agree with you more.

A node is a button, label, text input etc…

When its added to the stageCanvas it literally inserts it, similar to this:


for y = 1, nodeHeight do
        for x = 1, nodeWith do
             tBuffer[y][x] = {
             textColor = "color",
             backgroundColor = "backgroundColor",
             char = " ",
             nDefined = true --# tells DynaCode this is not blank space.. render it!
             }  
        end
end
--# tBuffer is the stageCavas
Edited on 20 September 2015 - 06:31 AM
Exerro #15
Posted 20 September 2015 - 11:20 AM
Seeing as you mentioned Flare, I'll go into a bit of detail on how Flare's buffering/graphics works:

Every element (view, node etc) has its own 'Canvas', which is basically a buffer. When the element changes, it goes "hey, I need to redraw", and when it moves, it goes "hey parent, you need to redraw". This bubbles up to the top element (the application object) which goes, "huh, I need to redraw". This recursively draws each element by drawing its Canvas directly onto its parent's Canvas, redrawing the element (which updates the element's Canvas) first if it needs to (remember the "hey, I need to redraw"?). I really optimised Canvasses to draw to other Canvasses insanely quickly, so it's not really a problem drawing one Canvas to another, meaning it can do that every redraw.

This still doesn't draw anything to the screen, however, and that's the part that really causes lag. The ScreenCanvas class (the type of canvas the application object has) contains a method to draw to the screen by detecting what's changed and drawing over that only. When it redraws, it updates its storage of what's already been drawn so the next time it draws it knows what has changed.

Another thing to note is how pixels are stored in Flare. buffer.pixels[y][x] is a table like { [1] = bc, [2] = tc, [3] = char }. Let's say it takes 0.001 more seconds to index a table using a string key as opposed to an array index. Do this for all 969 pixels on the screen and you're taking nearly a whole second longer to draw. Rather than storing pixels as a 'table', store them as an 'array', and you'll get faster lookups. If you don't know what I'm talking about here, Lua tables are composed of two parts, an array part, and a hash part. When you do `t.x = 1`, it adds it to the hash part, and when you do `t[1] = 1`, it adds it to the array part. The array part is much quicker… much much quicker… so if you can, it's better to use an 'array' rather than a 'table' (and I write 'array' and 'table' with '' because they're essentially the same Lua-side but treated differently C/Java-side).

Edit: just remembered that Flare uses a one-dimensional array to store pixels, so it's buffer[y * width + x + 1], where y and x are 0-based (meaning it goes between 1 and width * height indexes).
Edited on 20 September 2015 - 09:32 AM
Bomb Bloke #16
Posted 20 September 2015 - 11:27 AM
Stop resetting your buffer. Draw your elements to it, then leave the things there until they change in some way. Draw stuff that changes. Don't redraw stuff which doesn't.

This concept doesn't really have anything to do with buffers - the crux of the matter is that if you're going to waste time reprocessing unchanged content every frame, then your performance is going to suffer accordingly. Currently you're attempting to "pave over" the problem by drawing every screen update to a buffer before sending the completed frame to the display, but while that eliminates flicker, it's not going to make the underlying fault go away. If speed's your priority then ditch that technique.

Let's take BBCards as an example - don't worry about reading the code, just look at the screenshots and consider the implications of a table covered in stacked cards, many partially covering others. Now say you've got a pile of cards. You click on them, then shift half the stack to another part of the screen. What gets redrawn?

Well, the API figures out what part of the screen the cards were covering, then it figures out what other cards encroach on that screen space and will be revealed by the move, and then it redraws those (if there were no elements in a given screen location, then it draws the background colour there). Then it draws the moved cards in their new position.

Yes, this involves quite a few checks and calculations - the code always has to keep track of what's where, and which order the cards are stacked in. It has to consider the width/height of the stacks in order to spot overlaps, and so on. But that's still a heck of a lot faster than redrawing everything every frame it has to draw.

What the code doesn't do is use a buffer of any sort. It doesn't need to - there's never a need to "generate" an eight of spades, for example, because an eight of spades always looks the same. But what if the cards were more unpredictable objects (eg labels with custom sizes/text/whatever), or animations?

That's when a buffering system would start to offer a speed benefit. If you want to move an element that contains a frame from midway through The Terminator, you don't re-run the code that loaded the video, seeked to the frame, and drew it; you rig things so that the image was rendered to a buffer the very first time it was drawn. Then if you need to move it, you move the buffer - an operation completely unrelated to the code which drew to it. You still have to figure out what's in the old screen-space and draw the relevant elements there (be they background or buttons or labels or whatever), but the rest of the screen you leave as-is.

That is to say, if an element is complex and needs to be moved around the screen, then sure, buffer that element. You'll then be able to redraw it quickly if you have a good reason to.
hbomb79 #17
Posted 21 September 2015 - 06:20 AM
Right now, i have a function that loops the windows and draws each one into the buffer regardless of whether or not they're visible.

How are you calculating that for each card so quickly? If i had a function that detects whether or not the window is covered by something I would loop each window, then each pixel of the window and if it crosses return true… wayyy to laggy.

I feel like maybe this is where I am failing. If you want to see the function I am talking about please look here: https://github.com/hbomb79/DynaCode/blob/master/DynaCode#L621

If I can tell DynaCode to only draw the window to the buffer if its not overlapped by any other windows in an efficient way I am sure it will increase DynaCodes speed. However I don't know how to do so in a good way (as mention above). Is there a place in your source I should look at to better understand it?
Bomb Bloke #18
Posted 21 September 2015 - 10:54 AM
How are you calculating that for each card so quickly? If i had a function that detects whether or not the window is covered by something I would loop each window, then each pixel of the window and if it crosses return true… wayyy to laggy.

So cache the results, make a 51x19 array or something which can be used to quickly determine the ID of whatever window is visible at any given point on the display. You only need to do those sort of calculations when the windows move in some way - and even then it's pretty easy.

I feel like maybe this is where I am failing. If you want to see the function I am talking about please look here: https://github.com/hbomb79/DynaCode/blob/master/DynaCode#L621

When I said "ditch that technique", I seriously meant to entirely wipe out functions like that one. Give each window a buffer. If a window wants to render something, the data gets stored in the buffer, and if the data SHOULD be visible to the user at that time, then it gets rendered at that time. If a window later moves, then you use the buffered content to redraw what's needed.
hbomb79 #19
Posted 21 September 2015 - 11:09 AM
and even then it's pretty easy.
Maybe for you, but this is difficult for me and I am trying my best to figure it out. I have made solid progress recently and the speed of DynaCode has drastically increased. Although there are still many speed gains to be had.

Each window currently has a buffer. Although the part I am struggling with is detecting if the window is visible because I would have thought that running a function for every pixel would cause much more lag because wouldn't each function have to loop the windows to determine if that window is there or not?

Almost all of what you have said I have done (probably not well, but its there) with the exception of: "which can be used to quickly determine the ID of whatever window is visible at any given point on the display." I see what you want me to do, just not how. If you have example code (yours, someone elses, mock up) that would be great, just so I can see how exactly I would determine the visibility of the window.

- Thanks
Bomb Bloke #20
Posted 21 September 2015 - 01:50 PM
Let's say you got a table filled with windows, called "windows". Index 1 is the one at the bottom, 2 is above that, 3 is above that, yadda yadda until index #windows, which is the one on top - no windows may render on top of index #windows.

Each window has an x/y position on the screen, a height, and a width. Let's assume we can get at these via windows[x].x, windows[x].y, windows[x].width and windows[x].height. We'll also put in a "visible" attribute, which we can toggle if we want to eg minimise a window or otherwise stop rendering it, and we'll give each window a "redraw" function we can call if we want to re-render what's in its buffer. Each window will also need a way to know which index it's currently at within the "windows" table; so update window[x].index whenever you move them up/down.

Let's go with a basic cache to keep track of which screen positions have windows covering them. We'll use a table called "layers", such that layers[y][x] will contain the index of the window that's visible at any given x/y co-ord. If no window covers that screen location, then we'll just set the index to 0.

You'd build a function that can be passed a top-left co-ord and a lower right co-ord, able to update "layers" with the window data of the rectangle formed between the two points. A basic version might look like this:

local updateRegion(x1, y1, x2, y2)
	for curLayer = #windows, 1, -1 do  -- Loop from the top window down.
		local thisLayer = windows[curLayer]
		local winX1, winY1, winX2, winY2 = thisLayer.x, thisLayer.y, thisLayer.x + thisLayer.width - 1, thisLayer.y + thisLayer.height - 1
		
		if not (winX1 > x2 or winY1 > y2 or x1 > winX2 or y1 > winY2) then  -- If the window overlaps the area we're checking, then...
			-- Loop through the overlapping area:
			for y = math.max(winY1, y1), math.min(winY2, y2) do
				for x = math.max(winX1, x1), math.min(winX2, x2) do
					if layers[y][x] < curLayer and thisLayer.visible then
						-- This is the topmost layer visible at this point, stick the index into the map.
						layers[y][x] = curLayer
					elseif layers[y][x] == curLayer and not thisLayer.visible then
						-- This is the topmost layer at this point, but it's hidden, so set to background for now.
						layers[y][x] = 0
					else
				end
			end
			
			if thisLayer.visible then thisLayer.redraw() end  -- Layer map isn't fully updated yet, but it's accurate for THIS layer, so draw the buffered content.
		end
	end
	
	for y = y1, y2 do for x = x1, x2 do
		if layers[y][x] == 0 then
			-- Draw background colour at that point.
		end
	end end
end

Easy! Granted, it could be made more efficient, but for now I'm just trying to get the point across. Some simple loops performing some simple checks. It doesn't really matter because this code is only getting called when windows move around in some way (as opposed to when window content gets updated in some way).

A window's redraw function might go along these lines:

local function newWindow(...)
	local theWindow = {}
	
	...
	
	theWindow.redraw() = function()
		if not theWindow.visible then return end
		
		for y = 1, theWindow.height do for x = 1, theWindow.width do
			if layers[y + theWindow.y - 1][x + theWindow.x - 1] == theWindow.index then
				-- draw theWindow.buffer[y][x] to the screen location.
			end
		end end
	end
	
	...
	
	return theWindow
end

So let's say you wanted to move a window from one side of the screen to the other. You'd hide it, update the layer map, change its co-ords, unhide it, and update the map again:

windows[i].visible = false
updateRegion(windows[i].x, windows[i].y, windows[i].x + windows[i].width - 1, windows[i].y + windows[i].height - 1)
windows[i].x, windows[i].y = newX, newY
windows[i].visible = true
updateRegion(windows[i].x, windows[i].y, windows[i].x + windows[i].width - 1, windows[i].y + windows[i].height - 1)

Yes, this sort of code may take enough time to execute that you notice a very slight flicker when running it. But when you're not hiding/moving/adding windows, you're not running any of the above! You're simply storing the new content into eg windows[someWindow].buffer[y][x], and immediately updating the screen with it (assuming layers[y][x] gives you permission to do so for each point).

There's tons more code you'd need to wrap around the psuedo-code above - for example, you'd need functions so that window content can render through a window's buffer, and you need ways to update the layer map if windows are moved up/down the pile (tip: it can be done in a very, very similar to the "moving" example above - make windows with new indexes invisible, update map, update indexes, make affected windows visible again, update map), windows are added, removed, yadda yadda yadda.

Basically, think about what code runs often, and what code doesn't. Then rig things so that "work intensive" functions get called rarely, and start piling as much "work" from your commonly-called functions into those rare ones as you can. You could, for example, implement a system that spots when a window has been entirely covered, and if so, sets a separate secondary "invisibility" flag for it which it can use to spot that there's no point in checking every index in layers[y][x] because they're all covered and hence every check will get the same answer. There are tons of options. The more thinking you do, the less thinking you can make your script do, on average.
Edited on 21 September 2015 - 11:52 AM
hbomb79 #21
Posted 22 September 2015 - 05:02 AM
Ah ha, I think I understand what you are saying.

So I basically need to "map" which window can draw where, and when drawing the window I first check if the windows pixel is allowed to be there, if it isn't then skip it.

So when would I call theWindow.redraw()? I understand that updateRegion() is only called when the windows move. I would have thought I should instruct the node to tell the stage to redraw, although the problem is what do I draw?

If the node is moving should I loop the last location to clear it, and then draw the node in its new location to the buffer. I feel like when I have this mapping implemented by next (and hopefully final for the time being) problem will be how to handle nodes animating and how I should go about drawing the nodes to the stages buffer.

Thanks for your help so far, much obliged


EDIT: Actually, I think I get what you are saying now. So if I am animating a node I hide it, update the region so its either background or the window below it and then move it, show it again and update layers again
Edited on 22 September 2015 - 03:06 AM
hbomb79 #22
Posted 22 September 2015 - 07:33 AM
How should I create the layers table? I have a loop creating it right now is this the best way to go about it:


local function formMap()
	for y = 1, 10 do
		local layerY = objects.layers[y]
		objects.layers[y] = {}
		for x = 1, 10 do
			objects.layers[y][x] = false
		end
	end
end

My problem right now is the question above and how to handle node redrawing. You said to only draw the canvas when needed. How do I do this if the node is animating etc…
Edited on 22 September 2015 - 05:35 AM
Bomb Bloke #23
Posted 22 September 2015 - 08:42 AM
How should I create the layers table? I have a loop creating it right now is this the best way to go about it:

Yeah sure just go however big your "display" is.

My problem right now is the question above and how to handle node redrawing. You said to only draw the canvas when needed. How do I do this if the node is animating etc…

Draw window content the moment it's updated, assuming the relevant window area isn't obscured. Only REdraw if the content was obscured and is later revealed.

If you want to applying a buffering system to the individual elements WITHIN your windows, then the same deal applies: Think the node's window as being the screen, and the nodes that belong to that window as being windows on that screen. Each node gets a buffer. Define a layer map for that window, refer to that when the nodes want to draw through their individual buffers. If a node wants to draw new content - eg the next frame of an animation - then draw that content immediately. Only redraw a node from within its buffer if another node that was obscuring it is no longer doing so.
hbomb79 #24
Posted 22 September 2015 - 08:46 PM
So, when should I redraw the window? Every time a change happens or when the window moves? How do I detect that content was, but no longer is obscured?


I am having so much trouble with this, I apologize
Edited on 22 September 2015 - 06:50 PM
Bomb Bloke #25
Posted 23 September 2015 - 01:03 AM
So, when should I redraw the window? Every time a change happens or when the window moves?

Well, given that changes within a window would be drawn to the screen immediately when they happen (subject to the rule that they must never draw over a window on top of it), you'd indeed only ever need to redraw a window if the positioning of the windows themselves change in some way.

How do I detect that content was, but no longer is obscured?

Re-read this post. The updateRegion() function mockup, in particular, would not only update the layer map according to any changes, but it'd also trigger redraws of any windows that become unobscured by them.

In any situation where a window is being removed from an area of the screen - say because you're closing it, making it smaller, moving it down the order of layers, or preparing to move it somewhere else - you make the window invisible and pass its screenbounds to the aforementioned function.

In any situation where a window is being added to an area of the screen - say because you just created it, or are finishing off one of the above operations - you ensure the window is flagged as visible and again pass its screenbounds to something like updateRegion().

Heck, I even gave you a code snippet demonstrating how you might move a window with it.
hbomb79 #26
Posted 23 September 2015 - 06:32 AM
Heck, I even gave you a code snippet demonstrating how you might move a window with it.

Just because you gave it to me doesn't mean I understand it. I am trying my best and I am struggling to understand, I appreciatte your help to a great deal, but this is the first time I have really dug into buffers… at all. I understand it may be frustrating trying to help someone as dim-witted as I am. However I have implemented the pixel mapping and it works much faster now.

My next task is the nodes buffer. Currently the nodes draw straight to the stages canvas and then they get drawn to the screen using the windows redraw function. I am slowly getting my head around how exactly I would do this.

Right now my understanding is that when the node is updated, draw it straight to the buffer. However the problem then is that when animating a node the trail is left behind.
If I were to animate a node from X: 1, to X: 5, it would move the node one pixel every tick, and the node will be drawn in all 5 places. There will literally be 5 of the nodes on the screen but only one of them will actually be there.

I thought maybe the way around it would be to make a map inside the window, and when the node wants to move I do the same procedure I would when moving a stage. That way when a node changes I can update the buffer, but the other nodes won't be redrawn to the buffer.

I thought this might be the way to go, but then the problem of the windows titleBar, close button etc arises and how exactly I would manage their draws too.

Is their any chance of you giving me a word (flow chart) kinda thing. As in event caught -> node updates -> then you do that -> then this and that. Then I can begin creating such steps and hope I can do it by myself this time.

I need to understand how to handle the stages window top bar, close button, title text etc and the nodes within them, because right now I can imagine how I would get the nodes to draw into the buffer, but I am unsure on how the buffers inside the window should be layed out. Maybe a buffer for the nodes and one for the window itself, and then when the stage is drawn I can check if theres a node in the node buffer, if so move that accross oftherwise use the stages buffer:


if nodeBuffer[y][x] then
   programBuffer[y][x] = nodeBuffer[y][x]
else
   programBuffer[y][x] = stageCanvas[y][x]
end

- Thanks very much.
Edited on 23 September 2015 - 04:34 AM
hbomb79 #27
Posted 24 September 2015 - 05:02 AM
Would it be better to have each nodes canvas available and then just pass that instead of the stages background when drawing (func window.redraw)? I feel like this will prevent the need to keep redrawing the stages titleBar
Bomb Bloke #28
Posted 24 September 2015 - 11:30 AM
I am trying my best and I am struggling to understand, I appreciatte your help to a great deal, but this is the first time I have really dug into buffers… at all.

I suppose I might as well try to sum up a couple of key points: If something's slow or complicated to generate, then you can save the result to a buffer, from where it can be pulled nearly instantly later. This has two potential benefits when dealing with a display buffer:

One, you can potentially avoid flicker. If updating the screen takes a long time, then you can leave the old frame on the screen while drawing to a buffer, then simply draw what's in the buffer the moment it's ready. This obviously offers no speed increase overall (in fact it's slightly slower than using no buffers at all), but it does reduce the time the screen spends in a blank state, and eliminates the chance of the user seeing a frame in "mid-draw". This is the technique you started out using.

Two, you can avoid generating the same content more than once. If you potentially need to draw the same object to the screen more than once, but the code that usually draws it is slow, then you stick the generated image into a buffer and use that every time you need to put it on the screen again. Since it potentially reduces the number of times you need to run your "slow" code, this can potentially give a great speed boost.

Right now my understanding is that when the node is updated, draw it straight to the buffer.

Sure, keeping in mind that each node needs its own buffer, one that's rigged to draw to the buffer of the window the node belongs to.

However the problem then is that when animating a node the trail is left behind.
If I were to animate a node from X: 1, to X: 5, it would move the node one pixel every tick, and the node will be drawn in all 5 places. There will literally be 5 of the nodes on the screen but only one of them will actually be there.

For which you use the exact same technique as in that updateRegion() function. Done well, you could use the exact same function to deal with nodes moving around within windows, as you use to deal with windows moving around within the screen.

I thought maybe the way around it would be to make a map inside the window, and when the node wants to move I do the same procedure I would when moving a stage. That way when a node changes I can update the buffer, but the other nodes won't be redrawn to the buffer.

Assuming by "stage" you mean "the window the node is inside", yep.

I thought this might be the way to go, but then the problem of the windows titleBar, close button etc arises and how exactly I would manage their draws too.

Well, I'd imagine nodes can't draw over their window's titlebars, so the only time you'd need to draw/redraw those would be if the windows themselves are altered in some way (as opposed to their contents).

Is their any chance of you giving me a word (flow chart) kinda thing. As in event caught -> node updates -> then you do that -> then this and that. Then I can begin creating such steps and hope I can do it by myself this time.

Well, let's say a mouse_click event occurs.

First I'd check to see which window the click occurred within. Assuming the windows are all in a table with the highest index pointing to the window that's in front, this'd just be a case of iterating down through them until you find one the click collides with.

I'd then subtract the co-ords of the window from the co-ords of the mouse click, and pass the modified event data to an event managing function tied to that window (likely stored within the window's table). This'd proceed to check another table containing all the nodes that're defined as belonging to that window (all the co-ords of which would be defined as relative to that window's own position); again iterating down through the list until a collision is found.

I'd then know exactly what node was clicked on, and could call whatever function was tied to that node to deal with it being clicked on.

Drawing is this process in reverse, in case it isn't clear: Node draws to window buffer (using co-ords that're relative to the window's position), window buffer draws to screen. If nodes move around (eg nodes are added/removed/repositioned), window uses node buffers to quickly redraw whatever content it needs to. If windows move around, screen manager uses window buffers to quickly redraw whatever content it needs to.
Edited on 24 September 2015 - 09:31 AM
hbomb79 #29
Posted 24 September 2015 - 08:58 PM
My idea currently is that each node will be drawn to a nodeBuffer inside the stage. Then in the windows redraw function, instead of drawing the window I will draw the node that is there. Of course when a node draws it will abide to a layers map.
hbomb79 #30
Posted 26 September 2015 - 09:35 AM
Thank you for all your help, I know I lack knowledge in buffering, however you have shed light upon a subject that I was really interested in. I am currently putting it into practise and there is already a huge speed increase while typing. However my next goal is speeding up animations. (maybe they should use a separate event loop in parallel)

Anyway, Thank you.