fix initialization errors using driver.love

Changes inside on.initialize are minefields. Until now, if you made a
mistake when modifying on.initialize, you could end up in a situation
where the app would fail irrecoverably on the next startup. You'd have
to go dig up a text editor to fix it.

After this commit, errors in on.initialize wait for commands from
driver.love just like any other error.

Recovering from errors during initialization is a little different than
normal. I don't know how much of initialization completed successfully,
so I redo all of it.

I think this should be safe; the sorts of things we want to do on
startup tend to be idempotent just like the sorts of things we do within
an event loop with our existing error handling.

Things are still not ideal. Initialization by definition happens only
when the app starts up. When you make changes to it, you won't find out
about errors until you restart the app[1], which can be much later and a
big context switch. But at least you'll be able to fix it in the usual
way. Slightly more seamless[2].

One glitch to note: at least on Linux, an app with an initialization
error feels "sticky". I can't seem to switch focus away from it using
Alt-tab. Hitting F4 on the driver also jarringly brings the client app
back in focus when there was an initialization error. But the mouse does
work consistently. This feels similar to the issues I find when an app
goes unresponsive sometimes. The window manager really wants me to
respond to the dialog that it's unresponsive.

Still, feels like an improvement.

[1] I really need to provide that driver command to restart the app! But
there's no room in the menus! I really need a first-class command
palette like pensieve.love has!

[2] https://lobste.rs/s/idi1wt/open_source_vs_ux
This commit is contained in:
Kartik K. Agaram 2023-11-17 08:31:41 -08:00
parent ef5e7e0556
commit a428068d3e
4 changed files with 61 additions and 12 deletions

View File

@ -62,6 +62,35 @@ Initializing settings:
```
Hit F4. The error disappears.
Driver can connect to app on errors in on.initialize.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define on.initialize with a run-time error:
```
on.initialize = function()
foo = bar+1
end
```
Hit F4.
Quit the client app and restart. App shows an error.
Edit `on.initialize` in the driver and remove the error:
```
on.initialize = function()
end
```
Hit F4. The error disappears from the app and driver.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, define on.initialize with a run-time error:
```
on.initialize = function()
foo = bar+1
end
```
Hit F4.
Quit the client app and restart. App shows an error.
Hit F4 again in the driver (without fixing the error).
The client app continues to show the error.
* clone this repo to a new client app, clear its save dir, run it, run the
driver, add a definition containing invalid Lua:
```

22
app.lua
View File

@ -36,15 +36,17 @@ function love.run()
end
end
-- Stash current state of App for tests
App_for_tests = {}
for k,v in pairs(App) do
App_for_tests[k] = v
end
-- there's one nested table
App_for_tests.screen = {}
for k,v in pairs(App.screen) do
App_for_tests.screen[k] = v
-- Stash initial state of App (right after loading files) for tests
if App_for_tests == nil then
App_for_tests = {}
for k,v in pairs(App) do
App_for_tests[k] = v
end
-- there's one nested table
App_for_tests.screen = {}
for k,v in pairs(App.screen) do
App_for_tests.screen[k] = v
end
end
-- Mutate App for the real app
-- disable test methods
@ -140,7 +142,7 @@ function love.run()
end
App.initialize_globals()
App.initialize(love.arg.parseGameArguments(arg), arg)
xpcall(function() App.initialize(love.arg.parseGameArguments(arg), arg) end, live.handle_initialization_error)
love.timer.step()
local dt = 0

View File

@ -33,7 +33,9 @@ on = {}
-- === on startup, load all files with numeric prefix
function live.load()
live.freeze_all_existing_definitions()
if Live.frozen_definitions == nil then -- a second run due to initialization errors will contain definitions we don't want to freeze
live.freeze_all_existing_definitions()
end
-- version control
Live.filenames_to_load = {} -- filenames in order of numeric prefix
@ -80,6 +82,11 @@ function live.update(dt)
local possibly_mutated = live.run(buf)
if possibly_mutated then
Mode = 'run'
if Redo_initialization then
Redo_initialization = nil
love.run() -- won't actually replace the event loop;
-- we're just running it for its initialization side-effects
end
end
if on.code_change then on.code_change() end
end
@ -392,6 +399,11 @@ function live.handle_error(err)
print(Error_message)
end
function live.handle_initialization_error(err)
Redo_initialization = true
live.handle_error(err)
end
-- I tend to read code from files myself (say using love.filesystem calls)
-- rather than offload that to load().
-- Functions compiled in this manner have ugly filenames of the form [string "filename"]

View File

@ -237,7 +237,13 @@ end
function App.keyreleased(key, scancode)
if Mode == 'error' then
Mode = 'run'
if Redo_initialization then
Redo_initialization = nil
love.run() -- won't actually replace the event loop;
-- we're just running it for its initialization side-effects
else
Mode = 'run'
end
return
end
-- ignore events for some time after window in focus (mostly alt-tab)