Skip to content

Commit 2e399de

Browse files
committed
add screenshot support and debugger actions
1 parent cd6ef3c commit 2e399de

13 files changed

Lines changed: 380 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Add play/pause logs button
1515
- Add changelog page (github link)
1616
- Add license page (github link)
17+
- Add screenshot plugin
18+
- Add basic UI support for plugins
19+
- Add action support for plugins
20+
- Add screenshot option for error capture
1721

1822
### Changed
1923

README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,22 @@ end
6464

6565
`Feather:init(config)` accepts the following options:
6666

67-
| Option | Type | Default | Description |
68-
| -------------------------- | ---------- | ------------------- | --------------------------------------------------------------------------------------------------- |
69-
| `debug` | `boolean` | `false` | Enable or disable Feather entirely. |
70-
| `host` | `string` | `"*"` | Host address to bind the server to. |
71-
| `port` | `number` | `4004` | Port to listen on. |
72-
| `baseDir` | `string` | `""` | Base directory path for file references and deeplinking to vs code, useful for multi-project setups |
73-
| `wrapPrint` | `boolean` | `false` | Wrap `print()` calls to send to Feather's log viewer. |
74-
| `whitelist` | `table` | `{ "127.0.0.1" }` | List of IPs allowed to connect. |
75-
| `maxTempLogs` | `number` | `200` | Max number of temporary logs stored before rotation. |
76-
| `updateInterval` | `number` | `0.1` | Interval between sending updates to clients. |
77-
| `defaultObservers` | `boolean` | `false` | Register built-in variable watchers. |
78-
| `errorWait` | `number` | `3` | Seconds to wait for error delivery before showing LÖVE's handler. |
79-
| `autoRegisterErrorHandler` | `boolean` | `false` | Replace LÖVE's `errorhandler` to capture errors. |
80-
| `errorHandler` | `function` | `love.errorhandler` | Custom error handler to use. |
81-
| `plugins` | `table` | `{}` | List of plugin modules to load. (Support Coming soon) |
67+
| Option | Type | Default | Description |
68+
| -------------------------- | ---------- | ------------------- | ---------------------------------------------------------------------------------------------------- |
69+
| `debug` | `boolean` | `false` | Enable or disable Feather entirely. |
70+
| `host` | `string` | `"*"` | Host address to bind the server to. |
71+
| `port` | `number` | `4004` | Port to listen on. |
72+
| `baseDir` | `string` | `""` | Base directory path for file references and deeplinking to vs code, useful for multi-project setups |
73+
| `wrapPrint` | `boolean` | `false` | Wrap `print()` calls to send to Feather's log viewer. |
74+
| `whitelist` | `table` | `{ "127.0.0.1" }` | List of IPs allowed to connect. |
75+
| `maxTempLogs` | `number` | `200` | Max number of temporary logs stored before rotation. |
76+
| `updateInterval` | `number` | `0.1` | Interval between sending updates to clients. |
77+
| `defaultObservers` | `boolean` | `false` | Register built-in variable watchers. |
78+
| `errorWait` | `number` | `3` | Seconds to wait for error delivery before showing LÖVE's handler. |
79+
| `autoRegisterErrorHandler` | `boolean` | `false` | Replace LÖVE's `errorhandler` to capture errors. |
80+
| `errorHandler` | `function` | `love.errorhandler` | Custom error handler to use. |
81+
| `plugins` | `table` | `{}` | List of plugin modules to load. (Support Coming soon) |
82+
| `captureScreenshot` | `boolean` | `false` | Capture screenshots on error. WARNING: This impact performance and may cause lags. Use with caution. |
8283

8384
---
8485

ROADMAP.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
## 0.4.0 – Performance & Assets
44

55
- [ ] Editable observed variables
6-
- [ ] Add optional screenshot on error
76
- [ ] Add observed type, metadata
8-
- [ ] Add take screenshot button (on logs an screenshot entry is created)
97
- [ ] SPIKE: Explore gif creation
108
- [ ] SPIKE: Asset viewer (textures, audio list)
119
- [ ] Asset viewer (textures, audio list)

