Optimize Actor::DestroyChildren

This commit is contained in:
Wojtek Figat
2025-06-20 09:05:25 +02:00
parent 2e10d776e9
commit d3a50cdacb
6 changed files with 124 additions and 4 deletions

View File

@@ -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++)

View File

@@ -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<Actor>();
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();
}
}
/// <summary>
/// Gets the actor node.
/// </summary>
@@ -709,6 +752,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged += OnActorOrderInParentChanged;
Level.ActorNameChanged += OnActorNameChanged;
Level.ActorActiveChanged += OnActorActiveChanged;
Level.ActorDestroyChildren += OnActorDestroyChildren;
}
/// <inheritdoc />
@@ -726,6 +770,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged;
Level.ActorNameChanged -= OnActorNameChanged;
Level.ActorActiveChanged -= OnActorActiveChanged;
Level.ActorDestroyChildren -= OnActorDestroyChildren;
// Cleanup graph
Root.Dispose();

View File

@@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// The parent node.
/// </summary>
protected SceneGraphNode parentNode;
internal SceneGraphNode parentNode;
/// <summary>
/// Gets the children list.

View File

@@ -466,12 +466,71 @@ Array<Actor*> 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<Actor*> 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);
}

View File

@@ -263,6 +263,9 @@ Delegate<Actor*, Actor*> Level::ActorParentChanged;
Delegate<Actor*> Level::ActorOrderInParentChanged;
Delegate<Actor*> Level::ActorNameChanged;
Delegate<Actor*> Level::ActorActiveChanged;
#if USE_EDITOR
Delegate<Actor*> Level::ActorDestroyChildren;
#endif
Delegate<Scene*, const Guid&> Level::SceneSaving;
Delegate<Scene*, const Guid&> Level::SceneSaved;
Delegate<Scene*, const Guid&> 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
}
}

View File

@@ -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<Actor*> ActorDestroyChildren;
#endif
};