From e7932329cfd32b6deb0c996f306b6c84086005b5 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 14 Aug 2018 00:30:48 +0100 Subject: [PATCH] Add a much better Lampulator This one (ab)uses the love2d.org lua game framework. Much of the code is copied (and lightly modified) from the older linux-draw-xpm codebase, which should probably otherwise be tossed into an incinerator. --- linux-draw-love/draw.lua | 185 +++++++++++++++++++++++++++++++++++++++ linux-draw-love/main.lua | 53 +++++++++++ linux-draw-love/net.lua | 10 +++ 3 files changed, 248 insertions(+) create mode 100644 linux-draw-love/draw.lua create mode 100644 linux-draw-love/main.lua create mode 100644 linux-draw-love/net.lua diff --git a/linux-draw-love/draw.lua b/linux-draw-love/draw.lua new file mode 100644 index 0000000..431a914 --- /dev/null +++ b/linux-draw-love/draw.lua @@ -0,0 +1,185 @@ +-- LOVE interface +limg = require "love.image" +ltmr = require "love.timer" +lthr = require "love.thread" + +imgd = limg.newImageData(8,4) +netchan = lthr.getChannel ( "netc" ) +framechan = lthr.getChannel ( "framec" ) + +-- emulate enough framebuffer functionality, backed by a Lua array <<< +ws2812 = {} +ws2812.SHIFT_LOGICAL = 0 +ws2812.SHIFT_CIRCULAR = 1 + +remotefb = {} +remotefb.length = 32 +function remotefb.set(self,ix,a1,a2,a3) + if ix > self.length then return end + if type(a1) == 'string' and a2 == nil and a3 == nil then + self[ix] = a1 + else + self[ix] = string.char(math.floor(a1),math.floor(a2),math.floor(a3)) + end +end +function remotefb.size(self) return self.length end +function remotefb.fade(self,factor,out) + local ix, f + if not out + then f = function(c) return string.char(string.byte(c,1) / factor) end + else f = function(c) return string.char(string.byte(c,1) * factor) end + end + for ix = 1,self.length do self[ix] = self[ix]:gsub(".", f) end +end +function remotefb.fill(self,...) + local i + for i = 1,self.length do self:set(i,...) end +end +function remotefb.shift(self,n,m,i,j) + if j == nil then j = self.length + elseif j < 0 then j = self.length - j + if j <= 0 then return end + elseif j == 0 then return + elseif j > self.length then return + end + if i == nil then i = 1 + elseif i < 0 then i = self.length - i + if i <= 0 then return end + elseif i == 0 then return + elseif i > self.length then return + end + + if m == nil then m = remotefb.SHIFT_LOGICAL end + + if n == 0 then return + elseif n > 0 then + local ix + for ix = 1, n do + local v = table.remove(self,j) + if m == ws2812.SHIFT_LOGICAL then v = string.char(0,0,0) end + table.insert(self,i,v) + end + elseif n < 0 then + local ix + for ix = 1, -n do + local v = table.remove(self,i) + if m == ws2812.SHIFT_LOGICAL then v = string.char(0,0,0) end + table.insert(self,j,v) + end + end +end +remotefb:fill(0,0,0) +-- >>> +-- drawfailsafe and dodraw <<< +local function drawfailsafe(t,fb,p) fb:fill(0,0,0) end +function loaddrawfn(name) + local f = loadfile (string.format("draw-%s.lua",name)) + local fn = f and f() + if fn + then return function(t,fb,p) return fn(t,fb,p) end + else return drawfailsafe + end +end + +-- dump the ws2812 array into a LOVE image and push it into a channel +function dodraw() + local ix = 0, r, c + for r = 0,3 do + for c = 0,7 do + ix = ix + 1 + imgd:setPixel(c, r, + math.min(0xFF,string.byte(remotefb[ix],2)*16)/256, + math.min(0xFF,string.byte(remotefb[ix],1)*16)/256, + math.min(0xFF,string.byte(remotefb[ix],3)*16)/256 + ) + end + end + + framechan:push(imgd) +end +--- >>> +-- timer queue and tmr emulation <<< + +local snooze = 1 +tq = require("core/tq/tq")(nil) +tq.now = function() return ltmr.getTime() * 1000000 end +tq.arm = function(self,t,et) + snooze = (t + 20) / 1000 -- round up a bit so we don't so often undershoot +end +local tqf = tq.fire +tq.fire = function(self) snooze = nil ; tqf(self) end + +-- how backwards is this!? We are using tq as faked above for tmr support +-- since it's not obvious to me how to remove pending events in cqueues. +tmr = {} +tmr.ALARM_SINGLE = 0 +tmr.ALARM_SEMI = 1 +tmr.ALARM_AUTO = 2 +function tmr._start(self) + if self.fn then self.tqe = tq:queue(self.period,self.fn) end +end +function tmr.stop(self) + if self.tqe then tq:dequeue(self.tqe); self.tqe = nil end +end +function tmr.unregister(self) + self:stop() + self.fn = nil +end +function tmr.register(self,period,mode,fn) + tmr.stop(self) + + self.period = period + if mode == tmr.ALARM_AUTO then + self.fn = function() tmr._start(self); fn() end + else + self.fn = fn + end +end +function tmr.start(self) + tmr.stop(self) + tmr._start(self) +end +function tmr.alarm(self,period,mode,fn) + tmr.stop(self) + tmr.register(self,period,mode,fn) + tmr.start(self) +end +function tmr.interval(self, period) + self.period = period + if self.tqe == nil then return end -- just update interval + self:start() -- otherwise, re-schedule +end +tmr_mt = { __index = tmr } +function tmr.create() + return setmetatable({}, tmr_mt) +end + +-- >>> +-- remote timer management <<< + +remotetmr = tmr.create() +remoteqtmrs = {} + +function removeremote() + local k,v + + -- drop all pending script timers + for k,v in pairs(remoteqtmrs) do v:unregister() end + remoteqtmrs = {} + + -- and the current remote animation's timer + remotetmr:unregister() +end + +-- >>> + +while true do + local line = netchan:demand(snooze) + + if line then + local from, cmd = line:match("^(%S+)%s+(.*)$") + if cmd then dofile("lamp-remote.lua")(cmd) end + end + + tq:fire() +end diff --git a/linux-draw-love/main.lua b/linux-draw-love/main.lua new file mode 100644 index 0000000..38119d2 --- /dev/null +++ b/linux-draw-love/main.lua @@ -0,0 +1,53 @@ +function love.threaderror(thread, errorstr) + print("Thread error!", thread, errorstr) + love.event.push('quit') +end + +function love.load() + love.window.setTitle("lampulator") + love.window.setMode(32, 16, { display = 1, x = 0, y = 0 }) + + framechan = love.thread.getChannel ( "framec" ); + netchan = love.thread.getChannel ( "netc" ); + + netthread = love.thread.newThread ( "net.lua" ); + netthread:start() + + drawthread = love.thread.newThread ( "draw.lua" ); + drawthread:start() +end + +-- Override the event loop +function love.run() + if love.load then love.load(love.arg.parseGameArguments(arg), arg) end + if love.timer then love.timer.step() end + + return function() + local dt = 0 + + if love.event then + local name, a,b,c,d,e,f + love.event.pump() + for name, a,b,c,d,e,f in love.event.poll() do + if name == "quit" then + if not love.quit or not love.quit() then + return a or 0 + end + end + love.handlers[name](a,b,c,d,e,f) + end + end + + -- XXX We have to poll the threads periodically to catch errors + local imgd = framechan:demand(1) + if imgd then + local img = love.graphics.newImage(imgd) + love.graphics.clear(love.graphics.getBackgroundColor()) + love.graphics.draw(img, 0, 0, 0, 4, 4) + end + love.graphics.present() + + if love.timer then dt = love.timer.step() end + + end +end diff --git a/linux-draw-love/net.lua b/linux-draw-love/net.lua new file mode 100644 index 0000000..cbe674d --- /dev/null +++ b/linux-draw-love/net.lua @@ -0,0 +1,10 @@ +-- Pull lines off stdin and push them into a LOVE channel +lev = require "love.event" +ltmr = require "love.timer" +lthr = require "love.thread" + +netchan = lthr.getChannel ( "netc" ) +for line in io.lines() do + netchan:push(line) +end +lev.push("quit") -- 2.50.1