-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[3.17] [Bug] didInsert / willDestroy Firing Out Of Order #18873
Comments
This is expected behavior. Destructors are scheduled to happen asynchronously, which means that their timing relative to other hooks is not guaranteed. Stepping back, destructors are really meant to work like garbage collection in a sense - they're for doing cleanup that needs to happen eventually, for things like avoiding memory leaks, etc. but they shouldn't need to happen immediately for any reason. Ideally, you would be able to schedule them to run with The More compelling use cases would also be helpful. In this case, I don't think adding/removing items from a scene is particularly compelling - it shouldn't matter what order the items are added or removed in typically. |
The use case... Phaser.io or babylon.js game engines use a canvas to draw WebGL. Let say for each level in a game that's managed by an Ember app and uses one of those engines, I need to clear the scene and setup the next scene. My idea was to transition into a route, one for each level / scene and have the route setup the level on did-insert. WebGL doesn't like the canvas being teared down and recreated for each scene in a single page app, after a while it starts generating errors about "Too many active WebGL contexts. Oldest context will be lost." which I can't solve. Im guessing this is not a good thing though, even if I am not using the old contexts any more. So instead of each level rending a canvas through including a canvas component and passing the level object as a param the recommended solution is to use one canvas and pass the level object through a service. So that's what I did, having the level register with the service and then monitoring the level in the service. Once the level completes and the level hands back to the service the app transitions to the next level and it starts over. Unfortunately as the did-insert fires before the willDestroy the new level starts setting up the canvas before the last level has teared down. This crashed the game engine. I was using this as a way of learning Ember Octane and previously I would have used willDestroyElement, infact I do use willDestroyElement a lot in my apps, as well as the previous Init / didInsertElement. I realise you want as much to be done in the templates as possible but sometimes it isn't possible, especially when working with imported packages. For instance I use bloodhound.js for an autocomplete element and everything works fine in Chrome passing in params, but in Safari on OSX I need to use a Ember.run.later() function to setup bloodhound. That seems to be a Safari bug, but one I used ember's component lifecycle to fix. Why put a game engine in Ember? I wrote an npm package to create Ember / Capacitor apps so its easy to build iOS and Android apps with Ember. https://www.npmjs.com/package/ember-cap and so I was wondering how easy it would be to build an Ember Octane / Capacitor / iOS game. |
The more detailed breakdown does make the use case more compelling, so I think we could add it to the RFC to add this functionality. We hadn't found an example of a library that would require these timing semantics, but expected at least one existed. Thank you for taking the time to write this up!
Do you think that the ability for modifiers to run code before their element is removed would satisfy your use case here? |
That would depend if the modifier ran before any did-insert modifiers ran on the new route. I can obviously add extra code into my app when programmatically transitioning and extra code on the routes to catch any other transitions so I can always tear down the canvas before the new route is entered. But I would have expected a way of doing like the way willDestroyElement worked in Ember classic. |
It would necessarily run before |
@pzuraq - I have another example if you need it. Concerning a third-party library (but the problem is not in it I believe but how browsers work). We use dashjs for playing video files. In a
Because the element is no longer in the DOM. There is literally nothing I can do to "fix" that. I have to either not dispose of the player or ignore the error. This can only be fixed by synchronous destructors. Besides, why did you name the modifier will-destroy and let it run after the element has been destroyed? |
Like I've mentioned above, I have raised this issue with core and there is general consensus that we need to add a capability to modifiers for running destruction code before the element is destroyed. This should be in the form of a new lifecycle hook, not replacing the current I'm currently very busy and not sure when I will have time to write the RFC for that, and would definitely appreciate help getting it drafted and up. If anyone has the time, let me know! |
Let me chime in here. So my concerns are also related to WebGL rendering, but could be relevant for other (DOM-less) use cases as well...
I didn't experience problems so far related to <Scene>
<Mesh mesh={{this.mesh}}/>
</Scene> The Mesh component should of course not render anything to DOM, but modify the scene tree. So in the constructor it will add a new mesh node to the scene, and on And as we are talking about DOM-less rendering in this example here, this means we would need that timing guarantee for components, not a modifier. (as you cannot use modifiers without DOM, see emberjs/rfcs#597) |
After thinking about this a bit more, I realized I might have confused things a bit... @pzuraq by saying that destructors are scheduled to happen asynchronously, do you mean that's just how the semantics of the Glimmer component manager happen to be (I believe here: https://github.com/glimmerjs/glimmer.js/blob/master/packages/%40glimmer/component/addon/-private/ember-component-manager.ts#L53), or is this always the case? Or to rephrase: can I expect that |
The current semantics of destroy should match the semantics defined in the destroyables RFC I think. |
I used to have no problems with One component has tabs, and when we change a category those tabs change. When you insert the first tab it is set to active automatically. When a tab is destroyed and it was active it will unset the active. So when changing category, which changes all the tabs used, it would unset active so then when the first insert happens the tab would be activated. This worked up until recently. I don't know if it was just luck or a slower machine so the async happened earlier. Also how you describe it working (by design) isn't intuitive at all for developers. It sounds like the only use case it was designed for was using 3rd party tools and cleaning them up. |
@robclancy like I mentioned above, we have determined that there are use cases for both eager destruction (which blocks rendering) and lazy destruction (which does not). We are very open to adding a If you're interested in getting this functionality, I'm happy to help with writing and reviewing the RFC and getting it through the process. It's on my backlog to do it myself, but I'm unsure about when I will have time to get it done. I'll also note that I think you could probably model that component's data flow without needing to use @simonihmig currently destructors run after rendering rather than mid render, which was the change that caused issues here. Timing semantics are clearly important (having caused issues already here, even though we made a change that was allowed and expected in the RFC itself), so it is very unlikely we would just change this and start running destructors after paint some time. I believe it would probably need to be rolled out via an optional feature flag, or via a new capability. The main point is this is where we would like to push destruction, because the majority of use cases for destroying don't need to be sync, and the few that do should be able to opt-in to the more expensive sync destruction, ideally. |
I am very willing and motivated to champion something here. Given the backlog of 4.0 things, I'd be happy to it after 4.0 is released. This is a bug often coming up when migrating. |
I have 2 routes, each with 1 component. Each component uses the {{did-insert}} render modifier and the willDestroy action.
Problem
When transitioning routes the did-insert action for the new component, on the new route fires before the willDestroy action of the old component on the old route.
I would expect the old component, on the route I am transitioning from, to be destroyed before the new component on the route I am transitioning to fires {{did-insert}}
Reproduction Scenario
I have created an Ember Twiddle to illustrate the problem
https://ember-twiddle.com/2412fd6f6549a66ae4e20c3cb0613858?openFiles=components.scene-2%5C.js%2Ctemplates.components.scene-2%5C.hbs
You will need to use the developer console to see the output. Which should look like the below, which happens when transitioning from Index -> Page 2
[Log] Scene 1 - didInsert
[Log] Scene 2 - didInsert
[Log] Scene 1 - willDestroy
The text was updated successfully, but these errors were encountered: