Skip to content

Conversation

walkie
Copy link
Contributor

@walkie walkie commented Nov 23, 2021

This PR adds a bunch of new functionality to the runtime interface related to events and transitions. It also includes several refactorings, both in the runtime interface and in the generated code, which supports these new features while also simplifying the interface and setting up future work.

The rest of this post describes these changes in a bit more detail.

New callback interface for events

Frame events include interface method calls and state enter/exit events--basically, anything that can be handled in Frame by defining a corresponding handler associated with a state.

You can now register callbacks to be notified of Frame events. Callbacks can be registered via two methods, both associated with the EventMonitor obtainable via the runtime interface from a state machine instance:

  • add_event_sent_callback -- Adds a callback that will be invoked whenever an event is sent.
  • add_event_handled_callback -- Adds a callback that will be invoked whenever an event has been completely handled.

The sent callbacks will be invoked for events in the more intuitive order. For example, if calling method m triggers a transition from state A to state B, then the callbacks will be invoked for events "m" (the method call), "A:<" (old state exit event), and "B:>" (new state enter event), in that order. However, because the handler for m will not have terminated yet until the transition is complete, the event "m" received by the callback will not yet contain the return value of m (since it hasn't been computed yet).

The handled callbacks will be invoked when the handlers for each event have completed. So, for the scenario above, the callbacks will be invoked in the order "A:<", "B:>", "m", since the handler for m doesn't terminate until after the transition. However, in this case, the event "m" received by the callback will contain the return value.

In all cases, the events received by the callback are references wrapped in Rc<..>, so they can be freely saved. The return value for an event received via a sent callback will be updated when the handler terminates, so if this event is saved, it's return value will be accessible later.

I suspect that usually clients will want to be notified of events in the more intuitive order provided by add_event_sent_callback. However, add_event_handled_callback is provided in case the return value is needed at the time the callback is invoked.

Configurable event and transition history

The runtime interface (again via the EventMonitor) now maintains a history of recent events and transitions. The histories can be obtained via get_event_history and get_transition_history.

The length of history maintained can be configured via set_event_history_capacity and set_transition_history_capacity, which take values of type Option<usize>. A None value indicates an unbounded history, while Some(n) indicates a history bounded to size n. The history feature can be disabled by setting the capacity to Some(0).

By default, the history capacities are set to Some(0) (disabled) for the event history and Some(1) (maintain the last transition only) for the transition history. This minimizes memory impact if these features are not needed.

The default values can be configured via the new Framec configuration options codegen.rust.runtime.event_history_capacity and codegen.rust.runtime.transition_history_capacity, configurable via YAML or the attribute system, as usual. As with the built-in defaults, these can be overridden at runtime using the set_ methods listed above.

Events in the event history are listed in the order the events were sent. The history stores Rc<..> references to each event, which will be updated as the event is handled. In particular, the return value of an event (if any) will be set once the event has been fully handled.

Fixes #61
Fixes #62

Runtime interface refactoring

Several of the *Info types that were previously traits are now structs, standardizing and simplifying the interface.

The main reason these were previously traits is that there was a cyclic reference, which causes problems for the struct-based representation, but not for the trait-based one. This is now resolved via a OnceCell.

The drawback of this solution is that the once_cell crate must now be included whenever the runtime interface is used. This must appear as a dependency in the host project since we're generating code that uses the crate, not just using it in the frame_runtime library itself. I decided that the better interface was worth this cost...

Perhaps more significantly, almost all Environment values (of which there are several) are now returned as Rc<dyn Environment> rather than &dyn Environment. This enables significant improvements in the generated code, and also enables saving references to environments, as needed by the history mechanisms described above.

Framec's Rust backend refactoring

The ability to save a history of references to events and states required refactoring of the generated Rust code. Mostly this refactoring involved shuffling where various Rc<..> and RefCell<..> types occur.

There are multiple side benefits of these refactorings, however, aside from supporting the history feature.

First and most importantly, it makes working with state contexts much more flexible. This should make it significantly easier to resolve #59, which is our most critical outstanding issue. (And resolving that would provide a workaround for #60.)

Second, it enabled removing explicit lifetime annotations from event and argument structs, which were previously needed when runtime_support was enabled. These complicated the code generator and were hard to reason about.

Third, it should make it trivial to extend the callback interface to include actions in the future.

walkie added 29 commits November 5, 2021 15:35
This is a significant refactor of the runtime interface. It makes several code quality and consistency improvements, and sets up work on monitoring more kinds of events and storing event and transition history.

All tests that depend on the runtime system are disabled since the code generator has not been updated to use the new interface.
This is a mostly complete implementation of the codegen for the refactored state context and event representations, needed to support the extended runtime interface. Most of the codegen for the new runtime interface itself is also done, but there are likely still gaps there.

Unfortunately, I realized that my refactoring of how domain variables are represented is going to break almost all existing code and make the interface for simple cases worse... so I'm going to back those changes out, which is the reason for this WIP commit.

Essentially all end-to-end tests are disabled in this commit due to the problem described above and since there are likely other issues still lurking in the new codegen stuff.
More work on the codegen for the new state context/event representation. All tests with runtime_support disabled now pass.
More work on codegen to get the runtime interface working again for the basic tests.
Everything else is assuming single-threaded, so this is overly restrictive and prevents useful applications, e.g. saving events which are wrapped in Rc<..>.
This makes the history reflect the more natural event ordering. Since the history stores a reference to the event, the return value will be set in the history when the event is fully handled.
Also adds getters for the history capacities.
@walkie walkie merged commit 4f0fabe into main Nov 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant