From d3a50cdacb9349487fde4ba9951bc214f85f5fba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 20 Jun 2025 09:05:25 +0200 Subject: [PATCH] Optimize `Actor::DestroyChildren` --- Source/Editor/Modules/SceneEditingModule.cs | 4 ++ Source/Editor/Modules/SceneModule.cs | 45 +++++++++++++++ Source/Editor/SceneGraph/SceneGraphNode.cs | 2 +- Source/Engine/Level/Actor.cpp | 63 ++++++++++++++++++++- Source/Engine/Level/Level.cpp | 8 +++ Source/Engine/Level/Level.h | 6 ++ 6 files changed, 124 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 3c7130615..11ab2fcb3 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -711,7 +711,11 @@ namespace FlaxEditor.Modules private void OnActorChildNodesDispose(ActorNode node) { + if (Selection.Count == 0) + return; + // TODO: cache if selection contains any actor child node and skip this loop if no need to iterate + // TODO: or build a hash set with selected nodes for quick O(1) checks (cached until selection changes) // Deselect child nodes for (int i = 0; i < node.ChildNodes.Count; i++) diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 56c420964..9ca92ddce 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; @@ -658,6 +659,48 @@ namespace FlaxEditor.Modules //node?.TreeNode.OnActiveChanged(); } + private void OnActorDestroyChildren(Actor actor) + { + // Instead of doing OnActorParentChanged for every child lets remove all of them at once from that actor + ActorNode node = GetActorNode(actor); + if (node != null) + { + if (Editor.SceneEditing.HasSthSelected) + { + // Clear selection if one of the removed actors is selected + var selection = new HashSet(); + foreach (var e in Editor.SceneEditing.Selection) + { + if (e is ActorNode q && q.Actor) + selection.Add(q.Actor); + } + var count = actor.ChildrenCount; + for (int i = 0; i < count; i++) + { + var child = actor.GetChild(i); + if (selection.Contains(child)) + { + Editor.SceneEditing.Deselect(); + break; + } + } + } + + // Remove all child nodes (upfront remove all nodes to run faster) + for (int i = 0; i < node.ChildNodes.Count; i++) + { + if (node.ChildNodes[i] is ActorNode child) + child.parentNode = null; + } + node.TreeNode.DisposeChildren(); + for (int i = 0; i < node.ChildNodes.Count; i++) + { + node.ChildNodes[i].Dispose(); + } + node.ChildNodes.Clear(); + } + } + /// /// Gets the actor node. /// @@ -709,6 +752,7 @@ namespace FlaxEditor.Modules Level.ActorOrderInParentChanged += OnActorOrderInParentChanged; Level.ActorNameChanged += OnActorNameChanged; Level.ActorActiveChanged += OnActorActiveChanged; + Level.ActorDestroyChildren += OnActorDestroyChildren; } /// @@ -726,6 +770,7 @@ namespace FlaxEditor.Modules Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged; Level.ActorNameChanged -= OnActorNameChanged; Level.ActorActiveChanged -= OnActorActiveChanged; + Level.ActorDestroyChildren -= OnActorDestroyChildren; // Cleanup graph Root.Dispose(); diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index b6cbdb135..20ac3a6a5 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph /// /// The parent node. /// - protected SceneGraphNode parentNode; + internal SceneGraphNode parentNode; /// /// Gets the children list. diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 02210d910..24ca6b139 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -466,12 +466,71 @@ Array Actor::GetChildren(const MClass* type) const void Actor::DestroyChildren(float timeLeft) { + if (Children.IsEmpty()) + return; PROFILE_CPU(); + + // Actors system doesn't support editing scene hierarchy from multiple threads + if (!IsInMainThread() && IsDuringPlay()) + { + LOG(Error, "Editing scene hierarchy is only allowed on a main thread."); + return; + } + + // Get all actors Array children = Children; + + // Inform Editor beforehand + Level::callActorEvent(Level::ActorEventType::OnActorDestroyChildren, this, nullptr); + + if (_scene && IsActiveInHierarchy()) + { + // Disable children + for (Actor* child : children) + { + if (child->IsActiveInHierarchy()) + { + child->OnDisableInHierarchy(); + } + } + } + + Level::ScenesLock.Lock(); + + // Remove children all at once + Children.Clear(); + _isHierarchyDirty = true; + + // Unlink children from scene hierarchy + for (Actor* child : children) + { + child->_parent = nullptr; + if (!_isActiveInHierarchy) + child->_isActive = false; // Force keep children deactivated to reduce overhead during destruction + if (_scene) + child->SetSceneInHierarchy(nullptr); + } + + Level::ScenesLock.Unlock(); + + // Inform actors about this + for (Actor* child : children) + { + child->OnParentChanged(); + } + + // Unlink children for hierarchy + for (Actor* child : children) + { + //child->EndPlay(); + + //child->SetParent(nullptr, false, false); + } + + // Delete objects const bool useGameTime = timeLeft > ZeroTolerance; for (Actor* child : children) { - child->SetParent(nullptr, false, false); child->DeleteObject(timeLeft, useGameTime); } } @@ -1280,7 +1339,6 @@ void Actor::OnActiveChanged() if (wasActiveInTree != IsActiveInHierarchy()) OnActiveInTreeChanged(); - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, this, nullptr); } @@ -1311,7 +1369,6 @@ void Actor::OnActiveInTreeChanged() void Actor::OnOrderInParentChanged() { - //if (GetScene()) Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr); } diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5516e6d53..49bef81c3 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -263,6 +263,9 @@ Delegate Level::ActorParentChanged; Delegate Level::ActorOrderInParentChanged; Delegate Level::ActorNameChanged; Delegate Level::ActorActiveChanged; +#if USE_EDITOR +Delegate Level::ActorDestroyChildren; +#endif Delegate Level::SceneSaving; Delegate Level::SceneSaved; Delegate Level::SceneSaveError; @@ -851,6 +854,11 @@ void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) case ActorEventType::OnActorActiveChanged: ActorActiveChanged(a); break; +#if USE_EDITOR + case ActorEventType::OnActorDestroyChildren: + ActorDestroyChildren(a); + break; +#endif } } diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 597bc0a87..484ba35b8 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -549,7 +549,13 @@ private: OnActorOrderInParentChanged = 3, OnActorNameChanged = 4, OnActorActiveChanged = 5, +#if USE_EDITOR + OnActorDestroyChildren = 6, +#endif }; static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b); +#if USE_EDITOR + API_EVENT(Internal) static Delegate ActorDestroyChildren; +#endif };