SecondHalfGames / yakui

yakui is a declarative Rust UI library for games
Apache License 2.0
221 stars 18 forks source link

Data dependencies between `update` and `event` require dropped updates #115

Open Ralith opened 7 months ago

Ralith commented 7 months ago

Some widgets (e.g. TextBox) share ownership of data with the application by accepting a value in their Props and returning a value modified by events. Because update is responsible for both accepting new values from the application and returning event-modified values, the application cannot observe an updated value without first passing in an obsolete value, which the widget must ignore lest the updated value be clobbered.

This is a footgun for widget implementers. If the application is updating the shared state independently of the widget, it also causes those updates to be lost so long as events are triggering changes. Because events might be delivered every frame for arbitrarily long intervals (e.g. due to mouse motion), this can cause unbounded of user-visible lag. For some widgets (e.g. a camera controller that the application is manipulating to track a fast-moving target), even a single frame of lag might severely degrade user experience.

One possible solution would be to split Widget::update into two phases. The first, perhaps show, is responsible for constructing child widgets and yielding the Widget::Response. The second is responsible for accepting new Props from the application. User-facing API might look like:

let mut res = button();
if res.clicked {
    res.update("I was clicked on this frame!");
} else {
    res.update("I was not clicked on this frame");
}

Widgets which do not accept state from the application should not require the application to issue an explicit update call.

A side benefit is that this permits non-linear data flow between widgets: when multiple widgets are shown, any or all of their responses may influence any or all of their following update calls.