Skip to content

Conversation

lawnjelly
Copy link
Member

@lawnjelly lawnjelly commented Jun 13, 2025

Node3D maintains its own list of Node3D-only children (separate from the Node), in order to (in theory) make child iteration faster for only node3d children. The same happens with CanvasItem and CanvasItem children.

The problem is that these lists are stored as a linked list, and there is no reason to require linked list. Linked lists are bad for cache.

Forward port of #107480

Benchmark

(See 3.x PR, but gains should be similar in 4.x)
before: 157 fps
after: 280 fps

The benchmark creates a large number of Node3D children to the root node, then rotates the root. This stresses the _propagate_transform_changed() which utilizes the children list.

Notes

  • This is actually a no brainer, I also tested removing the Node3D children list entirely and utilizing the Node children list instead with fast casting, but this PR is still faster (because of no need for cast checks).
  • Some NULL checks in the original were redundant, I don't think these pointers can be NULL given the code here.
  • I may end up being able to use this list for SceneTreeFTI now it is faster, instead of Node children. Not 100% on this yet, but worth looking into.
  • Have done the same for CanvasItem, the same applies.
  • I've done testing on startup / shutdown times, and as expected there's no significant difference.

Additional

These lists are similar in 3.x and 4.x. Unlike the Node children list, they are unordered, so no need to change when rearranging children, only when entering / exiting tree, and removing items is O(1) rather than O(N).

Instead of relying on the ability of linked list to remove an element fast, we use remove_at_unordered to remove elements in O(1). With linear lists, and keeping an ID of which child we are in the parent list, we have to rejig the child that is moved during remove_at_unordered(), and reassign its child ID. That should be the only thing required to keep the IDs in sync.

@lawnjelly lawnjelly requested review from a team as code owners June 13, 2025 09:20
@AThousandShips AThousandShips changed the title Node3D and CanvasItem children change to LocalVector Use LocalVector for Node3D and CanvasItem children Jun 13, 2025
@AThousandShips AThousandShips added this to the 4.x milestone Jun 13, 2025
@mihe
Copy link
Contributor

mihe commented Jun 13, 2025

Instead of relying on the ability of linked list to remove an element fast, we use remove_at_unordered to remove elements in O(1). With linear lists, and keeping an ID of which child we are in the parent list, we have to rejig the child that is moved during remove_at_unordered(), and reassign its child ID. That should be the only thing required to keep the IDs in sync.

I might be missing something here, but isn't this a pretty serious change? Moving nodes around changes their processing order, and in the case of 2D their drawing order.

@lawnjelly
Copy link
Member Author

lawnjelly commented Jun 13, 2025

I might be missing something here, but isn't this a pretty serious change? Moving nodes around changes their processing order, and in the case of 2D their drawing order.

You are missing something. Draw / processing order is determined by Node children, not CanvasItem children.

It's a peculiar arrangement. The Spatial children and CanvasItem children are a subset of the actual Node children, and they are unordered. They are used for propagating transforms and a few other flags.

There's only about 5 or so uses of the Spatial and CanvasItem children, and they aren't exposed - they are purely a "difficult to notice" internal optimization that was introduced when Godot went open source. I may be able to use them for SceneTreeFTI too.

@lawnjelly lawnjelly force-pushed the localvector_children branch from d27ba23 to ad82425 Compare June 18, 2025 04:59
Copy link
Member

@Ivorforce Ivorforce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look great to me!
To repeat what I said in #107480: LocalVector should be a straight upgrade List for this situation, with no regressions expected.
Just needs final adjustments commented on #107480.

@Ivorforce Ivorforce modified the milestones: 4.x, 4.6 Jun 18, 2025
@lawnjelly lawnjelly force-pushed the localvector_children branch from ad82425 to af2b9be Compare June 18, 2025 12:32
@Repiteo Repiteo merged commit 1c056c7 into godotengine:master Sep 18, 2025
20 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented Sep 18, 2025

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants