Advertisement
PaymentOption

QuickBuffer

Sep 14th, 2014
562
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 20.59 KB | None | 0 0
  1. --[[
  2.     Buffer                 Trystan Cannon
  3.                            24 August 2014
  4.  
  5.         The Buffer object allows for the screen
  6.     to be sectioned off such that they can be personally
  7.     redirected to and used as a separate terminal. This
  8.     makes things such as windowing and switching between
  9.     different programs in some kind of OS possible.
  10.  
  11.     NOTES:
  12.         - All methods, unless redirected to, require a 'self'argument
  13.           so that they can operate upon the buffer as a table.
  14.           HOWEVER, if the buffer is redirected to via term.redirect (buffer:redirect()),
  15.           then this is unnecessary.
  16.  
  17.         - The buffer is redirected to via its redirect table: self.tRedirect.
  18.  
  19.         - All generic terminal methods return the buffer as a 'self'
  20.           parameter along with whatever they were ment to return.
  21.  
  22.         - EX: buffer:getSize() returns the width, height, and self.
  23.  
  24.     IMPORTANT NOTE 1:
  25.             Each buffer's contents is separated into three tables of
  26.         equal width and height:
  27.             - tText
  28.             - tTextColors
  29.             - tBackColors
  30.             Each of whom is setup that each character represents either a textual
  31.         character or a hex digit to represent a color as a single byte.
  32.  
  33.             Colors are then converted at render time from their hex equivalent
  34.         into decimal format.
  35.  
  36.     IMPORTANT NOTE 2:
  37.             What makes this buffer special is the way that
  38.         it handles rendering. While many similar apis simply
  39.         save a pixel as a character, a text color, and a background
  40.         color, then write said pixel out with the respective
  41.         term API calls, this buffer does something different:
  42.  
  43.             Instead of changing colors all of the time, this
  44.         buffer makes rendering quicker by using a function
  45.         called 'getChunks.'
  46.  
  47.             'getChunks' goes through a line in a buffer and
  48.         returns every 'chunk' of text that has the given
  49.         text and background colors. This way, the maximum
  50.         amount of text can be written before colors are
  51.         changed.
  52.  
  53.             To prevent having to make 256 different iterations,
  54.         only the color pairs (set of text and background colors)
  55.         which are actually in the buffer are checked and rendered.
  56.         This is done by recording those used in 'write' and various
  57.         'clear' calls. Also, a function called 'updateColorPairs'
  58.         brute force checks the entire buffer for what color pairs
  59.         actually exist, then stores in them in the 'tColorPairs'
  60.         hash table which looks like this:
  61.             tColorPairs[sTextColor .. sBackColor] = true
  62.             (The value is true if it exists, nil or false if not.)
  63.  
  64.         However, it is important to note that this maximizes
  65.         efficiency for common use, for most programs make use
  66.         of large portions of similarly colored text both in
  67.         the text color and background color.
  68.             - HOWEVER, situations in which the text and background
  69.               color pair is changing very often, this buffer may
  70.               actually be SLOWER than the classic change-every-iteration
  71.               approach!
  72. ]]
  73.  
  74.  
  75.  
  76. ----------------------- Variables -----------------------
  77. local COLOR_PAIR_THRESHOLD = 25
  78. local tBufferMetatable = { __index = getfenv() }
  79. ----------------------- Variables -----------------------
  80.  
  81. ---------------------------------------------------------------------
  82. --[[
  83.         Returns the size of a table by using 'pairs' and counting
  84.     found key/value pairs.
  85.  
  86.     @return size
  87. ]]
  88. local function getTableSize(tTable)
  89.     local size  = 0
  90.     local pairs = pairs
  91.  
  92.     for _, __ in pairs(tTable) do
  93.         size = size + 1
  94.     end
  95.  
  96.     return size
  97. end
  98. ---------------------------------------------------------------------
  99. --[[
  100.             Goes through a line in a buffer and
  101.         returns every 'chunk' of text that has the given
  102.         text and background colors. This way, the maximum
  103.         amount of text can be written before colors are
  104.         changed.
  105.  
  106.         Each chunk looks like this:
  107.             - tChunks[n] = {
  108.                 nStart = (Position in the line at which this chunk starts.),
  109.                 nStop  = (Position in the line at which this chunk stops.)
  110.             }
  111.  
  112.     @return tChunks
  113. ]]
  114. function getChunks (self, nLineNumber, sTextColor, sBackColor)
  115.     local sTextColors = self.tTextColors[nLineNumber]
  116.     local sBackColors = self.tBackColors[nLineNumber]
  117.  
  118.     if not sTextColors:match (sTextColor) or not sBackColors:match (sBackColor) then
  119.         return {}
  120.     end
  121.  
  122.     local tChunks       = {}
  123.     local nStart, nStop = nil, nil
  124.  
  125.     repeat
  126.         nStart, nStop = sTextColors:find (sTextColor .. "+", nStart or 1)
  127.  
  128.         if nStart then
  129.             local sChunk                = sBackColors:sub (nStart, nStop)
  130.             local nBackStart, nBackStop = nil, nil
  131.  
  132.             repeat
  133.                 nBackStart, nBackStop = sChunk:find (sBackColor .. "+", nBackStart or 1)
  134.  
  135.                 if nBackStart then
  136.                     tChunks[#tChunks + 1] = { nStart = nStart + nBackStart - 1,
  137.                                               nStop  = nStart + nBackStop - 1
  138.                                             }
  139.                 end
  140.  
  141.                 nBackStart = (nBackStop ~= nil) and nBackStop + 1 or nil
  142.             until not nBackStart
  143.         end
  144.  
  145.         nStart = (nStop ~= nil) and nStop + 1 or nil
  146.     until not nStart
  147.  
  148.     return tChunks
  149. end
  150. ---------------------------------------------------------------------
  151. --[[
  152.         Iterates through the entirety of the buffer and updates the
  153.     tColorPairs table for the buffer. This is the brute force method
  154.     of checking which pairs actually exist in the buffer so that we
  155.     can maximize rendering speed.
  156.  
  157.     @return self
  158. ]]
  159. function updateColorPairs (self)
  160.     local tCheckedPairs = {}
  161.  
  162.     for nLineNumber = 1, self.nHeight do
  163.         local sTextColors = self.tTextColors[nLineNumber]
  164.         local sBackColors = self.tBackColors[nLineNumber]
  165.  
  166.         for sColorPair, _ in pairs (self.tColorPairs) do
  167.             if not tCheckedPairs[sColorPair] then
  168.                 local sTextColor, sBackColor = sColorPair:match ("(%w)(%w)")
  169.                 tCheckedPairs[sColorPair] = sTextColors:find (sTextColor) ~= nil and sBackColors:find (sBackColor, sTextColors:find (sTextColor)) or nil
  170.             end
  171.         end
  172.     end
  173.  
  174.     self.tColorPairs = tCheckedPairs
  175.     return self
  176. end
  177. ---------------------------------------------------------------------
  178. function getSize (self)
  179.     return self.nWidth, self.nHeight, self
  180. end
  181. ---------------------------------------------------------------------
  182. function getCursorPos (self)
  183.     return self.nCursorX, self.nCursorY, self
  184. end
  185. ---------------------------------------------------------------------
  186. function setCursorPos (self, x, y)
  187.     self.nCursorX = math.floor (x) or self.nCursorX
  188.     self.nCursorY = math.floor (y) or self.nCursorY
  189.  
  190.     return self
  191. end
  192. ---------------------------------------------------------------------
  193. function isColor (self)
  194.     return self.tTerm.isColor(), self
  195. end
  196. ---------------------------------------------------------------------
  197. function setTextColor (self, nTextColor)
  198.     self.sTextColor = string.format ("%x", math.log (nTextColor) / math.log (2)) or self.sTextColor
  199.     return self
  200. end
  201. ---------------------------------------------------------------------
  202. function setBackgroundColor (self, nBackColor)
  203.     self.sBackColor = string.format ("%x", math.log (nBackColor) / math.log (2)) or self.sBackColor
  204.     return self
  205. end
  206. ---------------------------------------------------------------------
  207. function setCursorBlink (self, bCursorBlink)
  208.     self.bCursorBlink = bCursorBlink
  209.     return self
  210. end
  211. ---------------------------------------------------------------------
  212. isColour            = isColor
  213. setTextColour       = setTextColor
  214. setBackgroundColour = setBackgroundColor
  215. ---------------------------------------------------------------------
  216. function clearLine (self, nLineNumber, bCheckColorPairs)
  217.     bCheckColorPairs = bCheckColorPairs or nLineNumber == nil
  218.     nLineNumber      = nLineNumber or self.nCursorY
  219.  
  220.     if nLineNumber >= 1 and nLineNumber <= self.nHeight then
  221.         self.tText[nLineNumber]       = (" "):rep (self.nWidth)
  222.         self.tTextColors[nLineNumber] = self.sTextColor:rep (self.nWidth)
  223.         self.tBackColors[nLineNumber] = self.sBackColor:rep (self.nWidth)
  224.  
  225.         self.tColorPairs[self.sTextColor .. self.sBackColor] = true
  226.  
  227.         if bCheckColorPairs then
  228.             self:updateColorPairs()
  229.         end
  230.     end
  231. end
  232. ---------------------------------------------------------------------
  233. function clear (self, bRecord)
  234.     for nLineNumber = 1, self.nHeight do
  235.         self:clearLine (nLineNumber)
  236.     end
  237.  
  238.     self.tColorPairs[self.sTextColor .. self.sBackColor] = true
  239.     self:updateColorPairs()
  240.  
  241.     -- Initialize the buffer's redirect if it doesn't have one yet.
  242.     -- However, if the user wants to record, then the redirect will
  243.     -- be generated such that it writes to the screen and the buffer
  244.     -- at the same time.
  245.     -- Recreate the redirect if the user wants to turn recording off, too.
  246.     if not self.tRedirect or bRecord or (self.bRecord and not bRecord) then
  247.         self.tRedirect = {}
  248.         self.bRecord   = bRecord
  249.  
  250.         for sFunctionName, _ in pairs (self.tTerm) do
  251.             -- Create two different kinds of functions instead of
  252.             -- performing the check within the function itself.
  253.             if bRecord then
  254.                 self.tRedirect[sFunctionName] = function (...)
  255.                     self.tTerm[sFunctionName](...)
  256.                     return self[sFunctionName] (self, ...)
  257.                 end
  258.             else
  259.                 self.tRedirect[sFunctionName] = function (...)
  260.                     return self[sFunctionName] (self, ...)
  261.                 end
  262.             end
  263.         end
  264.     end
  265.  
  266.     return self
  267. end
  268. ---------------------------------------------------------------------
  269. function scroll (self, nTimesToScroll)
  270.     for nTimesScrolled = 1, math.abs (nTimesToScroll) do
  271.         if nTimesToScroll > 0 then
  272.             for nLineNumber = 1, self.nHeight do
  273.                 self.tText[nLineNumber]       = self.tText[nLineNumber + 1] or string.rep (" ", self.nWidth)
  274.                 self.tTextColors[nLineNumber] = self.tTextColors[nLineNumber + 1] or string.rep (self.sTextColor, self.nWidth)
  275.                 self.tBackColors[nLineNumber] = self.tBackColors[nLineNumber + 1] or string.rep (self.sBackColor, self.nWidth)
  276.             end
  277.         else
  278.             for nLineNumber = self.nHeight, 1, -1 do
  279.                 self.tText[nLineNumber]       = self.tText[nLineNumber - 1] or string.rep (" ", self.nWidth)
  280.                 self.tTextColors[nLineNumber] = self.tTextColors[nLineNumber - 1] or string.rep (self.sTextColor, self.nWidth)
  281.                 self.tBackColors[nLineNumber] = self.tBackColors[nLineNumber - 1] or string.rep (self.sBackColor, self.nWidth)
  282.             end
  283.         end
  284.     end
  285.  
  286.     self.tColorPairs[self.sTextColor .. self.sBackColor] = true
  287.     self:updateColorPairs()
  288. end
  289. ---------------------------------------------------------------------
  290. function write (self, sText)
  291.     if self.nCursorY >= 1 and self.nCursorY <= self.nHeight then
  292.         -- Our rendering problems might be stemming from a problem regarding the color pairs that are registered at render time.
  293.         self.tColorPairs[self.sTextColor .. self.sBackColor] = true
  294.  
  295.         sText = tostring (sText):gsub ("\t", " "):gsub ("%c", "?")
  296.  
  297.         local sTextLine   = self.tText[self.nCursorY]
  298.         local sTextColors = self.tTextColors[self.nCursorY]
  299.         local sBackColors = self.tBackColors[self.nCursorY]
  300.  
  301.         --[[
  302.             This could be better. We just need to calculate stuff instead of using a for loop.
  303.         ]]
  304.         for nCharacterIndex = 1, sText:len() do
  305.             if self.nCursorX >= 1 and self.nCursorX <= self.nWidth then
  306.                 sTextLine =
  307.                       sTextLine:sub (1, self.nCursorX - 1) ..
  308.                       sText:sub (nCharacterIndex, nCharacterIndex) ..
  309.                       sTextLine:sub (self.nCursorX + 1)
  310.                 sTextColors = sTextColors:sub (1, self.nCursorX - 1) .. self.sTextColor .. sTextColors:sub (self.nCursorX + 1)
  311.                 sBackColors = sBackColors:sub (1, self.nCursorX - 1) .. self.sBackColor .. sBackColors:sub (self.nCursorX + 1)
  312.             end
  313.  
  314.             self.nCursorX = self.nCursorX + 1
  315.         end
  316.  
  317.         self.tText[self.nCursorY]       = sTextLine
  318.         self.tTextColors[self.nCursorY] = sTextColors
  319.         self.tBackColors[self.nCursorY] = sBackColors
  320.     end
  321.  
  322.     return self
  323. end
  324. ---------------------------------------------------------------------
  325. --[[
  326.         Renders the contents of the buffer to its tTerm object using
  327.     the traditional method of switching colors every pixel.
  328.  
  329.         This is used for when the number of color pairs in used in
  330.     the buffer is high enough such that the optimised method no
  331.     longer is, well, optimised.
  332.  
  333.     @see COLOR_PAIR_THRESHOLD
  334. ]]
  335. function traditional_render(self)
  336.     local redirect     = term.redirect
  337.     local tCurrentTerm = redirect(self.tTerm)
  338.  
  339.     local setBackgroundColor = term.setBackgroundColor
  340.     local setTextColor       = term.setTextColor
  341.     local write              = term.write
  342.     local setCursorPos       = term.setCursorPos
  343.     local setCursorBlink     = term.setCursorBlink
  344.    
  345.     local tText       = self.tText
  346.     local tTextColors = self.tTextColors
  347.     local tBackColors = self.tBackColors
  348.  
  349.     local nHeight  = self.nHeight
  350.     local nWidth   = self.nWidth
  351.     local nCursorX = self.nCursorX
  352.     local nCursorY = self.nCursorY
  353.     local x        = self.x
  354.     local y        = self.y
  355.  
  356.     local sTextLine, sTextColorLine, sBackColorLine
  357.     local sCurrentTextColor, sCurrentBackColor
  358.     local sTextColor, sBackColor
  359.  
  360.     local tonumber = _G.tonumber
  361.     local sub      = string.sub
  362.  
  363.     for nLine = 1, nHeight do
  364.         sTextLine      = tText[nLine]
  365.         sTextColorLine = tTextColors[nLine]
  366.         sBackColorLine = tBackColors[nLine]
  367.  
  368.         setCursorPos(1, nLine)
  369.  
  370.         for nPixel = 1, nWidth do
  371.             sTextColor = sub(sTextColorLine, nPixel, nPixel)
  372.             sBackColor = sub(sBackColorLine, nPixel, nPixel)
  373.  
  374.             if sCurrentTextColor ~= sTextColor then
  375.                 sCurrentTextColor = sTextColor
  376.                 setTextColor(2 ^ tonumber(sCurrentTextColor, 16))
  377.             end
  378.             if sCurrentBackColor ~= sBackColor then
  379.                 sCurrentBackColor = sBackColor
  380.                 setBackgroundColor(2 ^ tonumber(sCurrentBackColor, 16))
  381.             end
  382.  
  383.             write(sub(sTextLine, nPixel, nPixel))
  384.         end
  385.     end
  386.  
  387.     setCursorPos(nCursorX + x - 1, nCursorY + y - 1)
  388.     setCursorBlink(self.bCursorBlink)
  389.  
  390.     sTextColor = self.sTextColor
  391.     sBackColor = self.sBackColor
  392.  
  393.     if sTextColor ~= sCurrentTextColor then
  394.         setTextColor(2 ^ tonumber(sTextColor, 16))
  395.     end
  396.     if sBackColor ~= sCurrentBackColor then
  397.         setBackgroundColor(2 ^ tonumber(sBackColor, 16))
  398.     end
  399.  
  400.     redirect(tCurrentTerm)
  401.     return self
  402. end
  403. ---------------------------------------------------------------------
  404. --[[
  405.         Renders the contents of the buffer to its tTerm object. This should
  406.     be the current terminal object or monitor or whatever output it should
  407.     render to.
  408.  
  409.         The position of the cursor, blink state, and text/background color
  410.     states are restored to their states prior to rendering.
  411.  
  412.     @return self
  413.     @return bOptimisedRender -- false if the buffer rendered traditionally.
  414. ]]
  415. function render (self)
  416.     if getTableSize(self.tColorPairs) >= COLOR_PAIR_THRESHOLD then
  417.         return self:traditional_render(), false
  418.     end
  419.  
  420.     local tCurrentTerm = term.redirect (self.tTerm)
  421.  
  422.     local sCurrentTextColor;
  423.     local sCurrentBackColor;
  424.  
  425.     for sColorPair, _ in pairs (self.tColorPairs) do
  426.         local sTextColor, sBackColor = sColorPair:match ("(%w)(%w)")
  427.  
  428.         if sCurrentTextColor ~= sTextColor then
  429.             term.setTextColor (2 ^ tonumber (sTextColor, 16))
  430.             sCurrentTextColor = sTextColor
  431.         end
  432.         if sCurrentBackColor ~= sBackColor then
  433.             term.setBackgroundColor (2 ^ tonumber (sBackColor, 16))
  434.             sCurrentBackColor = sBackColor
  435.         end
  436.  
  437.         for nLineNumber = 1, self.nHeight do
  438.             for _, tChunk in ipairs (self:getChunks (nLineNumber, sTextColor, sBackColor)) do
  439.                 term.setCursorPos (tChunk.nStart + self.x - 1, nLineNumber + self.y - 1)
  440.                 term.write (self.tText[nLineNumber]:sub (tChunk.nStart, tChunk.nStop))
  441.             end
  442.         end
  443.     end
  444.  
  445.     term.setCursorPos (self.nCursorX + self.x - 1, self.nCursorY + self.y - 1)
  446.     term.setCursorBlink (self.bCursorBlink)
  447.  
  448.     if self.sTextColor ~= sCurrentTextColor then
  449.         term.setTextColor (2 ^ tonumber (self.sTextColor, 16))
  450.     end
  451.     if self.sBackColor ~= sCurrentBackColor then
  452.         term.setBackgroundColor (2 ^ tonumber (self.sBackColor, 16))
  453.     end
  454.  
  455.     term.redirect(tCurrentTerm)
  456.     return self, true
  457. end
  458. ---------------------------------------------------------------------
  459. --[[
  460.         Creates and returns a new Buffer object with the specified dimensions
  461.     and offset position on the screen.
  462.  
  463.         The tCurrentTerm object is the object returned by term.current()
  464.     such that this is the output desired for the buffer when its render
  465.     function is invoked.
  466.         The term.current() return value should be the screen that the
  467.     user will see so that rendering actually produces visual output.
  468.  
  469.     @return self
  470. ]]
  471. function new (nWidth, nHeight, x, y, tCurrentTerm)
  472.     return setmetatable (
  473.         {
  474.             nWidth  = nWidth,
  475.             nHeight = nHeight,
  476.  
  477.             tRedirect = false,
  478.  
  479.             x = x,
  480.             y = y,
  481.  
  482.             tTerm = tCurrentTerm,
  483.  
  484.             nCursorX = 1,
  485.             nCursorY = 1,
  486.  
  487.             sTextColor = "0",
  488.             sBackColor = "f",
  489.  
  490.             tText       = {},
  491.             tTextColors = {},
  492.             tBackColors = {},
  493.             tColorPairs = {},
  494.  
  495.             bCursorBlink = false
  496.         }
  497.     , tBufferMetatable):clear()
  498. end
  499. ---------------------------------------------------------------------
  500. --[[
  501.         This is essentially deprectated, but I've left it here so that
  502.     this Buffer is compatible with code written for earlier versions
  503.     in which the table was generated at every call.
  504.  
  505.     @return self.tRedirect
  506. ]]
  507. function redirect (self)
  508.     return self.tRedirect
  509. end
  510. ---------------------------------------------------------------------
  511. --[[
  512.         Creates and returns a function that renders the current state of the
  513.     buffer.
  514.  
  515.     @return fStaticBuffer
  516. ]]
  517. function getStaticBuffer (self)
  518.     local tBufferTerm   = self.tTerm
  519.     local tListenerTerm = {
  520.         tCalls = {}
  521.     }
  522.  
  523.     for sFunctionName, fFunction in pairs (tBufferTerm) do
  524.         tListenerTerm[sFunctionName] = function (...)
  525.             local tArgs = { ... }
  526.  
  527.             tListenerTerm.tCalls[#tListenerTerm.tCalls + 1] = function()
  528.                 tBufferTerm[sFunctionName] (unpack (tArgs))
  529.             end
  530.         end
  531.     end
  532.  
  533.     self.tTerm = tListenerTerm
  534.     self:render()
  535.     self.tTerm = tBufferTerm
  536.  
  537.     return function()
  538.         for _, fCall in ipairs (tListenerTerm.tCalls) do
  539.             fCall()
  540.         end
  541.     end
  542. end
  543. ---------------------------------------------------------------------
  544. --[[
  545.     Creates and returns a "section" of the buffer from the given line
  546.     to the a stopping point.
  547.  
  548.     WARNING: Parameters are not checked for validity!
  549. ]]
  550. function getSection(self, nStartX, nStopX, nStartY, nStopY)
  551.     local tSection = Buffer.new(nStopX - nStartX + 1, nStopY - nStartY + 1, self.x, self.y, self.tTerm)
  552.  
  553.     for nLine = nStartY, nStopY do
  554.         if not tSection.tText[nLine - nStartY + 1] then
  555.             error("tSection didn't have line #" .. (nLine - nStartY + 1))
  556.         elseif not self.tText[nLine] then
  557.             error("Buffer didn't have line #" .. nLine .. "; s = " .. nStartY .. ", e = " .. nStopY)
  558.         end
  559.  
  560.         tSection.tText[nLine - nStartY + 1]       = self.tText[nLine]:sub(nStartX, nStopX)
  561.         tSection.tTextColors[nLine - nStartY + 1] = self.tTextColors[nLine]:sub(nStartX, nStopX)
  562.         tSection.tBackColors[nLine - nStartY + 1] = self.tBackColors[nLine]:sub(nStartX, nStopX)
  563.     end
  564.  
  565.     return tSection
  566. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement