I noticed that the term api has a few incomprehensibly slow functions like setTextColor and setBackgroundColor. If I call these functions everytime before drawing a character on the screen then the display is going to be flickering. I think of something like this:
for y = startY, endY do
for x = startX, endX do
local bc = getBc(x, y)
local tc = getTc(x, y)
local char = getChar(x, y)
term.setCursorPos(x, y)
term.setBackgroundColor(bc)
term.setTextColor(tc)
tem.write(char)
end
end
Number of calls with a 51 * 19 characters large display:
setBackgroundColor : 969 (every case)
setTextColor : 969 (every case)
write : 969 (every case)
setCursorPos : 969 (every case)
Erhmm… that's a lot of function calls per screen draw. Accurately 3876. With 20 FPS, that's 77520 (!!)
We should optimize our application somehow. Maybe with fewer function calls, it would be faster, but how can we achieve such a thing? Perhaps it is unnecessary to draw every pixel. Then that means our screen would have "blank pixels". That wouldn't be good. Oh, not. If the renderer draws a pixel at x, y and at the next draw x,y is the same color, same character then it wouldn't be blank. Wait… It lets us be able to draw only the updated pixels, because the pixel drawn in the previous cycle will be perfect for us. One thing is left. How can the computer remember the previously rendered pixels? Right, we have to store them in a buffer. A pretty simple implementation will do the job now, so we can test our new code:
local buffer = Buffer(51, 19)
-- And this goes inside our update loop.
for y = startY, endY do
for x = startX, endX do
local mustRender = false
-- get colors and characters what should be displayed
local bc = getBc(x, y)
local tc = getTc(x, y)
local char = getChar(x, y)
-- are the new pixel datas the same as the old? chexk it with simple conditions
if buffer:getCharAt(x, y) ~= char then
mustRender = true
end
if not mustRender and buffer:textColorAt(x, y) ~= tc then
mustRender = true
end
if not mustRender and buffer:backgroundColor(x, y) ~= bc then
mustRender = true
end
-- it is true if a pixel wasn't the same in the last cycle
if mustRender then
-- save the new pixel datas, otherwise next time the pixel checking algorithm (above) will return true, because we forgot to tell the buffer that we drew the new pixel
buffer:updatePixelDatas(x, y, char, tc, bc)
term.setCursorPos(x, y)
term.setBackgroundColor(bc)
term.setTextColor(tc)
tem.write(char)
end
end
end
Huh, this one is a bit more complex, but is it more effective? Well, for the first drawing, it is not faster than the previous implementation, but after keeping a list from the drawn pixels, it won't draw every single pixel every time we draw a tiny black spot onto the monitor. So maybe the number of function calls will be reduced from 3876 to 0. What if we want animations? Is is going to be enough? If you try to move a 4 * 4 characters large cyan rectangle on the screen with a white screen (so everything is white, except the rectangle), then moving it by 1 X in every update will make the program to draw only 8 pixels. (When I say pixels, I refer to CC pixels). However if you move 5 shapes with different colors, it will give you a horrible experience.
Now we got to the point. It doesn't move fast, but why? What makes it so slow if we only draw a few pixels? My answer : setting the text colors and background colors (mostly)
Now everyone knows this, so it will be very easy to implement something what uses less color adjusting.
The solution is the following: Sort the pixels by background colors. The best effect will be achieved by sorting the pixels by background colors. To make it work, we will have to draw in 4 steps.
- Checking for updated pixels
- Sorting updated pixels by background colors
- Drawing the pixels
- Updating buffer
local buffer = Buffer(51, 19) -- buffer for the renderer
local sortedBuffer = {} - A table will be good for me now.
-- sort function
function sortBuffer()
table.sort(sortedBuffer, function(p1 , p2)
return buffer:getBc(p[1], p[2]) > buffer:getBc(p2[1], p2[2])
end
end
-- draw function
function drawSortedBufferContent()
local lastBc = nil
local lastTc = nil
for k, v in pairs(sortedBuffer) do
if lastBc ~= buffer:getBc(v[1], v[2]) then
lastBc = buffer:getBc(v[1], v[2])
term.setBackgroundColor(lastBc)
end
if lastTc ~= buffer:getTc(v[1], v[2]) then
lastTc = buffer:getTc(v[1], v[2])
-- Only has to set text color if the character is a visible letter or symbol, otherwise it will be unwanted and you will ruin performance
if buffer:getCharAt(v[1], v[2]) ~= " " then
term.setTextColor(lastTc)
end
end
term.setCursorPos(v[1], v[2])
term.write(buffer:getCharAt(v[1], v[2])
end
end
-- the draw behavior inside our app logic loop
for y = startY, endY do
for x = startX, endX do
local mustRender = false
-- get colors and characters what should be displayed
local bc = getBc(x, y)
local tc = getTc(x, y)
local char = getChar(x, y)
-- are the new pixel datas the same as the old? check it with simple conditions
if buffer:charAt(x, y) ~= char then
mustRender = true
end
if not mustRender and buffer:textColorAt(x, y) ~= tc then
mustRender = true
end
if not mustRender and buffer:backgroundColor(x, y) ~= bc then
mustRender = true
end
-- it is true if a pixel wasn't the same in the last cycle
if mustRender then
buffer:updatePixelDatas(x, y, char, tc, bc)
table.insert(sortedBuffer, {
x,
y
})
end
end
end
sortBuffer() -- sort after checking pixels
drawSortedBufferContent() -- draw after sorting for better performance
Because the background colors are sorted the program will only set the background color when the next pixel's bc is not the same than the last one's color and this can only happen if there is no more from the specific color. This method reduces the maximum amount of setBackgroundColor to 16 (!!) and this value is resolution dependent (constant), while with the first approach, the maximum number of setBackgroundColor calls was width * height (969 by default for advanved computers). You might noticed that there is an other clever thing for setTextColor. If the drawable character is a whitespace, then the app won't set the text color, because it won't be visible.
Final result?
Number of:
Maximum setBackgroundColor calls: 16
textColor calls : 969, but can be 0, if you don't show characters just whitespaces
setCursorPos calls: 969
write calls: 969
In the best case: About 1900 calls per drawing (whole screen), but usually less, because the programs don't change every single pixel, so with caching it is way more efficient. It is better than the 3876 (constant) we used for the first time what is really similar to the one, beginner programmers create. I hope you've learnt something. At least that you shouldn't listen to me. :D/>
Sorry for all of my mistakes. It is 00:00 here. :D/>
And last, but not least: please leave some techniques here. I'm sure that everyone would learn a lot. :)/>