Change default UpdateOrder for MemberEditors on Inspectors to INT_MAX to ensure they display the correct value at the end of world refresh
Is your feature request related to a problem? Please describe.
The various MemberEditor components run their text updating code in their OnChanges() hook. This means that, during the Changes stage of World.Refresh(), anything that happens after the MemberEditor updating on the same world refresh will still see the "old" value during any given frame, subtly lying to the user about the true value of a field at any given frame.
Currently, the various MemberEditors on an inspector have a default UpdateOrder of 0. If anything has an UpdateOrder greater than 0, or just so happens to be placed later in the toApplyChanges bucket of the world's UpdateManager, the text display will lag behind by a frame of the true field's value.
This just caused me about 2 days of confusion about a setup that, in my mind, shouldn't have been possible in the first place, but only after showing it to other people and having one bring up the idea of the inspector lying about the value (thanks ohzee!), I realized it was the MemberEditor running order causing it in the first place (more info about this specific setup in the additional context section).
Describe the solution you'd like
Set the default UpdateOrder for MemberEditors on all inspectors to INT_MAX (2147483647). This basically ensures that it will run last during the Changes stage of the world, and as such almost always show the correct value.
This shouldn't have any adverse changes, as nobody will be writing to the string field on the inspector linked to the PME, other than the case of directly editing the value on the inspector. This 1 frame delay doesn't really matter for that, since it's user interaction controlling the value and thus not exactly imperative to know the value between frames.
Describe alternatives you've considered
A completely separate world refresh order specifically for updating the visuals of these values. That seems a little overkill, though.
Additional Context
This is the setup that had me utterly baffled. My intuition was that, since Changes strictly happens after Updates, any kind of UpdateOrder on the ValueCopy<double> shouldn't affect the value of the 2nd ValueField at all, and yet it seemingly was.
Alas, I was misled by the inspector. The field does have the correct value at the end of the refresh, but the inspector simply updates the text field for the value before the value gets updating during the refresh, so it appears as a frame behind. This confused me for two days before ohzee suggested that it may be the inspector rather than the underlying value.
If I change the UpdateOrder of the PrimitiveMemberEditor for the 2nd ValueField, then the "pivot" that determines whether the value appears a frame behind or not changes to that number. Here, it's currently 0.
Displaying the correct value:
Displaying the value 1 frame behind:
Requesters
yosh
We could potentially make this change, but there's two caveats with this that I see:
- Values can technically change at any point during the update cycle, even after "OnChanges" update. So while this would make a few more values show 1 frame fresher value, it wouldn't cover 100 % of the cases.
- Inspectors are for human interaction. At worst, the value you see is off by 1 frame. That's way too short for a human to register when just looking at the values, so in a away this is a solving a problem that shouldn't really be a problem
If you're doing any debugging that requires exact per-frame value diagnosis, I'd probably recommend dumping them to something.
Values can technically change at any point during the update cycle, even after "OnChanges" update. So while this would make a few more values show 1 frame fresher value, it wouldn't cover 100 % of the cases.
Yeah, I'm aware of this. Most values will be done by the end of this refresh stage, though, from what I can see (at least, the only stages that jump out at me at a surface-level glance are Destructions, ProtoFluxDiscreteChangesPost, MovedSlots, and UserPose). It would cover most time-dependent component/protoflux chains that users would make, which is at least the mystery example that left me chasing my tail for 2 days trying to work my head around why it appeared a frame behind.
Inspectors are for human interaction. At worst, the value you see is off by 1 frame. That's way too short for a human to register when just looking at the values, so in a away this is a solving a problem that shouldn't really be a problem
If you're doing any debugging that requires exact per-frame value diagnosis, I'd probably recommend dumping them to something.
That's a fair assessment, but I have two reservations with it:
- While I could in fact dump this chain via protoflux or something, a quick way that I've always checked if a value might be a frame behind is to take a screenshot and see the value. It's a convenience thing that saves me ~20 seconds compared to making a protoflux chain to write to displays to see the value.
- I believe that anything that helps end-user UX in any fashion is generally a net positive, even if it might go against what the developer intended the usage of a system. A user might come along and, unknowing of this quirk or the idealized "intended use" of an inspector being strictly for human interaction, also use screenshotting for checking per-frame potential value delays, assumping that an inspector is an accurate representation of the value on any given frame. If they didn't think about how PME might be affecting this (like I didn't!), they might also rag on for a while wondering why this happens, potentially tunnel-visioning (like I did) enough to not think about dumping with flux or whatever. While yes, this doesn't entirely solve the issue, it will help tremendously for these cases.
In the meantime, I did make a simple mod that sets the UpdateOrder to INT_MAX for me in MemberEditor.Setup(), which to my knowledge is only called when building inspector UI, so having this in the base game isn't too imperative for me personally anymore. I'm not sure about the other upvoters, though.
My issue with pushing things to do what they're not intended for is that it inevitably pushes it into corner.
There's a design principle I tend to adhere to - make things fail fast when they're not doing what they're supposed to - so I'm generally adverse to changes that legitimize uses that go against the purpose of the feature.
My concern is that if we make it more usable for something that it's not designed for, it'll take longer for people to realize that - and become more dependent on it. And then demand we'll fix the values being a frame behind for the remainder cases too - which we won't be able to do without much hackery, because they were not designed for that - we essentially push it into a corner that might be hard to get out of.
I think this one is small enough that we could do it - but this is also where my head alarm bells ring, because a number of times I ended up making such decision, I ended up regretting it, when things came to head. So I'm a bit conflicted on this.
My issue with pushing things to do what they're not intended for is that it inevitably pushes it into corner. ... My concern is that if we make it more usable for something that it's not designed for, it'll take longer for people to realize that - and become more dependent on it. And then demand we'll fix the values being a frame behind for the remainder cases too - which we won't be able to do without much hackery, because they were not designed for that - we essentially push it into a corner that might be hard to get out of.
That's fair--I get that, given the history of the engine and how users treated features in the past (stares at SimpleAvatarProtection).
I think the line I personally draw is "adding or altering the behavior of the components themselves". Past that line starts introducing tech debt and pretense for other modifications of behavior to take place. An edit like this is only done during building of UI--no fundamental behaviors of any related components of FrooxEngine are modified (world update cycle, membereditor component I/O/Variables, etc.), so I personally don't consider it to cross that line into scary territory.
You are not me though, so it's your call at the end of the day. I think it'd be a net benefit.
Hmm... there's actually another component to this that I forgot about - UIX is done by mesh updates. Those are semi-asynchronous and might not complete before the frame is rendered. So there's a possibility that even if the actual UIX components get updated that frame - the mesh you see might not make it!
Even worse, this can be inconsistent depending on the timing - some frames you get fresh value from that frame, some frames you get the old one.