docs/plugins.md

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,28 @@ local plugin = FeatherPluginManager.createPlugin(MyPlugin, "my-plugin", {
7070

7171
The FeatherPluginManager will handle the lifecycle of the plugin and call the appropriate functions. Here's a breakdown of the plugin lifecycle:
7272

73-
1. **Initialization**: The plugin is initialized with the provided options.
74-
2. **Request Handling**: The plugin handles requests from the client.
75-
3. **Update**: The plugin is updated every frame.
76-
4. **Error Handling**: The plugin handles errors that occur in the game.
77-
5. **Finish**: The plugin is finished and the game is closed.
73+
### Initialization
74+
75+
- `init(config)`: This function is called when the plugin is initialized.
76+
- `getConfig()`: This function returns the configuration for the plugin when the Feather app is initialized. Sent to the client app.
77+
78+
### Request Handling
79+
80+
- `handleRequest(request, feather)`: This function is called when a request is received. (GET)
81+
- `handleActionRequest(request, feather)`: This function is called when an action request is received. (POST)
82+
- `handleParamsUpdate(request, feather)`: This function is called when a params update request is received. (PUT)
83+
84+
### Update
85+
86+
- `update(dt, feather)`: This function is called every frame. (Called after the request handling)
87+
88+
### Error Handling
89+
90+
- `onerror(msg, feather)`: This function is called when an error occurs. Errors in this function will close the game abruptly. No frame is rendered after this function is called.
91+
92+
### finish
93+
94+
- `finish(feather)`: This function is called when the server is closed.
7895

7996
## Plugin options
8097

@@ -114,6 +131,31 @@ function MyPlugin:getConfig()
114131
end
115132
```
116133

134+
## Using Plugin Actions
135+
136+
Feather plugins can also be used to trigger actions from game code at runtime.
137+
138+
```lua
139+
local debugger = FeatherDebugger({
140+
debug = true,
141+
plugins = {
142+
FeatherPluginManager.createPlugin(ScreenshotPlugin, "screenshots", {
143+
screenshotDirectory = "screenshots",
144+
fps = 30,
145+
gifDuration = 5,
146+
}),
147+
},
148+
})
149+
150+
function love.keypressed(key)
151+
if key == "f1" then
152+
debugger:action("screenshots", "screenshot", {})
153+
elseif key == "f2" then
154+
debugger:action("screenshots", "gif", { duration = 3, fps = 60 })
155+
end
156+
end
157+
```
158+
117159
## Plugin examples
118160

119161
Here are some examples of Feather plugins:

src-lua/feather/init.lua

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ local customErrorHandler = errorhandler
4848
---@field maxTempLogs? number
4949
---@field updateInterval? number
5050
---@field defaultObservers? boolean
51+
---@field captureScreenshot? boolean
5152
---@field errorWait? number
5253
---@field autoRegisterErrorHandler? boolean
5354
---@field errorHandler? function
@@ -65,6 +66,7 @@ function Feather:init(config)
6566
self.maxTempLogs = conf.maxTempLogs or 200
6667
self.updateInterval = conf.updateInterval or 0.1
6768
self.defaultObservers = conf.defaultObservers or false
69+
self.captureScreenshot = conf.captureScreenshot or false
6870
---TODO: find a better way to ensure that the error handler is called, maybe a thread?
6971
self.errorWait = conf.errorWait or 3
7072
self.autoRegisterErrorHandler = conf.autoRegisterErrorHandler or false
@@ -175,7 +177,7 @@ function Feather:__onerror(msg, finish)
175177
end
176178

177179
local err = self:__errorTraceback(msg)
178-
self.featherLogger:log({ type = "error", str = self:__errorTraceback(msg) })
180+
self.featherLogger:log({ type = "error", str = self:__errorTraceback(msg) }, true)
179181
if self.wrapPrint then
180182
self.featherLogger.logger("[Feather] ERROR: " .. err)
181183
end
@@ -240,7 +242,11 @@ function Feather:update(dt)
240242
end
241243

242244
if request.path == "/logs" then
243-
response.data = self.featherLogger.logs
245+
local bodyData = {
246+
data = self.featherLogger.logs,
247+
screenshotEnabled = self.captureScreenshot,
248+
}
249+
response.data = bodyData
244250
self.lastDelivery = os.time()
245251
end
246252

@@ -273,6 +279,10 @@ function Feather:update(dt)
273279
if request.params.action == "clear" then
274280
self.featherLogger:clear()
275281
end
282+
283+
if request.params.action == "toggle-screenshots" then
284+
self:toggleScreenshots(not self.captureScreenshot)
285+
end
276286
end
277287

278288
if request.path ~= nil and startsWith(request.path, "/plugins") then
@@ -287,6 +297,7 @@ function Feather:update(dt)
287297

288298
client:close()
289299
end
300+
self.featherLogger:update(dt)
290301
self.pluginManager:update(dt, self)
291302
end
292303

@@ -303,6 +314,21 @@ function Feather:trace(...)
303314
self.featherLogger:print("trace", str)
304315
end
305316

317+
--- Execute an action from a plugin
318+
---@param plugin string
319+
---@param action string
320+
---@param params table
321+
function Feather:action(plugin, action, params)
322+
self.pluginManager:action(plugin, action, params, self)
323+
end
324+
325+
--- ToggleScreenshots
326+
--- @param enabled boolean
327+
function Feather:toggleScreenshots(enabled)
328+
self.captureScreenshot = enabled
329+
self.featherLogger.captureScreenshot = self.captureScreenshot
330+
end
331+
306332
---@type fun(config: FeatherConfig): Feather
307333
---@diagnostic disable-next-line: assign-type-mismatch
308334
local casted = Feather

src-lua/feather/plugin_manager.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,21 @@ function FeatherPluginManager:getConfig()
139139
return pluginsConfig
140140
end
141141

142+
function FeatherPluginManager:action(plugin, action, params, feather)
143+
local request = {
144+
params = {},
145+
path = "/plugins/" .. plugin,
146+
method = "CUSTOM",
147+
}
148+
149+
for key, value in pairs(params) do
150+
request.params[key] = value
151+
end
152+
153+
request.params["action"] = action
154+
155+
self.logger.logger("[FeatherPluginManager] Action: " .. plugin .. ":" .. action)
156+
self:handleActionRequest(request, feather)
157+
end
158+
142159
return FeatherPluginManager

src-lua/feather/plugins/logger.lua

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ local wrapWith = require(PATH .. ".utils").wrapWith
99
---@field logs FeatherLine[]
1010
---@field debug boolean
1111
---@field wrapPrint boolean
12+
---@field captureScreenshot boolean
13+
---@field lastScreenshot string|love.ByteData
1214
---@field maxTempLogs number
13-
---@field log fun(self: FeatherLogger, line: FeatherLine) Logs a line
15+
---@field log fun(self: FeatherLogger, line: FeatherLine, screenshot?: boolean) Logs a line
1416
---@field logger fun(...: any)
1517
---@field print fun(self: FeatherLogger, ...: any)
1618
---@field clear fun(self: FeatherLogger)
@@ -23,6 +25,8 @@ local FeatherLogger = Class({
2325
self.debug = config.debug
2426
self.wrapPrint = config.wrapPrint
2527
self.maxTempLogs = config.maxTempLogs
28+
self.captureScreenshot = config.captureScreenshot
29+
self.lastScreenshot = nil
2630

2731
-- Wrap print
2832
self.logger = print
@@ -44,6 +48,23 @@ function FeatherLogger:print(...)
4448
self:__countOnRepeat("output", ...)
4549
end
4650

51+
function FeatherLogger:update()
52+
if not self.captureScreenshot then
53+
return
54+
end
55+
56+
self.lastScreenshot = nil
57+
58+
love.graphics.captureScreenshot(function(img)
59+
local fileData = img:encode("png")
60+
local pngBytes = fileData:getString()
61+
62+
local b64 = love.data.encode("string", "base64", pngBytes)
63+
64+
self.lastScreenshot = b64
65+
end)
66+
end
67+
4768
--- Manages the print function internally
4869
--- @param self FeatherLogger
4970
--- @param type LogType
@@ -72,13 +93,18 @@ end
7293
---@field time? number
7394
---@field count? number
7495
---@field trace? string
75-
---@alias FeatherLog fun(self: FeatherLogger, line: FeatherLine)
96+
---@field screenshot? string|love.ByteData
97+
---@alias FeatherLog fun(self: FeatherLogger, line: FeatherLine, screenshot?: boolean)
7698
---@type FeatherLog
77-
function FeatherLogger:log(line)
99+
function FeatherLogger:log(line, screenshot)
78100
if not self.debug then
79101
return
80102
end
81103

104+
if screenshot then
105+
line.screenshot = self.lastScreenshot
106+
end
107+
82108
line.id = tostring(os.time()) .. "-" .. tostring(#self.logs + 1)
83109
line.time = os.time()
84110
line.count = 1

src-lua/main.lua

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ local FeatherDebugger = require("feather")
33
local FeatherPluginManager = require("feather.plugin_manager")
44
local HumpSignalPlugin = require("plugins.hump.signal")
55
local LuaStateMachinePlugin = require("plugins.lua-state-machine")
6-
local ScreenshotPlugin = require("feather.plugins.screenshots")
6+
local ScreenshotPlugin = require("plugins.screenshots")
77

88
local TestPlugin = require("demo.plugin")
99
local test = require("demo.another.lib")
@@ -153,6 +153,7 @@ local debugger = FeatherDebugger({
153153
defaultObservers = true,
154154
autoRegisterErrorHandler = true,
155155
baseDir = "src-lua",
156+
captureScreenshot = true,
156157
debug = true,
157158
plugins = {
158159
FeatherPluginManager.createPlugin(TestPlugin, "test", {
@@ -255,4 +256,14 @@ function love.keypressed(key)
255256

256257
test()
257258
end
259+
260+
if key == "f" then
261+
debugger:toggleScreenshots(not debugger.captureScreenshot)
262+
end
263+
264+
if key == "f1" then
265+
debugger:action("screenshots", "screenshot", {})
266+
elseif key == "f2" then
267+
debugger:action("screenshots", "gif", { duration = 3, fps = 60 })
268+
end
258269
end

0 commit comments

Comments
 (0)