[SOLVED] Is there a way to detect when a Spatial's ancestor is detached?

This might sound similar to topics that have been brought up before, but I am trying to handle this from a Control so I don’t have to be specific when I want lights removed.
For this example, say I’ve got a lantern in the room, it’s physical (doesn’t matter too much). The light’s position is updated by a LightControl, and my spawner’s code gets that light added to the Root Node without issue.
But, if I were to detach the lantern, there would still be a light to deal with. On a specific case, I could tell that it was a lantern, maybe even extend a custom Control that referenced the Root Node that my removal code could trigger to cleanup.
Now, say I have a LightNode in place so I can adjust the light’s relative position. Now the lantern lacks any indication it needs cleaning up. If that lantern was attached to another node, that’s another step removed.

I thought maybe I could create a custom Control extending LightControl that holds the node the light gets assigned to (Root Node, but it could be different)

There are a few snags to this plan:

  1. A Control wouldn’t get updated after its Spatial was detached.
  2. I could make a custom Node (I’d call it ‘DethNode’) that could “announce” certain Controls (and other DethNode children) of their impending demise. But if it was the child of a normal Node, I’m back in the same boat for nothing.
  3. A Control I’ll call “DethControl” or maybe “EventControl” that my removing code can spot and pass a “detaching” message to pass down through. (It would check if the Spatial were a node, then if it had children, and if any of those children had an EventControl and pass the signal). On adding, it could even attempt to propagate upward and add an EventControl until it was just shy of the Root Node (or just hit the Root Node, it wouldn’t matter).

The “DethControl/EventControl” idea could probably work and I could implement it that way, but it feels messy to just have a string of Controls that just exist to pass a signal. Any more experienced hands have a cleaner way to tell when an ancestor’s been detached? Like a listener or something?
I was planning to use some implementation of this concept to clean up light sources, remove references from my scripting engine, or maybe announce a removal to a queue I could set up during my spawning step.

When you remove a node, check its subtree to see if it has lights and remove them too. It’s easier than any other solution discussed and won’t have any edge cases where it doesn’t work.

The issue in the general case is that a node can be in the subgraph of any number of viewports and aside from doing a mark-and-sweep every frame, it’s impossible to tell if it is still under one of those viewports.

2 Likes

Keep in mind that the scene graph (nodes, lights, geometries, and controls) is for visualization only.

The lantern should be represented by a game object outside the scene graph. That game object might keep a reference to the Light and a reference to the Geometry that visualizes the lantern. When it comes time to delete the game object, both the Light and the Geometry get removed from the scene.

I know it seems like duplication to create game objects when you already have scene-graph objects, but in the long run the benefits will be worth it.

1 Like

Oh, ok. I was worrying about duplication. I could just modify my object spawn code to create a coded game object alongside the scenegraph object (it already has to do a similar step to add the lantern to the PhysicsSpace). I just have to add a reference to the object in the scenegraph object so I can find and clean the right object(s) up when I destroy something after collision. (Say, by shooting the lantern).
This approach could probably work. Even if I imported a “level” from a scene, I’d still need to give the lamps and physical objects special treatment anyway, so I could just connect game objects at that stage.

Without going too deeply into explaining the “MVC” pattern, just note that you will be happiest if the scene graph is always reactive to changes in the game objects.

So in the context of “shooting the lantern”, the collision code hits some object and uses that to look up the game object ID tucked away on that object (say as UserData on the spatial or whatever). The game logic is called with something like “shoot this ID”. The game logic code decides what shooting the ID means… which may mean destroying the game object, may mean subtracting 10 health, whatever. Destroying the game object then causes any other visual representations of that object to be removed from their respective places.

It saves a lot of trouble later if things that are really ‘game logic’ aren’t directly modifying the scene graph.

@Demon382 for one thing i had similar problem, but anyway 99% of things should really be in ECS or some other object logic.

for this 1% i needed to make “listener” that check if spatial ancestor is root

1 Like

Having let it rattle around my head over a day, I think I might do just that. Checking the subtree. Then I can just use my extended LightControl that can handle cleanup regardless of the “game”. The spawner code and cleanup code are meant to be independent from the “games” as a shared utility. If I have a specific object and game in mind, the spawner/cleanup code can have specific cases plugged into it.

This solution is safe even if I had something edit the scene tree and snip a parent node (accidentally or intentionally) and not leave a “phantom” game object. I can slightly refine this search by checking requiresUpdates(), since that should return true if there are controls. Then just iterate for a Control implementing “CleanupControl” and tell that to get busy. I could implement CleanupControl for future stuff if really really needed.

My true project is a bit uglier because it has pieces of scene editor in there for developer adjustments. Hence an accidental removal. Or in another case, if I import an asset with lights that I want to transfer to the rootNode (like a lantern or a lamp, streetlights if I import a bigger scene). LightCleanupControls will be added as the Unpack command adds those lights to Root.
Granted, I could create an object to represent the unpack operation and point to the lights needed for removal. Thanks for the different perspective.

idk, but it sounds complicated.

if you really cant solve it via ECS - because you want it to be out of ECS.

then i suggest: make control with static manager, when control spatial is attatched, then add into static manager and add Light, when control spatial is removed from parent - well, control do not let know about it, but static manager that would work in update loop having list of attatched controls, will check if control spatial.hasAncestor(roorNode) is not null, if it is null, then simply remove light and control from manager list.

1 Like

As others have said here, using an ECS is your best bet. When your game data (in the ECS) controls the contents of the scene this isn’t an issue any longer - when something happens to cause a higher-level entity to be removed, you have a system that removes the associated node (and does any subtree cleanup needed). Your exact usecase might be slightly more complicated but you’ll probably be working with some fairly simple variant of that pattern.

When your scenegraph is derived from your data and changes with your data, these sorts of problems are much, much easier to deal with (and in many cases just sort of go away “automatically”).

1 Like

Listeners might run better. Less traversal, and only needs to trigger on a removal. At spawn, my code is in position to add a listener. During the remove events, I can just tell the listeners what node gets removed and they can just check their ancestors. Plus it’s slightly outside the scene tree. At least the removal step calling the listeners, anyway. Might not even need to be a listener so long as I have an available list of Spatials that really “need” to know that their ancestor could be removed. Sorry if I’ve been going around in circles. Thanks for the advice.

1 Like