-
-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adjust iterator index when removing behaviors during tick/tock #5403
Conversation
@@ -693,13 +699,15 @@ class AScene extends AEntity { | |||
*/ | |||
tick (time, timeDelta) { | |||
var i; | |||
var it = this.behaviorIterators; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand variable name. iterator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's so common to refer to iterators as it
or iter
to the point that iterator
would harm readability IMHO. At the very least it would make the following for-loop too unwieldy, though it could as well become a while loop.
Let's first settle on the name for behaviorIterators
as that would impact this local name as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not familiar with it. It might be in your context / environment.
We always avoid acronyms (only exception we have is el
for element because of how pervasive it is). a while loop should be fine. Still not convinced with the iterator
name that seems loaded and used in other contexts but need to think about an alternative. You're probably in a better mind space than me atm to come up with a better name
@@ -58,6 +58,7 @@ class AScene extends AEntity { | |||
self.time = self.delta = 0; | |||
|
|||
self.behaviors = {tick: [], tock: []}; | |||
self.behaviorIterators = {tick: -1, tock: -1}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if iterator is the best name here. looked at the code for 5 mins without context and hard to understand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Open for suggestions. The name seemed quite fitting to me as an iterator over an array is effectively just an index and it makes it clear that it's relevant when we're iterating over the behaviours.
Perhaps behaviorCursors
works better? It places a bit more emphasis on pointing to a specific element, which is also the invariant that should be upheld when adding or removing behaviours mid-iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's are JS Iterators so not sure
This took me a bit to understand. It's subtle so worried might be hard to maintain once the context is forgotten. An alternative. Instead of removing the behavior right away we flag it as |
That was actually the first approach I had in mind, but it isn't without its drawbacks. The behaviour objects aren't really owned by the scene, so if it would flag the object itself with something like What also doesn't help is that behaviors could be added or removed during all stages of a frame (before, during or after any tick or tock from either a component or system). So not only will there be an additional post render operation performing the actual removal, both the tick and tock loops will need to skip over them and the It's that last point that actually made me not explore this option further as it would imply a behavioural change. As far as I know A-Frame makes no guarantees about the order in which ticks and tocks are called, but the current behaviour is essentially the order in which components are "added". While people shouldn't rely on it, I'm certain there are situations where people resolved inter-component dependencies by adjusting the order.
The current ad-hoc changes aren't ideal, but postponing them until the end is going to have a cascading effect. Let's say we would want to guarantee that for any given component its I don't think changing it would be worth it, unless there are more (serious) bugs we're unaware of. But it's bound to change or break quite a few apps that relied on seeing changes reflected immediately. It's relatively easy to workaround quirks related to frames where a component is added or repeatedly paused and resumed. These can have inconsistent number of invocations of |
More complex but wonder if it's worth moving the responsibility of calling the ticks from the scene back to the entity. That way we don't need to maintain a parallel structure with the ticks and can envision an API to control the order of component execution at the entity level. e.g:
I would add a A bit of history. add/RemoveBehavior was added a bit preemptively thinking there might be other logic we might want to invoke every frame. So far that hasn't happened. Thoughts? |
Moving this responsibility to the entity is going to make things harder, I'm afraid. Currently the "bookkeeping" burden only happens when adding or removing behaviours, meaning that the scene's Not to mention that we'd still have to deal with the cases where things change mid-frame. Although instead of being concentrated on the resulting
Component execution ordering would indeed be a very welcome addition. However, the order at the entity level is only part of the problem. Take for example a simple And I'd rather avoid requiring users to specify the order manually, where possible. For the most part things should "just work", so One approach is to order the registered components and execute the But let's not get too distracted. The way I see it there are three distinct "problems":
This PR only address the second point. The first point is relatively easy to workaround for a user so doesn't need any action IMO. For the third point, perhaps best to open a dedicated issue to discuss it further and allow others to weigh in as well. |
Solution I propose is simple. Scene just has to traverse the entities:
We don't have to implement the component tick order at first pass just pointing out that the new structure makes it much easier to think about and implement an API. |
Also for the |
Lastly if a component is added we should probably call its tick method but not sure because could alter the children to parent execution order so might be more clear to just skip the current frame |
This still means that the scene will have to handle on-the-fly insertions and removals on a list while it iterates over it. Not that that is inherently bad, but we'll face the exact same situation as we do now for the entity list alone. I think this is easier than I though especially if we follow a root to leaf tick order. sceneEl.children is the source of truth and copied over to a sceneEl.tickTockList at the beginning of each frame. can check for
Entity loading is indeed from the leaves to the root, but the initial And the entity list will be order sensitive, in that the parents have to precede their children. Otherwise it's possible for an entity to get its Can be done from top to bottom too by calling root entities instead. Important is the consistency that also maintained as entities are added / removed.
Actually, it's this step that probably has to consider the most cases when it comes to mid-frame changes. While going over the components it's possible that components are added or removed. If the components are iterated using an index, we have the same situation as on the scene. Won't use index. Iterate over the component object keys and check if the component still exists before calling The real tricky part is when entities are added or removed. Entities added to leaf nodes that have been 'processed' already will become leaf nodes themselves. Either we don't process them, or we do, but then the child - parent order is broken for them. And if we do process them, extra care needs to be taken to prevent calling the Removal of entities give similar problems. If the child entity is the last one (and thus responsible for calling the parent's played / added entities or components should not call tick / tock until next frame. I think makes everything more clear and predictable. also if tick executed from parent to leaves no issues with single child
It'd be nice if there's only one mechanism that calls the
Similar to the play/pause case above, the component might've missed the boat when it's added after the component tick handling. This will hold especially true when we support component execution ordering. Calling a |
To sum up. Still think it's a better design for the scene to call tick on the root entities (parent is sceneEl).
I think this makes tick / tock invocation more consistent and gives us a better playground to explore a component order API. |
@mrxz What do you think of #5403 (comment)? |
#5500 made this redundant? |
Yes, this is no longer needed. |
Description:
While looking into #5400 I noticed that following an element removal the
tick
method of theanimation
component wasn't called for that frame. The scene iterates over thetick
/tock
behaviours but doesn't handle the situation where the behaviour array is mutated mid iteration, which can result intick
/tock
behaviours to be skipped.While there are several possible solutions, letting the scene detect these cases and adjusting the loop indices seemed the most straightforward. It's worth noting that this bug is likely pretty common across A-Frame experiences, as it isn't tied to element removal per se, but any behaviour removal, meaning removing a component can trigger it. Luckily skipping the tick method for a frame isn't a problem for most components.
Changes proposed: