diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bba60d..7811958 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: run: luarocks install luacc - name: Install luamin - run: npm install -g https://github.com/cyberbit/luamin + run: sudo yarn global add https://github.com/cyberbit/luamin - name: Build run: ./build.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index be504a1..f3a6de7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "autobuild": true, + // "autobuild": true, "Lua.diagnostics.globals": [ "colors", "colours", @@ -26,6 +26,12 @@ "turtle", "vector", "window", - "mekanismEnergyHelper" - ] + "mekanismEnergyHelper", + "require" + ], + "triggerTaskOnSave.tasks": { + "build": [ + "src/telem/**/*.lua" + ] + } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 08af515..83764e5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,7 +10,7 @@ "presentation": { "close": true, "focus": false, - "reveal": "silent", + "reveal": "always", "panel": "shared" } } diff --git a/docs/reference/Backplane.md b/docs/reference/Backplane.md index d08f865..3489075 100644 --- a/docs/reference/Backplane.md +++ b/docs/reference/Backplane.md @@ -151,6 +151,14 @@ Backplane:debug (state: boolean) Set internal debug state. When `state` is `true`, Backplane will write verbose information to the terminal during a cycle. When `state` is `false`, Backplane will only write adapter faults to the terminal. Any adapters added *after* calling `debug()` will also have their debug state set to the same value. +### `cache` + +```lua +Backplane:cache (state: boolean) +``` + +Set output cache state. When `state` is `true`, Backplane will save the data history of cacheable output adapters to the filesystem at regular intervals. If the program or computer halts and restarts, the cache will be loaded and the output adapters will be re-initialized with the cached data. An output adapter must call `self:cacheable()`, as well as implement `getState()` and `loadState()`, to be cacheable. + ## Usage ```lua diff --git a/docs/reference/InputAdapter.md b/docs/reference/InputAdapter.md index f49a0d8..d1bb5ce 100644 --- a/docs/reference/InputAdapter.md +++ b/docs/reference/InputAdapter.md @@ -84,10 +84,10 @@ self.components = { } ``` -### `read` +### `read` -Reads all assigned components. +```lua +InputAdapter:read (): MetricCollection +``` -::: danger -Because InputAdapter is an abstract class, calling this method on InputAdapter or on an incomplete implementation of InputAdapter will throw an error. -::: \ No newline at end of file +Reads data from implementation-defined sources into a [MetricCollection](MetricCollection) and returns it. \ No newline at end of file diff --git a/docs/reference/OutputAdapter.md b/docs/reference/OutputAdapter.md index 740f7d6..f5c2a10 100644 --- a/docs/reference/OutputAdapter.md +++ b/docs/reference/OutputAdapter.md @@ -4,7 +4,7 @@ outline: deep # OutputAdapter -OutputAdapter is an [abstract](https://en.wikipedia.org/wiki/Abstract_type) class that functions as a metric consumer. Typically, an OutputAdapter implementation will accept one or more parameters to its constructor method. These properties define what data sources and/or peripherals it should query when `read()` is called. +OutputAdapter is an [abstract](https://en.wikipedia.org/wiki/Abstract_type) class that functions as a metric consumer. Typically, an OutputAdapter implementation will accept one or more parameters to its constructor method. These properties define what targets and/or peripherals it should interact with when `write()` is called. ## Properties @@ -77,14 +77,42 @@ self.components = { } ``` -## `write` +### `write` ```lua OutputAdapter:write (metrics: MetricCollection) ``` -Writes provided [MetricCollection](MetricCollection) to all assigned components. Specific behavior is implementation-dependent. +Writes provided [MetricCollection](MetricCollection) to implementation-defined targets. This will be called by the [Backplane](Backplane) during a cycle. -::: danger -Because OutputAdapter is an abstract class, calling this method on InputAdapter or on an incomplete implementation of InputAdapter will throw an error. -::: \ No newline at end of file +### `debug` + +```lua +OutputAdapter:debug (state: boolean) +``` + +Set internal debug state. When `state` is `true`, OutputAdapter will write verbose information to the terminal. + +### `cacheable` + +```lua +OutputAdapter:cacheable () +``` + +Flag this OutputAdapter implementation as cacheable. This will notify the Backplane to call `getState()` and `loadState()` on this adapter when needed. + +### `getState` + +```lua +OutputAdapter:getState (): table +``` + +Returns a serializable version of the OutputAdapter's internal state. + +### `loadState` + +```lua +OutputAdapter:loadState (state: table) +``` + +Loads a previously saved state into the OutputAdapter. \ No newline at end of file diff --git a/docs/reference/output/ChartLine.md b/docs/reference/output/ChartLine.md index b29d627..1073c31 100644 --- a/docs/reference/output/ChartLine.md +++ b/docs/reference/output/ChartLine.md @@ -14,6 +14,10 @@ telem.output.plotter.chartLine ( ) ``` +::: tip +This adapter is [cacheable](/reference/Backplane#cache). +::: + Search the available metrics using the syntax defined in [`find`](/reference/MetricCollection#find) and output a line chart to a specified window. If a matching metric is found, the metric value is pushed to the chart buffer. The X axis (horizontal value) represents the number of data points recorded, and has a default width of `50`, which can be overridden by passing `maxEntries` in the constructor. The Y axis (vertical) represents the value of the metric over time. Once the fixed width of the graph is reached, the oldest values will be dropped from the buffer when new values are added. The minimum and maximum range of the Y axis is determined by the minimum and maximum metric values in the graph buffer. The background of the widget and all labels will be `bg`, and the graph/text color will be `fg`. diff --git a/docs/reference/output/basalt/Graph.md b/docs/reference/output/basalt/Graph.md index 6404a8e..e48640a 100644 --- a/docs/reference/output/basalt/Graph.md +++ b/docs/reference/output/basalt/Graph.md @@ -18,6 +18,10 @@ telem.output.basalt.graph ( Requires **[Basalt 1.7+](https://basalt.madefor.cc/)**. This is **not** included in the installer's dependencies due to size. ::: +::: tip +This adapter is [cacheable](/reference/Backplane#cache). +::: + Search the available metrics using the syntax defined in [`find`](/reference/MetricCollection#find) and output a graph to a specified [Basalt container](https://basalt.madefor.cc/#/objects/Container). If a matching metric is found, the metric value is pushed to the graph buffer. The X axis (horizontal value) represents the number of data points recorded, and has a default width of `50`, which can be overridden by passing `maxEntries` in the constructor. The Y axis (vertical) represents the value of the metric over time. Once the fixed width of the graph is reached, the oldest values will be dropped from the buffer when new values are added. The minimum and maximum range of the Y axis is determined by the minimum and maximum metric values in the graph buffer. The background of the widget and all labels will be `bg`, and the graph/text color will be `fg`. diff --git a/init_devcontainer.sh b/init_devcontainer.sh new file mode 100755 index 0000000..14958bc --- /dev/null +++ b/init_devcontainer.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# set up workspace +mkdir .luatmp +cd .luatmp + +# install lua +# sudo apt update +sudo apt install lua5.3 liblua5.3-dev + +# install luarocks +wget https://luarocks.org/releases/luarocks-3.11.1.tar.gz +tar zxpf luarocks-3.11.1.tar.gz +cd luarocks-3.11.1 +./configure && make && sudo make install + +# install luacc +sudo luarocks install luacc + +# remove workspace +cd ../.. +rm -rf .luatmp + +# install luamin +sudo yarn global add https://github.com/cyberbit/luamin \ No newline at end of file diff --git a/src/telem/init.lua b/src/telem/init.lua index e45bb4a..dd1da6b 100644 --- a/src/telem/init.lua +++ b/src/telem/init.lua @@ -1,9 +1,9 @@ -- Telem by cyberbit -- MIT License --- Version 0.5.3 +-- Version 0.6.0 local _Telem = { - _VERSION = '0.5.3', + _VERSION = '0.6.0', util = require 'telem.lib.util', input = require 'telem.lib.input', output = require 'telem.lib.output', diff --git a/src/telem/lib/Backplane.lua b/src/telem/lib/Backplane.lua index 2e14dda..272c45f 100644 --- a/src/telem/lib/Backplane.lua +++ b/src/telem/lib/Backplane.lua @@ -10,6 +10,11 @@ Backplane.type = 'Backplane' function Backplane:constructor () self.debugState = false + + self.loaded = false + self.cacheState = false + self.lastCache = 0 + self.cacheRate = 1 self.inputs = {} self.outputs = {} @@ -93,6 +98,33 @@ function Backplane:cycle() self:dlog('Backplane:cycle :: ' .. os.date()) self:dlog('Backplane:cycle :: cycle START !') + -- load output states + if not self.loaded and self.cacheState then + self:dlog('Backplane:cycle :: loading state...') + + local inf = fs.open('/.telem/state', 'r') + + if inf then + local cache = textutils.unserialize(inf.readAll()) or {} + inf.close() + + for k, v in pairs(cache) do + local output = self.outputs[k] + + if output and output.isCacheable then + self:dlog('Backplane:cycle :: - ' .. k) + + local results = {pcall(output.loadState, output, v)} + + if not table.remove(results, 1) then + t.log('loadState fault for "' .. k .. '":') + t.pprint(table.remove(results, 1)) + end + end + end + end + end + self:dlog('Backplane:cycle :: reading inputs...') -- read inputs @@ -131,8 +163,42 @@ function Backplane:cycle() self:dlog('Backplane:cycle :: saving state...') + self:dlog('Backplane:cycle :: - Backplane') self.collection = metrics + -- cache output states + if self.cacheState then + local time = os.epoch('utc') + + if self.lastCache + self.cacheRate * 1000 <= time then + local cache = {} + + for _, key in pairs(self.outputKeys) do + local output = self.outputs[key] + + if output.isCacheable then + self:dlog('Backplane:cycle :: - ' .. key) + + local results = {pcall(output.getState, output)} + + if not table.remove(results, 1) then + t.log('getState fault for "' .. key .. '":') + t.pprint(table.remove(results, 1)) + end + + cache[key] = table.remove(results, 1) + end + end + + fs.makeDir('/.telem') + local outf = fs.open('/.telem/state', 'w') + outf.write(textutils.serialize(cache, { compact = true })) + outf.flush() + + self.lastCache = time + end + end + self:dlog('Backplane:cycle :: writing outputs...') -- write outputs @@ -149,6 +215,10 @@ function Backplane:cycle() end end + if not self.loaded then + self.loaded = true + end + self:dlog('Backplane:cycle :: cycle END !') return self @@ -201,6 +271,12 @@ function Backplane:updateLayouts() self:dlog('Backplane:updateLayouts :: Layouts updated') end +function Backplane:cache(cache) + self.cacheState = cache and true or false + + return self +end + function Backplane:debug(debug) self.debugState = debug and true or false diff --git a/src/telem/lib/OutputAdapter.lua b/src/telem/lib/OutputAdapter.lua index 28a4e0d..5393cb6 100644 --- a/src/telem/lib/OutputAdapter.lua +++ b/src/telem/lib/OutputAdapter.lua @@ -11,6 +11,8 @@ function OutputAdapter:constructor() self.asyncCycleHandler = nil + self.isCacheable = false + -- boot components self:setBoot(function () self.components = {} @@ -51,6 +53,18 @@ function OutputAdapter:addComponentByPeripheralType (type) self.components[key] = tempComponent end +function OutputAdapter:cacheable () + self.isCacheable = true +end + +function OutputAdapter:getState () + t.err(self.type .. ' has not implemented getState()') +end + +function OutputAdapter:loadState (state) + t.err(self.type .. ' has not implemented loadState()') +end + function OutputAdapter:write (metrics) t.err(self.type .. ' has not implemented write()') end diff --git a/src/telem/lib/output/basalt/GraphOutputAdapter.lua b/src/telem/lib/output/basalt/GraphOutputAdapter.lua index 40a8170..f92aee1 100644 --- a/src/telem/lib/output/basalt/GraphOutputAdapter.lua +++ b/src/telem/lib/output/basalt/GraphOutputAdapter.lua @@ -25,6 +25,8 @@ end function GraphOutputAdapter:constructor (frame, filter, bg, fg, maxEntries) self:super('constructor') + self:cacheable() + self.bBaseFrame = assert(frame, 'Frame is required') self.filter = assert(filter, 'Filter is required') @@ -135,4 +137,49 @@ function GraphOutputAdapter:write (collection) return self end +function GraphOutputAdapter:getState () + local graphdata = {} + + for k,v in ipairs(self.graphdata) do + graphdata[k] = v + end + + return { + graphdata = graphdata, + tick = self.tick + } +end + +function GraphOutputAdapter:loadState (state) + self.graphdata = state.graphdata + self.tick = state.tick + + local newmin, newmax = graphtrackrange(self) + + if newmin == newmax then + newmin = newmin - 1 + newmax = newmax + 1 + end + + self.graph:setMinValue(newmin):setMaxValue(newmax) + + for _,v in ipairs(self.graphdata) do + self.graph:addDataPoint(v) + + if self.tick == self.SCALE_TICK then + self.graphscale:addDataPoint(100) + self.tick = 1 + else + self.graphscale:addDataPoint(50) + self.tick = self.tick + 1 + end + end + + self.label:setFontSize(2) + self.label:setText(t.shortnum(self.graphdata[#self.graphdata])) + + self.labelmax:setText(t.shortnum(newmax)) + self.labelmin:setText(t.shortnum(newmin)) +end + return GraphOutputAdapter \ No newline at end of file diff --git a/src/telem/lib/output/plotter/ChartLineOutputAdapter.lua b/src/telem/lib/output/plotter/ChartLineOutputAdapter.lua index c73ba77..ac1bb9d 100644 --- a/src/telem/lib/output/plotter/ChartLineOutputAdapter.lua +++ b/src/telem/lib/output/plotter/ChartLineOutputAdapter.lua @@ -15,6 +15,8 @@ ChartLineOutputAdapter.X_TICK = 10 function ChartLineOutputAdapter:constructor (win, filter, bg, fg, maxEntries) self:super('constructor') + self:cacheable() + self.win = assert(win, 'Window is required') self.filter = assert(filter, 'Filter is required') @@ -89,6 +91,24 @@ function ChartLineOutputAdapter:write (collection) return self end +function ChartLineOutputAdapter:getState () + local plotData = {} + + for k,v in ipairs(self.plotData) do + plotData[k] = v + end + + return { + plotData = plotData, + gridOffsetX = self.gridOffsetX + } +end + +function ChartLineOutputAdapter:loadState (state) + self.plotData = state.plotData + self.gridOffsetX = state.gridOffsetX +end + function ChartLineOutputAdapter:render () local dataw = #{self.plotData}