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

[Buffer] Blazing Fast!

Started by Symmetryc, 16 December 2013 - 08:49 PM
Symmetryc #1
Posted 16 December 2013 - 09:49 PM
Hey guys! After talking with Lyqyd on the LyqydOS thread about buffering and buffering techniques, I found myself in awe of how inefficient my past techniques have been, so I set out to devise a much more powerful buffer, and I believe I have achieved my goal to some extent in that this new buffer is not only fast while being written to, but is blazing fast when being drawn.

Features:
- Everything is swept together into one fluid loop of optimized instructions
- Removes unnecessary color changing
- Removes unnecessary cursor position changing
- Appends writes ex: write("5") write("6") becomes write("56")

Usage:

local x, y = term.getSize()
local buffer = dofile("buffer")
local test = buffer.buffer()
test.pos_x, test.pos_y = 2, 14 -- setting the cursor pos
test.back = colors.blue -- setting back color
test.text = colors.red -- setting text color
test:write("Hello!") -- writing
test.back = colors.lightGray -- changing back color
test:write("Different Color!") -- writing with new color
   :write("Chaining =D") -- writing (using chaining)
   :draw() -- drawing (using chaining)

Code: https://github.com/Symmetryc/Buffer

If you have any suggestions, feel free to put in a pull request :)/>

Enjoy :)/>!
Edited on 27 December 2013 - 02:18 PM
theoriginalbit #2
Posted 16 December 2013 - 09:54 PM
Finally! someone using better design patterns (referring to chaining ability)! :)/>
lieudusty #3
Posted 16 December 2013 - 10:00 PM
May be a dumb question but whats the use of this? Or is there something I'm not seeing?
Edited on 16 December 2013 - 09:00 PM
theoriginalbit #4
Posted 16 December 2013 - 10:02 PM
May be a dumb question but whats the use of this? Or is there something I'm not seeing?
What do you think a buffer would be?………
Symmetryc #5
Posted 16 December 2013 - 10:03 PM
May be a dumb question but whats the use of this? Or is there something I'm not seeing?
It prevents screen flickering; normally you would set the color, write, set the cursor position, write, etc. but with this not only is all of that swept into one fluid function to stop flickering, it also detects unnecessary color changes, pos changes, and appends writes together if both of those circumstances are met Ex: write("5") write("6") becomes write("56")
Edited on 16 December 2013 - 09:03 PM
lieudusty #6
Posted 16 December 2013 - 10:10 PM
May be a dumb question but whats the use of this? Or is there something I'm not seeing?
It prevents screen flickering; normally you would set the color, write, set the cursor position, write, etc. but with this not only is all of that swept into one fluid function to stop flickering, it also detects unnecessary color changes, pos changes, and appends writes together if both of those circumstances are met Ex: write("5") write("6") becomes write("56")
Oh I see now :)/>
AgentE382 #7
Posted 18 December 2013 - 10:24 PM
Symmetryc, let me say that this is a cool idea. I haven't seen anyone do buffering like this yet.

Also, I'm almost finished altering this to allow for redirecting. I haven't done anything except rearrange the code and add methods for each of the term API functions. It's on my Pastebin, but I won't post it here until I finish implementing the only methods which require testing.
Symmetryc #8
Posted 18 December 2013 - 10:31 PM
Symmetryc, let me say that this is a cool idea. I haven't seen anyone do buffering like this yet.

Also, I'm almost finished altering this to allow for redirecting. I haven't done anything except rearrange the code and add methods for each of the term API functions. It's on my Pastebin, but I won't post it here until I finish implementing the only methods which require testing.
Thanks! If you'd like to join forces, I'm all for it :)/>.
Edited on 18 December 2013 - 09:31 PM
theoriginalbit #9
Posted 18 December 2013 - 10:33 PM
add methods for each of the term API functions.
really you wouldn't need to add for them all, only ones that need to access or make use of the buffer, 'cause if you assign the __index to term.native it will use those for anything you don't define, for example term.getSize which doesn't need to be overrode in the buffer. An example of this can be seen in a few of my programs where I make a terminal object to quickly and easily add normal/advanced computer support.

--# create a terminal object with a non-advanced computer safe version of setting colors
local termObj = {
  setTextColor = function(n) pcall(term.native.setTextColor , n) end,
  setBackgroundColor = function(n) pcall(term.native.setBackgroundColor , n) end
}
--# also override the English spelling of the colour functions
termObj.setTextColour = termObj.setTextColor
termObj.setBackgroundColour = termObj.setBackgroundColor

