Replies: 1 comment 3 replies
-
I'm not sure I understand this point. Commands can also add or remove components, they just won't do this immediately. Is that the problem here? |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This is a longer response to @cart 's recent question on the discord about the pros and cons of fine-grained vs. coarse grained reactive frameworks. To that end, I'm going to give an example of an actual problem I ran into with fine-grained.
In Quill, there's a method called
Element::style_dyn()
, which is used to do dynamic styling:This is a simplified example, most dynamic styles are considerably more complex than this. The
style_dyn
function accepts two parameters: a closure, and a dependency value (in this case,hover
). The purpose of the dependency is twofold: first, it determines how often the style is updated: it only updates when the dependency changes. Secondly, the dependency is passed to the closure, which uses that value to decide what changes to make to the styles.The builder argument is an
&mut StyleBuilder
.StyleBuilder
is a convenience type which provides a fluent interface for setting Bevy UI styles, similar to the kinds of interfaces offered by competing frameworks. Internally,StyleBuilder
contains anEntityWorldMut
which allows the builder to access and modify all of the various components that are related to styling. Note that this has to be anEntityWorldMut
, it can't be a command or deferred variation because some style operations add or remove components (like Image, BackgroundColor, and so on).Note that in this example, the reactivity happens outside the closure: specifically, the call to
is_hovered()
adds the current hover state as a dependency to the tracking scope for the current view template. The resulting value is passed in to the closure from the outside, the closure itself does not have any access to the world other than through the builder. This is the essence of coarse-grained: there's only a single reactive context for each template, and any finer-grained operations, like setting a style, are just memoized functions that happen within that reaction.Now let's look at what this would look like in a fine-grained framework. What you would want is something that looks like this:
As you can see, the
is_hovered()
call has been moved inside the closure, because the closure has its own independent reactive context, separate from the outer scope. There's no longer a dependency argument, because we no longer depend on the reactivity of the outer scope. In fact, the outer scope isn't even reactive at all! The outer scope is executed only once, and only the closures defined within it are ever executed more than once. So each closure has to be responsible for accessing the reactive data sources.However, the problem is that you can't actually write this code: it won't compile. The reason is due to a borrowing conflict: the
cx
argument must contain a reference toWorld
, because there's no way to know in advance which resources or components it will want to access - theoretically you can access anything viacx.use_resource()
orcx.use_component()
. At the same time, the builder needs to hold a mutableEntityWorldMut
. Well, you can't do both at the same time, there's just no way around it.DeferredWorld
won't help you here, nor willCommands
, nor dependency injection / one-shot systems.The only thing you could feasibly do is break up the effect into two separate functions - one which queries the
is_hover
state, and which pipes its output to the second one, which performs the mutation. This means that theEntityWorldMut
and theStyleBuilder
don't have to exist at the same time. This is certainly doable - both Solid and MobX have similar primitives, such ascreateReaction(tracking, mutation)
where the first function is reactive, and the second function is not, but only runs when the first function reacts. However, this bifurcation results in an API that is somewhat confusing for beginners - less ergonomic.Beta Was this translation helpful? Give feedback.
All reactions