Replies: 2 comments 6 replies
-
It would be cool to have something like a |
Beta Was this translation helpful? Give feedback.
-
I've done a decent amount of professional webdev and I am generally sold on "reactive" ui programing (I did a reasonable amount of React and Angular 2-X dev at Microsoft). I also think things like Jetpack Compose and Elm are worth learning from. Theres also "immediate mode" UI which has a lot of similarities with "reactive" programming (although the implementation details end up being very different). Given that we probably want "Bevy ECS components" to be our primary UI datastructure (to integrate nicely with the Bevy app model, take advantage of built in change detection, and work with Bevy Scenes), that means we've already broken ourselves into "retained state jail". It would be hard to make "immediate mode ui" feel integrated in that context. That means we already have a lot of parallels with the web world and it makes sense to learn from it. I agree that JQuery-style "imperative updates" won't be ideal for complex UI apps. Resolving this problem in a way that works "harmoniously" with the "Bevy App Model" is going to be the hardest problem we face when designing Bevy UI. I have some ideas here (hierarchical Bevy system executors, adding "observables" to bevy ECS, "ecs systems as ui components", etc), but I'll need to do some experimentation to see if they're actually viable. I won't start on that until I've wrapped up my renderer work, but others are free to start experimenting with "reactive bevy ui" designs if they feel inclined. If you find a design you believe in, feel free to create an RFC. Also check out the existing ui rfcs to see what other people are designing. |
Beta Was this translation helpful? Give feedback.
-
There's been some chat in the Discord about how to direct the API for Bevy's UI system. I'm a React dev by day and I wanted to post about some high-level UI dev concepts, and discuss Bevy's take on these things. I'll also post some links to articles and prior art for webdev for further reading.
UI is a pure function of state
For any given app state, we can describe a UI as a pure function that maps the state to an interface. For example:
This React function component maps the application state to a header that displays the count. We can project this into a wide variety of different interfaces. We could add buttons to increment and decrement the count, a text box to type the count in directly, or something else.
This is a pure function, one that only operates on its arguments and without side effects. This makes sense: we always want the UI to reflect the app state, and a pure function is a kind of reflection.
State updates are data
Events that happen in our application are values describing something that happened. Suppose we add a button to increment the Counter:
Since we're not mutating the DOM directly, this event needs to signal that something happened. In Elm or Redux, we would create a value (a Message or Action, respectively) that describes what happened in terms of application logic, and pass that to a dispatcher to kick off the state updates:
This value is passed into a pure 'reducer' function that returns the next state. This has a few advantages over direct mutation:
Imperative view updates are hard
Before React, front-end web dev often took on a kind of imperative form. Here's a snippet of jQuery code:
This code fetches a DOM element from the document and updates its html content. With jQuery (or pure js), the developer must ensure that the UI reflects state at all times by manually mutating the document each time state changes. This brings problems:
#counter-header
element in the DOM. If the output of$( "#counter-header" )
was null, the application would crash when we tried to invoke.html
. (Or in Rust, as an Option type, we'd have to contend with the None case somehow.)#counter-header
. To reuse this, we'd have to parameterize the element id (and at that point we may as well just put the mutation inline).#counter-header
, and any other implicit state this function relies on.By contrast, declarative frameworks like Elm and React will diff the output of your functions and reconcile the DOM on your behalf. This eliminates the above classes of problems! This is a huge win in development ergonomics over imperative development like IMGUI, jQuery, or Unity's UiElements (and if you don't believe me, take it from John Carmack!)
Declarative view updates need a backend
Any declarative solution needs a framework to do mutations. React's reconciler does a lot of work to diff and update the DOM to reflect state. The React team's work on these internal algorithms is an interesting topic unto itself, and React's design document includes ideas about scheduling updates:
We can think of a backend like this as a kind of programming runtime, built with its own tradeoffs in mind.
Implications
This post is getting long! To wrap things up, here are a few considerations:
O(view size)
, and notO(model size)
. I think we can do something similar to support a declarative UI without expensive operations on large numbers of entities.Thanks for reading! Here are some resources that I've linked above for additional reading:
Beta Was this translation helpful? Give feedback.
All reactions