--# make the terminal object refer to the native terminal for every other function
setmetatable(termObj, {__index = term.native})

--# redirect the terminal to the new object
term.redirect(termObj)
Edited on 18 December 2013 - 09:33 PM
AgentE382 #10
Posted 18 December 2013 - 11:16 PM
That would probably be a good thing to do. But, most of the functions need to be overriden, if only to fit with the design pattern that allows chaining.

Anyway, here's the paste: http://pastebin.com/3E832x86

I haven't been able to test it at all because I'm having issues with Java on my Linux box. So, if there are any errors, please fix them ;-)

Note that everything is still used exactly the same way as you had it earlier. But, you can also use the standard methods now. Also note that the methods follow the "." convention instead of the ":" convention, so they'll work for redirect.

I did my best to follow your design pattern and coding style when editing the code you wrote. Writing some of the methods from scratch, I did my best to follow the design pattern, but used my own coding style.

Spoiler
-- Buffer API, By Symmetryc
-- Edited by AgentE382 to change ":" OO-syntax to "." OO-syntax. (First step to redirect target based on this.)
-- Edited by AgentE382 to add preliminary redirect-target capability.
-- Edited by AgentE382 to add full redirect-target functionality.
return {
    new = function()
        local self = {
            act = {pos = {}};
            pos = {};
            back = colors.white;
            text = colors.lightGray;
            blink = true
        }

        function self.write(_str)
            local act = self.act
            local selfpos = self.pos
            local append = true
            if selfpos[1] ~= act.pos[1] or selfpos[2] ~= act.pos[2] then
                act[#act + 1] = {term.setCursorPos, selfpos[1], selfpos[2]}
                append = false
            end
            if self.back ~= act.back then
                act[#act + 1] = {term.setBackgroundColor, self.back}
                act.back = self.back
                append = false
            end
            if self.text ~= act.text then
                act[#act + 1] = {term.setTextColor, self.text}
                act.text = self.text
                append = false
            end
            if self.blink ~= act.blink then
                act[#act + 1] = {term.setCursorBlink, self.blink}
                act.blink = self.blink
                append = false
            end
            for line, nl in _str:gmatch("([^\n]*)(\n?)") do
                if append then
                    act[#act][2] = act[#act][2]..line
                    append = false
                else
                    act[#act + 1] = {term.write, line}
                end
                selfpos[1] = selfpos[1] + #line
                if nl == "\n" then
                    selfpos[1] = 1
                    selfpos[2] = selfpos[2] + 1
                    act[#act + 1] = {term.setCursorPos, 1, selfpos[2]}
                end
            end
            act.pos = {selfpos[1], selfpos[2]}
            return self
        end;
        function self.draw(self)
            for i, v in ipairs(self.act) do
                if v[3] then
                    v[1](v[2], v[3])
                else
                    v[1](v[2])
                end
            end
            self.act = {}
            return self
        end;

        function self.clear()
            local a = self.act
            for i = #a, 1 do
                a[i] = nil
            end
            return self
        end

        function self.clearLine()
            local a = self.act
            if a[#a][1] ~= term.clearLine then
                a[#a + 1] = {term.clearLine}
            end
            return self
        end

        function self.getCursorPos()
            local p = self.pos
            return p[1], p[2]
        end

        function self.setCursorPos(x, y)
            local p = self.pos
            p[1], p[2] = x, y
            return self
        end

        function self.setCursorBlink(state)
            self.blink = state
            return self
        end

        self.isColor = term.isColor

        self.getSize = term.getSize

        function self.redirect(target)
            term.redirect(target)
            return self
        end

        function self.restore()
            term.restore()
            return self
        end

        function self.scroll(n)
            local a = self.act
            if a[#a][1] ~= term.scroll then
                a[#a + 1] = {term.scroll, n}
            else
                local s = a[#a]
                s[2] = s[2] + n
            end
            return self
        end

        function self.setTextColor(color)
            self.text = color
            return self
        end

        function self.setBackgroundColor(color)
            self.back = color
            return self
        end

        return self
    end;
}
Edited on 18 December 2013 - 10:24 PM
theoriginalbit #11
Posted 18 December 2013 - 11:33 PM
quite honestly Symmetryc, I'm surprised that you didn't do this http://pastebin.com/sFP0zGLB :P/>
Edited on 18 December 2013 - 10:34 PM
AgentE382 #12
Posted 19 December 2013 - 12:20 AM
Taking a step back from my code, I realized that it won't work if you redirect the term API to this buffer, then try to use the term API to draw the buffer…

I added a redirect system, but realized that the way the code was written, it queued up the functions after each method call. If the buffer was redirected to a new target, it still had the old functions in the buffer, so it would still draw on the old screen as well. This could be seen as a bug, or a feature, but I saw it as a bug.

So, I changed the code to defer function lookup until draw-time. Otherwise, it wouldn't be possible to keep the buffer contents if it is switched from drawing on one monitor to another.

Check this out and see if it works / you like it: http://pastebin.com/jH9wVQuz

If you think it's more of a feature instead of a bug, I can switch it back. EDIT: In fact, here it is: http://pastebin.com/Mw17fH72
Edited on 18 December 2013 - 11:28 PM
Symmetryc #13
Posted 19 December 2013 - 03:18 PM
quite honestly Symmetryc, I'm surprised that you didn't do this http://pastebin.com/sFP0zGLB :P/>
Lol, I probably would've but this buffer is for efficiency and my index lookup thing will make it way slower :P/>.
Symmetryc #14
Posted 19 December 2013 - 03:32 PM
Taking a step back from my code, I realized that it won't work if you redirect the term API to this buffer, then try to use the term API to draw the buffer…

I added a redirect system, but realized that the way the code was written, it queued up the functions after each method call. If the buffer was redirected to a new target, it still had the old functions in the buffer, so it would still draw on the old screen as well. This could be seen as a bug, or a feature, but I saw it as a bug.

So, I changed the code to defer function lookup until draw-time. Otherwise, it wouldn't be possible to keep the buffer contents if it is switched from drawing on one monitor to another.

Check this out and see if it works / you like it: http://pastebin.com/jH9wVQuz

If you think it's more of a feature instead of a bug, I can switch it back. EDIT: In fact, here it is: http://pastebin.com/Mw17fH72
Wow, nice work! Couple of concerns though:
1. Your blinking code doesn't make sense at all… not only does blinking have no effect on appending, but also, the fact that you're changing the blink in the middle of drawing which the user will never see seems pointless…
2. object.clear() is clearing everything, including color changes, blink changes, pos changes, etc. Btw not only does the for loop's increment need to be set to one, but also the fact that you're looping backwards and not forward puzzles me.
3. object.setCursorPos could just do self.pos = {x, y} (Although I'm not sure if it's slower, I'd assume not)
4. For clearLine and scroll, use conditions over if statements, its more efficient iirc.
TheOddByte #15
Posted 19 December 2013 - 04:24 PM
Wow amazing! Can't believe you did this in so few lines, Great work! :D/>
AgentE382 #16
Posted 19 December 2013 - 06:08 PM
Taking a step back from my code, I realized that it won't work if you redirect the term API to this buffer, then try to use the term API to draw the buffer…

I added a redirect system, but realized that the way the code was written, it queued up the functions after each method call. If the buffer was redirected to a new target, it still had the old functions in the buffer, so it would still draw on the old screen as well. This could be seen as a bug, or a feature, but I saw it as a bug.

So, I changed the code to defer function lookup until draw-time. Otherwise, it wouldn't be possible to keep the buffer contents if it is switched from drawing on one monitor to another.

Check this out and see if it works / you like it: http://pastebin.com/jH9wVQuz

If you think it's more of a feature instead of a bug, I can switch it back. EDIT: In fact, here it is: http://pastebin.com/Mw17fH72
Wow, nice work! Couple of concerns though:
1. Your blinking code doesn't make sense at all… not only does blinking have no effect on appending, but also, the fact that you're changing the blink in the middle of drawing which the user will never see seems pointless…
2. object.clear() is clearing everything, including color changes, blink changes, pos changes, etc. Btw not only does the for loop's increment need to be set to one, but also the fact that you're looping backwards and not forward puzzles me.
3. object.setCursorPos could just do self.pos = {x, y} (Although I'm not sure if it's slower, I'd assume not)
4. For clearLine and scroll, use conditions over if statements, its more efficient iirc.
Thanks!

1. Yeah, I wasn't sure how that should be handled.
2. That was by design. I figured that the next buffer.write("xyz") call could use the position, color, background color, and blink information stored in the self table, but I forgot to clear those from the act table to trigger it. I just fixed the paste. Sorry about the for loop's increment. It should have been "-1". I updated the paste. I did loop backward because the "#" operator would get messed up if for some reason the clear was interrupted while clearing forwards.
3. It is slower. "self.pos = {x, y}" creates a new table, then stores that table in self.pos, while self.pos[1], self.pos[2] = x, y reuses the already existing table, just swapping out the values.
4. I have no idea what you mean.

Thanks for the feedback. What do you want to do with the blinking?
Edited on 19 December 2013 - 05:09 PM
Symmetryc #17
Posted 19 December 2013 - 06:20 PM
4. I mean instead of "if x then y = z end" do "y = x and z"

About blinking, you could just edit draw so that at the end of the draw it would do term.setCursorBlink(self.act.blink). Also, having to check out's index is rather time consuming (in the grand scheme of things, of course) during draw time, and the problem is is that there aren't many ways around that. To be honest, I don't see much useful application of creating a term.redirect object now, but it's your decision.
Edited on 19 December 2013 - 05:25 PM
AgentE382 #18
Posted 19 December 2013 - 11:06 PM
I got an idea. Give me until tomorrow to code it, because I'm going to sleep now. I was thinking about how to keep it the way you originally designed it while allowing redirects to work, and I think I figured it out.

Yeah, that makes sense for the blinking, because the user never sees it until the drawing is done.

4. It's more compact, but not actually more efficient. Both ways yield (almost) the same bytecode when compiled.

EDIT: I just made a long post about the bytecode listings, then I realized that the equivalent to y = x and z is if x then y = z else y = x end, so I tested the wrong thing. Anyway, you're right. The assignment version takes 3 less instructions than the if version when using globals, 2 less when using locals.
Edited on 19 December 2013 - 10:49 PM
Symmetryc #19
Posted 20 December 2013 - 02:16 PM
But the thing is, even if you get it to work with the same "jazz" as the original, what's the point? It's not like if you load it into a defualt program it will make it any better, because the default program won't call term.draw()…
Edited on 20 December 2013 - 01:16 PM
theoriginalbit #20
Posted 20 December 2013 - 08:58 PM
But the thing is, even if you get it to work with the same "jazz" as the original, what's the point? It's not like if you load it into a defualt program it will make it any better, because the default program won't call term.draw()…
Override term.redirect and if you detect that they're redirecting to one of your objects setup a coroutine that invokes the draw every few milliseconds, since it only renders the changes it shouldn't be a problem to do it this way.
Symmetryc #21
Posted 20 December 2013 - 10:42 PM
But the thing is, even if you get it to work with the same "jazz" as the original, what's the point? It's not like if you load it into a defualt program it will make it any better, because the default program won't call term.draw()…
Override term.redirect and if you detect that they're redirecting to one of your objects setup a coroutine that invokes the draw every few milliseconds, since it only renders the changes it shouldn't be a problem to do it this way.
Correct me if I'm wrong, but running that coroutine and calling that function over and over again will just be the same as if they simply used the term.native object…
Zambonie #22
Posted 21 December 2013 - 09:08 AM

test.text = colors.blue -- setting back color
test.back = colors.red -- setting text color


It seems like you mixed them up. test.text = colors.blue –setting back color. Should be text
Symmetryc #23
Posted 21 December 2013 - 09:12 AM

test.text = colors.blue -- setting back color
test.back = colors.red -- setting text color


It seems like you mixed them up. test.text = colors.blue –setting back color. Should be text
Ah, good catch, thanks!
Symmetryc #24
Posted 21 December 2013 - 09:22 PM
Alright, I'm pushing some updates:

- Blink support with object.blink
- Changed object.pos[1]/object.pos[2] to object.pos_x/object.pos_y for efficiency (no tables)
- Added object.shift_x/object.shift_y which can be used to shift the object
- No longer resets object.act after drawing
Edit:
- newline characters no longer go down one and all the way to the left, but rather go down one and go back to the x pos where the write started, so for example:


-- With this code:
object.pos_x = 3
object.pos_y = 1
object:write("Hello\nHow\nAre\nYou?")
      :draw()

-- Old write:
   Hello
How
Are
You?

-- New write:
   Hello
   How
   Are
   You?
Edited on 21 December 2013 - 10:12 PM
theoriginalbit #25
Posted 21 December 2013 - 11:44 PM
- newline characters no longer go down one and all the way to the left, but rather go down one and go back to the x pos where the write started
I don't like it, make it a different function call, 'cause what happens if I do want it to go to the left!
Symmetryc #26
Posted 22 December 2013 - 09:57 AM
- newline characters no longer go down one and all the way to the left, but rather go down one and go back to the x pos where the write started
'cause what happens if I do want it to go to the left!

obj.pos_x = 5
obj:write("something\n")
obj.pos_x = 1
obj:write("anotherthing\nthat\nis\non\nthe\nleft!")
:D/>
Edited on 22 December 2013 - 08:57 AM
theoriginalbit #27
Posted 22 December 2013 - 06:13 PM
'cause what happens if I do want it to go to the left!

obj.pos_x = 5
obj:write("something\n")
obj.pos_x = 1
obj:write("anotherthing\nthat\nis\non\nthe\nleft!")
:D/>
nope I don't like it, too verbose :P/>
Symmetryc #28
Posted 23 December 2013 - 10:01 AM
'cause what happens if I do want it to go to the left!

obj.pos_x = 5
obj:write("something\n")
obj.pos_x = 1
obj:write("anotherthing\nthat\nis\non\nthe\nleft!")
:D/>
nope I don't like it, too verbose :P/>
OK, make one program that utilizes it (well) and I will change it lol.
Edited on 23 December 2013 - 09:01 AM
kind-sir #29
Posted 26 December 2013 - 08:31 PM
Intended for Advanced Computers, a small benchmark is what I wrote.
Not accurate it is, but the job it does.
http://pastebin.com/2PkFLCfG
Spoiler
local x,y = term.getSize()
local buffer = dofile("api/buffer")
local test = buffer.new()
local trials = ... and ... or 10
local tstart,tend,tstart_buffer,tend_buffer = 0
term.clear()
--start benchmark of regular term api
tstart = os.clock()
for trial=trials,0,-1 do
  term.setCursorPos(1,1)
  for j=0,y do
	for i=0,x do
	  term.setCursorPos(i,j)
	  term.setBackgroundColor((2^math.random(0,15)))
	  term.setTextColor((2^math.random(0,15)))
	  term.write(tostring(math.random(0,10)))
	end
  end
end
tend = os.clock()
sleep(.2)
term.clear()
--start benchmark of buffer api
tstart_buffer = os.clock()
for trial=trials,0,-1 do
  test.pos_x = 1
  test.pos_y = 1
  for j=0,y do
	for i=0,x do
	  test.pos_x = i
	  test.pos_y = j
	  test.back = (2^math.random(0,15))
	  test.text = (2^math.random(0,15))
	  test:write(tostring(math.random(0,10)))
	end
  end
end
test:draw()
tend_buffer = os.clock()
--done with generation
sleep(.2)
term.setCursorPos(1,1)
term.setBackgroundColor(0x8000)
term.setTextColor(0x1)
term.clear()
term.write("Trials: "..(trials).." Unbuff: "..(tend-tstart).." buff: "..(tend_buffer-tstart_buffer).." diff: "..(math.abs((tend-tstart)-(tend_buffer-tstart_buffer))))
term.setCursorPos(1,2)

Run in singleplayer Minecraft with the latest ComputerCraft version, I was returned with:
>buffer_test
Trials: 10 Unbuff: 0.15 buff: 0.25 diff: 0.1
>buffer_test 20
Trials: 20 Unbuff: 0.3 buff: 0.6 diff: 0.3
>buffer_test 100
Trials: 100 Unbuff: 1.4 buff: 2.6 diff: 1.2

This is severely inaccurate due to how the os.clock is updated (per tick)
Testing this out in LOVE CC Emulator caused a stack overflow


Can someone double-check my code to make sure I didn't make a mistake or an inconsistency between the term and test loops?


Edit:
I realize how the above code does not actually provide a legitimate benchmark due to how the terminal draws and how the buffer api draws. It seems that the term api draws whenever there's an available time slice, probably through routines, while the buffer api draws after the whole frame has been generated.

I'm going to try this out with the os.loadAPI method and see how its performance compares.

Edit 2:
Results for using os.loadAPI method (with modified code): - NOT BETTER WHAT SO EVER
Spoiler
>buffer_test
Trials: 10 Unbuff: 0.15 buff: 0.9 diff: 0.75
>buffer_test 20
Trials: 20 Unbuff: 0.3 buff: 2.5 diff: 2.2
>buffer_test 30
Trials: 30 Unbuff: 0.45 buff: 4.65 diff: 4.2
--Anything over some number in (30,40) will cause the lua interpreter to kick in with "Too long without yielding"

modified api code:
http://pastebin.com/LYiuHgxQ
modified test code:
http://pastebin.com/3d733c3d
Edited on 26 December 2013 - 08:05 PM
Symmetryc #30
Posted 27 December 2013 - 01:11 PM
Reply to kind-sir:
SpoilerInteresting results, I tried it myself (I have a far more up-to-date version of this buffer, too lazy to put in on here :P/>), here was my outcome:

Testing file:

local ibuffer = dofile("ibuffer")
local t, p
local max_x, max_y = term.getSize()
local clock = os.clock()
local buff = ibuffer.buffer()
for i = 1, max_x do
for j = 1, max_y do
buff.pos_x, buff.pos_y = i, j
buff:write(" ")
end
end
buff:draw()
t = os.clock() - clock
local clock = os.clock()
for i = 1, max_x do
for j = 1, max_y do
term.setCursorPos(i, j)
write(" ")
end
end
p = os.clock() - clock
return t, p

Speed file:


local t, p = 0, 0
for i = 1, 100 do
local t1, p1 = dofile("startup")
t = t + t1
p = p + p1
end
print(t)
print(p)

The results were 1.45 (buffer) to 1.65 (term). Now obviously I used a biased test circumstance =P, but that was only because I wanted to show what the buffer can do better, which is removing unnecessary actions and appending. Furthermore, even if it is a lot weaker, it is still faster in terms of combining every thing into a single function for speed. In other words, let's say you had this:

local t = function()
  local x, y = term.getSize()
  term.setCursorPos(math.ceil(x / 3), y / 2)
  if term.isColor() then
	term.setBackgroundColor(colors.orange)
	term.setTextColor(colors.white)
  end
  write("term test")
end
local b = ibuffer.buffer()
local x, y = term.getSize()
b.pos_x, b.pos_y = math.ceil(x / 3), x / 2
if term.isColor() then
  b.text, b.back = colors.white, colors.orange
end
b:write("buffer test")
I can't test this at the moment, but I'm fairly certain that, when tallied up over time, b:draw() would be faster than t() because t() it would have to repeatedly do all of the color calculations and term.getSize calculations, whereas with the buffer, it is all stored into b.act, so it only has to do them once. This makes it so that if you are making a GUI based on a config file + term.getSize + term.isColor + http + etc., you wouldn't have to recalculate every time you draw. Hopefully that helped :)/>
Update on Buffer:
SpoilerAlright, major updates have taken place, I've added a way to sort of bind functions to your buffers and add them into one object and run it which will go through all of those functions and stuff, but that is largely in its infancy. I've also added the buffer to GitHub, so you guys can pull request/get added to contribute if you want :)/>. https://github.com/Symmetryc/Buffer
Edited on 27 December 2013 - 02:24 PM
theoriginalbit #31
Posted 27 December 2013 - 07:30 PM
which is removing unnecessary actions and appending.
which removes network traffic, which is also better!
H4X0RZ #32
Posted 10 January 2014 - 07:38 AM
May I use it in my OS/BIOS mod?
Symmetryc #33
Posted 10 January 2014 - 02:15 PM
May I use it in my OS/BIOS mod?
You may, however it is currently undergoing some major changes, so you may want to hold off for now.
cdel #34
Posted 10 December 2014 - 09:14 AM
Wasn't to sure, but I thought I'd ask just in case. Am I allowed to use this in my engine?

EDIT: Which file in the repo should I be using?
Edited on 10 December 2014 - 10:59 AM
Symmetryc #35
Posted 15 December 2014 - 02:28 AM
Hello, I would just like to inform you that most of this stuff is pretty outdated / unmaintained haha…

I'd suggest you look at this buffer instead, AFAIK, it's the most efficient one (I have been told it's faster than lyqyd's)
http://www.computercraft.info/forums2/index.php?/topic/20335-quick-buffer-a-fast-terminal-buffering-solution/page__p__193502#entry193502
cdel #36
Posted 15 December 2014 - 02:31 AM
thank you :)/>