pgaskin / NickelMenu

The easiest way to launch scripts, change settings, and run actions on Kobo e-readers.
https://pgaskin.net/NickelMenu
MIT License
556 stars 28 forks source link

Asynchronous action results #105

Open pgaskin opened 3 years ago

pgaskin commented 3 years ago

This would allow implementing asynchronous action results, e.g. confirmation dialogs (note that the current ok dialog result doesn't wait).

A few potential issues:

Note that this does not include implementing async actions since that has more implications and would result in quite a bit more complexity (for execution, checking the current state, and handling abortions) and potentially confusing behaviour.

pgaskin commented 3 years ago

This could be implemented by making void nm_menu_action_do(nm_menu_item_t *it, nm_argtransform_t argtransform, void *argtransform_data) into void nm_menu_action_do_async(nm_menu_item_t *it, nm_argtransform_t argtransform, void *argtransform_data, struct {int cfgver; nm_menu_action_t *cur; nm_action_result_t *res} *async_state):

  1. Check async_state.
    • If null, initialize it to the current config version, the current timestamp (for identifying log messages), and the first nm_menu_action_t.
    • Otherwise:
      • Check async_state.cfgver against the current config version. If it's different, log an error and return silently since the config pointers won't be valid anymore.
      • Check if the device is currently in mass storage mode. If so, log an error and return silently.
      • Check if the lock screen is showing. If so, log an error and return silently.
  2. Check async_state.res.
    • If it is null, execute the action and set it to the result.
    • If it is not null:
      • If it is synchronous, handle it, free it, then continue the loop.
      • Otherwise, start it and pass the slot a lambda which updates async_state.res and calls nm_action_do_async again, free it, then return.
  3. While async_state.cur.next (we could call nm_menu_action_do_async recursively, but is more efficient, easier to debug, and doesn't have a risk of stack overflows):
    1. Set the async_state.cur to the action.
    2. Execute the action and set async_state.res to the result.
    3. Check async_state.res:
      • If it is synchronous, handle it, free it, then continue the loop.
      • Otherwise, start it and pass the slot a lambda which updates async_state.res and calls nm_action_do_async again, free it, then return.
  4. Log that the action chain completed.

Actions results which do not use Qt signals are out of scope (since then we can't guarantee actions are running in the correct thread).

This should only require changes in nickelmenu.cc; the actions themselves should remain the same. In addition, the code path would remain almost identical for chains which do not use async results, significantly reducing the likelihood and impact of regressions.