diff --git a/Nerfed.Editor/Editor/EditorGui.cs b/Nerfed.Editor/Editor/EditorGui.cs index 9c9cce0..657652c 100644 --- a/Nerfed.Editor/Editor/EditorGui.cs +++ b/Nerfed.Editor/Editor/EditorGui.cs @@ -69,6 +69,7 @@ private static void HandleOnGui() foreach (MoonTools.ECS.System system in Program.editorSystems) { + using ProfilerScope scope = new(system.GetType().Name); system.Update(Engine.Timestep); } } diff --git a/Nerfed.Editor/Program.cs b/Nerfed.Editor/Program.cs index 676c99e..accbefc 100644 --- a/Nerfed.Editor/Program.cs +++ b/Nerfed.Editor/Program.cs @@ -28,7 +28,6 @@ private static void HandleOnInitialize() { //systems.Add(new ParentSystem(world)); systems.Add(new LocalToWorldSystem(world)); - systems.Add(new LocalToWorldThreadedSystem(world)); editorSystems.Add(new EditorProfilerWindow(world)); editorSystems.Add(new EditorHierarchyWindow(world)); @@ -49,13 +48,13 @@ private static void HandleOnInitialize() Entity ent5 = world.CreateBaseEntity("entity5"); - for (int i = 0; i < 10; i++) + for (int i = 0; i < 256; i++) { Entity newEnt = world.CreateBaseEntity(); world.Set(newEnt, new LocalTransform(new Vector3(i, i, i), Quaternion.Identity, Vector3.One)); Entity parent = newEnt; - for (int j = 0; j < 10; j++) { + for (int j = 0; j < 2; j++) { Entity newChildEnt = world.CreateEntity(); world.Set(newChildEnt, new LocalTransform(new Vector3(j, j, j), Quaternion.Identity, Vector3.One)); Transform.SetParent(world, newChildEnt, parent); @@ -72,10 +71,8 @@ private static void HandleOnUpdate() { foreach (MoonTools.ECS.System system in systems) { - using (new ProfilerScope(system.GetType().Name)) - { - system.Update(Engine.Timestep); - } + using ProfilerScope scope = new(system.GetType().Name); + system.Update(Engine.Timestep); } using (new ProfilerScope("EditorGui.Update")) @@ -94,7 +91,10 @@ private static void HandleOnUpdate() private static void HandleOnRender() { - EditorGui.Render(); + using (new ProfilerScope("EditorGui.Render")) + { + EditorGui.Render(); + } } private static void HandleOnQuit() diff --git a/Nerfed.Editor/Systems/EditorProfilerWindow.cs b/Nerfed.Editor/Systems/EditorProfilerWindow.cs index ec67b54..70c20d0 100644 --- a/Nerfed.Editor/Systems/EditorProfilerWindow.cs +++ b/Nerfed.Editor/Systems/EditorProfilerWindow.cs @@ -78,34 +78,37 @@ public override void Update(TimeSpan delta) ImGui.BeginChild("Combined", new System.Numerics.Vector2(0, 0)); // Gather combined data. - Dictionary combinedRecordData = new Dictionary(128); + Dictionary combinedRecordData = new Dictionary(128); foreach (Profiler.ProfileRecord record in frameData.records) { - if (combinedRecordData.TryGetValue(record.label, out double totalMs)) + if (combinedRecordData.TryGetValue(record.label, out (double ms, uint calls) combined)) { - combinedRecordData[record.label] = totalMs + record.ElapsedMilliseconds(); + combinedRecordData[record.label] = (combined.ms + record.ElapsedMilliseconds(), combined.calls + 1); } else { - combinedRecordData.Add(record.label, record.ElapsedMilliseconds()); + combinedRecordData.Add(record.label, (record.ElapsedMilliseconds(), 1)); } } - IOrderedEnumerable> orderedCombinedData = combinedRecordData.OrderByDescending(x => x.Value); + IOrderedEnumerable> orderedCombinedData = combinedRecordData.OrderByDescending(x => x.Value.ms); - if (ImGui.BeginTable("ProfilerCombinedData", 2, tableFlags, new System.Numerics.Vector2(0, 0))) + if (ImGui.BeginTable("ProfilerCombinedData", 3, tableFlags, new System.Numerics.Vector2(0, 0))) { - ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.8f, 0); + ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.6f, 0); ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1); + ImGui.TableSetupColumn("calls", ImGuiTableColumnFlags.WidthStretch, 0.2f, 2); ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible ImGui.TableHeadersRow(); - foreach (KeyValuePair combinedData in orderedCombinedData) + foreach (KeyValuePair combinedData in orderedCombinedData) { ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text($"{combinedData.Key}"); ImGui.TableNextColumn(); - ImGui.Text($"{combinedData.Value:0.000}"); + ImGui.Text($"{combinedData.Value.ms:0.000}"); + ImGui.TableNextColumn(); + ImGui.Text($"{combinedData.Value.calls}"); } ImGui.EndTable(); diff --git a/Nerfed.Runtime/Systems/LocalToWorldSystem.cs b/Nerfed.Runtime/Systems/LocalToWorldSystem.cs index 0be511b..cf460b2 100644 --- a/Nerfed.Runtime/Systems/LocalToWorldSystem.cs +++ b/Nerfed.Runtime/Systems/LocalToWorldSystem.cs @@ -15,19 +15,62 @@ namespace Nerfed.Runtime.Systems { public class LocalToWorldSystem : MoonTools.ECS.System { + private readonly bool useParallelFor = false; // When having a low amount of transforms or when in debug mode this might be slower. private readonly Filter rootEntitiesFilter; + private readonly Filter transformEntitiesFilter; + private readonly Action updateWorldTransform; public LocalToWorldSystem(World world) : base(world) { rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); + if (useParallelFor) + { + transformEntitiesFilter = FilterBuilder.Include().Build(); + updateWorldTransform = UpdateWorldTransformByIndex; + } } public override void Update(TimeSpan delta) { - foreach (Entity entity in rootEntitiesFilter.Entities) + if (rootEntitiesFilter.Empty) { - UpdateWorldTransform(entity, Matrix4x4.Identity); + return; } + + if (useParallelFor) + { + Profiler.BeginSample("ParallelFor.LocalToWorldCheck"); + // This check is needed because some entities might not have a LocalToWorld component yet. + // Adding this during the loop will break. + foreach (Entity entity in transformEntitiesFilter.Entities) { + if (Has(entity)) + { + continue; + } + + Set(entity, new LocalToWorld(Matrix4x4.Identity)); + } + Profiler.EndSample(); + + Profiler.BeginSample("ParallelFor.LocalToWorldUpdate"); + // This should only be used when the filter doesn't change by executing these functions! + // So no entity deletion or setting/removing of components used by the filters in this loop. + Parallel.For(0, rootEntitiesFilter.Count, updateWorldTransform); + Profiler.EndSample(); + } + else + { + foreach (Entity entity in rootEntitiesFilter.Entities) + { + UpdateWorldTransform(entity, Matrix4x4.Identity); + } + } + } + + private void UpdateWorldTransformByIndex(int entityFilterIndex) + { + Entity entity = rootEntitiesFilter.NthEntity(entityFilterIndex); + UpdateWorldTransform(entity, Matrix4x4.Identity); } private void UpdateWorldTransform(in Entity entity, Matrix4x4 localToWorldMatrix) @@ -41,8 +84,6 @@ private void UpdateWorldTransform(in Entity entity, Matrix4x4 localToWorldMatrix localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS()); LocalToWorld localToWorld = new(localToWorldMatrix); Set(entity, localToWorld); - //Task.Delay(10).Wait(); - //Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); } ReverseSpanEnumerator childEntities = World.InRelations(entity); diff --git a/Nerfed.Runtime/Systems/LocalToWorldThreadedSystem.cs b/Nerfed.Runtime/Systems/LocalToWorldThreadedSystem.cs deleted file mode 100644 index d0cbe05..0000000 --- a/Nerfed.Runtime/Systems/LocalToWorldThreadedSystem.cs +++ /dev/null @@ -1,81 +0,0 @@ -using MoonTools.ECS; -using Nerfed.Runtime.Components; -using Nerfed.Runtime.Util; -using System.Numerics; - -namespace Nerfed.Runtime.Systems -{ - public class LocalToWorldThreadedSystem : MoonTools.ECS.System - { - private readonly Filter rootEntitiesFilter; - private readonly Action forEntity; - - public LocalToWorldThreadedSystem(World world) : base(world) - { - rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); - forEntity = UpdateEntity; - } - - public override void Update(TimeSpan delta) - { - Parallel.For(0, rootEntitiesFilter.Count, forEntity); - } - - private void UpdateEntity(int entityFilterIndex) - { - Entity entity = rootEntitiesFilter.NthEntity(entityFilterIndex); - UpdateWorldTransform(entity, Matrix4x4.Identity); - } - - private void UpdateWorldTransform(Entity entity, Matrix4x4 localToWorldMatrix) - { - // TODO: Only update dirty transforms. - // If a parent is dirty all the children need to update their localToWorld matrix. - // How do we check if something is dirty? How do we know if a LocalTransform has been changed? - - if (Has(entity)) - { - LocalTransform localTransform = Get(entity); - localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS()); - LocalToWorld localToWorld = new(localToWorldMatrix); - Set(entity, localToWorld); - //Task.Delay(10).Wait(); - //Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); - } - - ReverseSpanEnumerator childEntities = World.InRelations(entity); - foreach (Entity childEntity in childEntities) - { - UpdateWorldTransform(childEntity, localToWorldMatrix); - } - } - } -} - - -//System.Random rnd = new System.Random(); - -//World world = new World(); -//Filter filter = world.FilterBuilder.Include().Build(); - -//for (int i = 0; i < 100; i++) -//{ -// Entity e = world.CreateEntity(i.ToString()); -// world.Set(e, new Test()); -//} - -//Action forEntityIndex = ForEntityIndex; -//ParallelLoopResult result = Parallel.For(0, filter.Count, forEntityIndex); -//Console.WriteLine(result.IsCompleted); - -//void ForEntityIndex(int entity) -//{ -// int delay = rnd.Next(1, 1000); -// Task.Delay(delay).Wait(); -// Console.WriteLine($"ForEntityIndex | {filter.NthEntity(entity).ID}"); -//} - -//namespace Hoi -//{ -// public readonly record struct Test; -//} \ No newline at end of file diff --git a/Nerfed.Runtime/Systems/ParentSystem.cs b/Nerfed.Runtime/Systems/ParentSystem.cs deleted file mode 100644 index 1cbf794..0000000 --- a/Nerfed.Runtime/Systems/ParentSystem.cs +++ /dev/null @@ -1,67 +0,0 @@ -using MoonTools.ECS; -using Nerfed.Runtime.Components; - -namespace Nerfed.Runtime.Systems -{ - //public class ParentSystem : MoonTools.ECS.System - //{ - // private readonly Filter parentsAddedFilter; - // private readonly Filter parentsRemovedFilter; - // private readonly Filter parentsFilter; - - // public ParentSystem(World world) : base(world) - // { - // parentsAddedFilter = FilterBuilder.Include().Exclude().Build(); - // parentsRemovedFilter = FilterBuilder.Include().Exclude().Build(); - // parentsFilter = FilterBuilder.Include().Include().Build(); - // } - - // public override void Update(TimeSpan delta) - // { - // // Update removed parents. - // foreach (Entity entity in parentsRemovedFilter.Entities) - // { - // // Do stuff here to update/remove child relations etc. - // //PreviousParent previousParent = Get(entity); - // //World.Unrelate(previousParent.parentEntity, entity); - // Remove(entity); - // } - - // // Update added parents. - // foreach (Entity entity in parentsAddedFilter.Entities) - // { - // Parent parent = Get(entity); - - // if (Has(parent.parentEntity) && Get(parent.parentEntity).parentEntity == entity) - // { - // Log.Warning($"Entity {entity} cannot be a parent of entity {parent.parentEntity}, because {parent.parentEntity} is the parent of {entity}"); - // Remove(entity); - // continue; - // } - - // PreviousParent previousParent = new(parent.parentEntity); - // Set(entity, previousParent); - // World.Relate(parent.parentEntity, entity, new ChildParentRelation()); - // } - - // // Update relations if the parent has changed. - // foreach (Entity entity in parentsFilter.Entities) - // { - // Parent parent = Get(entity); - // PreviousParent previousParent = Get(entity); - - // if(parent.parentEntity != previousParent.parentEntity) - // { - // World.Unrelate(previousParent.parentEntity, entity); - // Set(entity, new PreviousParent(parent.parentEntity)); - // World.Relate(parent.parentEntity, entity, new ChildParentRelation()); - // } - // } - - // // TODO: - // // What if an parent entity gets destroyed? - // // How does the child know if the parent is in valid. Also we need to remove the parent component. - // // Maybe if we also relate the other way around child -> parent via relations, and the relation is gone that means the parent is gone so we should remove the component. - // } - //} -} \ No newline at end of file