-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathgfxbuffer.lua
253 lines (219 loc) · 6.83 KB
/
gfxbuffer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
local component=require("component")
local unicode=require("unicode")
local len = unicode.len
local buffer={VERSION="1.0"}
local bufferMeta={}
local debugPrint=function() end
local function convColor_8toh(hex)
local r, g, b
if bit32
then
r,g,b=bit32.rshift(hex,16),bit32.rshift(hex,8)%256,hex%256
else
r,g,b=hex>>16,(hex>>8)%256,hex%256
end
r=round(r*7/255)
g=round(g*7/255)
b=round(b*3/255)
return r*32+g*4+b
end
local function encodeColor(fg,bg)
return bg*0x1000000+fg
end
local function decodeColor(c)
return math.floor(c/0x1000000),c%0x1000000
end
function bufferMeta.getBackground(buffer)
return convColor_8toh(buffer.colorBackground)
end
function bufferMeta.setBackground(buffer,color)
local p=buffer.colorBackground
buffer.colorBackground=color
buffer.color=encodeColor(buffer.colorForeground,color)
return p
end
function bufferMeta.getForeground(buffer)
return buffer.colorForeground
end
function bufferMeta.setForeground(buffer,color)
local p=buffer.colorForeground
buffer.colorForeground=color
buffer.color=encodeColor(color,buffer.colorBackground)
return p
end
function bufferMeta.copy(buffer,x,y,w,h,dx,dy)
buffer.flush()
return buffer.parent.copy(x,y,w,h,dx,dy)
end
function bufferMeta.fill(buffer,x,y,w,h,char)
buffer.flush()
buffer.parent.setForeground(buffer.colorForeground)
buffer.parent.setBackground(buffer.colorBackground)
return buffer.parent.fill(x,y,w,h,char)
end
function bufferMeta.get(buffer,x,y)
buffer.flush()
return buffer.parent.get(x,y)
end
function bufferMeta.set(buffer,x,y,str)
local spans=buffer.spans
local spanI=1
local color=buffer.color
local e=x+len(str)-1
while spans[spanI] and (spans[spanI].y<y or spans[spanI].y==y and spans[spanI].e<x) do
spanI=spanI+1
end
--ok, now spanI is either intersecting me or the first after me
--if intersect, crop
if not spans[spanI] then
debugPrint("just inserting at "..spanI)
local span={str=str,e=e,x=x,y=y,color=color}
spans[spanI]=span
else
local span=spans[spanI]
debugPrint("scanned to span "..spanI)
if span.y==y and span.x<e then
debugPrint("it starts before I end.")
--it starts before me. Can I merge with it?
if span.color==color then
--we can merge. Yay.
--splice myself in
debugPrint("splicing at "..math.max(0,(x-span.x)))
local a,c=unicode.sub(span.str,1,math.max(0,x-span.x)), unicode.sub(span.str,e-span.x+2)
debugPrint("before=\""..a.."\", after=\""..c..'"')
span.str=a..str..c
--correct x and e(nd)
if x<span.x then
span.x=x
end
if e > span.e then
span.e=e
end
else
--can't, gonna have to make a new span
--but first, split this guy as needed
debugPrint("can't merge. Splitting")
local a,b=unicode.sub(span.str,1,math.max(0,x-span.x)),unicode.sub(span.str,e-span.x+2)
if len(a)>0 then
span.str=a
span.e=span.x+len(a)
--span is a new span
span={str=true,e=true,x=true,y=y,color=span.color}
--insert after this span
spanI=spanI+1
table.insert(spans,spanI,span)
end
if len(b)>0 then
span.str=b
span.x=e+1
span.e=span.x+len(b)
--and another new span
span={str=true,e=true,x=true,y=y,color=color}
--insert /before/ this one
table.insert(spans,spanI,span)
end
--now make whatever span we're left with me.
span.color=color
span.x, span.e = x, e
span.str=str
span.y=y
end
else
--starts after me. just insert.
local span={x=x,e=e,y=y,color=color,str=str}
table.insert(spans,spanI,span)
end
--ok. We are span. We are at spanI. We've inserted ourselves. Now just check if we've obliterated anyone.
--while the next span starts before I end...
spanI=spanI+1
while spans[spanI] and spans[spanI].y==y and spans[spanI].x<=e do
local span=spans[spanI]
if span.e>e then
--it goes past me, we just circumcise it
span.str=unicode.sub(span.str,e-span.x+2)
span.x=e+1
break--and there can't be more
end
--doesn't end after us, means we obliterated it
table.remove(spans,spanI)
--spanI will now point to the next, if any
end
end
--[[this..won't work. Was forgetting I have a table per row, this would count rows.
if #spans>=buffer.autoFlushCount then
buffer.flush()
end
--]]
end
function bufferMeta.flush(buffer)
debugPrint("flush?")
if #buffer.spans==0 then
return
end
--sort by colors. bg is added as high value, so this will group all with common bg together,
--and all with common fg together within same bg.
table.sort(buffer.spans,
function(spanA,spanB)
if spanA.color==spanB.color then
if spanA.y==spanB.y then
return spanA.x<spanB.x
end
return spanA.y<spanB.y
end
return spanA.color<spanB.color
end )
--now draw the spans!
local parent=buffer.parent
local cfg,cbg=pfg,pbg
local spans=buffer.spans
for i=1,#spans do
local span=spans[i]
local bg,fg=decodeColor(span.color)
if fg~=cfg then
parent.setForeground(fg)
cfg=fg
end
if bg~=cbg then
parent.setBackground(bg)
cbg=bg
end
parent.set(span.x,span.y,span.str)
end
if cfg~=buffer.colorForeground then
parent.setForeground(buffer.colorForeground)
end
if cbg~=buffer.colorBackground then
parent.setBackground(buffer.colorBackground)
end
--...and that's that. Throw away our spans.
buffer.spans={}
--might have to experiment later, see if the cost of rebuilding (and re-growing) the table is offset
--by the savings of not having the underlying spans object grow based on peak buffer usage,
--but if I'm optimizing for memory (and I am, in this case), then this seems a safe call for now.
--If it ends up an issue, might be able to offset the computational cost by initing to an array of some average size, then
--niling the elements in a loop.
end
function buffer.create(parent)
parent=parent or component.gpu
local width,height=parent.getResolution()
local newBuffer={
colorForeground=0xffffff,
colorBackground=0x000000,
color=0x00ff,
width=width,
height=height,
parent=parent,
spans={},
autoFlushCount=32,
getResolution=parent.getResolution,
setResolution=parent.setResolution,
maxResolution=parent.maxResolution,
getDepth=parent.getDepth,
setDepth=parent.setDepth,
maxDepth=parent.maxDepth,
getSize=parent.getSize,
}
setmetatable(newBuffer,{__index=function(tbl,key) local v=bufferMeta[key] if type(v)=="function" then return function(...) return v(tbl,...) end end return v end})
return newBuffer
end
return buffer