Combined transform systems
Profiler window calls count Parallel transform system is now in the normal transform system Removed unused parent system
This commit is contained in:
		| @@ -69,6 +69,7 @@ namespace Nerfed.Editor | ||||
| 
 | ||||
|             foreach (MoonTools.ECS.System system in Program.editorSystems) | ||||
|             { | ||||
|                 using ProfilerScope scope = new(system.GetType().Name); | ||||
|                 system.Update(Engine.Timestep); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -28,7 +28,6 @@ internal class Program | ||||
|     { | ||||
|         //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 @@ internal class Program | ||||
| 
 | ||||
|         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,11 +71,9 @@ internal class Program | ||||
|     { | ||||
|         foreach (MoonTools.ECS.System system in systems) | ||||
|         { | ||||
|             using (new ProfilerScope(system.GetType().Name)) | ||||
|             { | ||||
|             using ProfilerScope scope = new(system.GetType().Name); | ||||
|             system.Update(Engine.Timestep); | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
|         using (new ProfilerScope("EditorGui.Update")) | ||||
|         { | ||||
| @@ -93,9 +90,12 @@ internal class Program | ||||
|     } | ||||
| 
 | ||||
|     private static void HandleOnRender() | ||||
|     { | ||||
|         using (new ProfilerScope("EditorGui.Render")) | ||||
|         { | ||||
|             EditorGui.Render(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static void HandleOnQuit() | ||||
|     { | ||||
|   | ||||
| @@ -78,34 +78,37 @@ namespace Nerfed.Editor.Systems | ||||
|             ImGui.BeginChild("Combined", new System.Numerics.Vector2(0, 0)); | ||||
| 
 | ||||
|             // Gather combined data. | ||||
|             Dictionary<string, double> combinedRecordData = new Dictionary<string, double>(128); | ||||
|             Dictionary<string, (double ms, uint calls)> combinedRecordData = new Dictionary<string, (double ms, uint calls)>(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<KeyValuePair<string, double>> orderedCombinedData = combinedRecordData.OrderByDescending(x => x.Value); | ||||
|             IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> 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<string, double> combinedData in orderedCombinedData) | ||||
|                 foreach (KeyValuePair<string, (double ms, uint calls)> 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(); | ||||
|   | ||||
| @@ -15,20 +15,63 @@ 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<int> updateWorldTransform; | ||||
| 
 | ||||
|         public LocalToWorldSystem(World world) : base(world) | ||||
|         { | ||||
|             rootEntitiesFilter = FilterBuilder.Include<LocalTransform>().Exclude<Child>().Build(); | ||||
|             if (useParallelFor) | ||||
|             { | ||||
|                 transformEntitiesFilter = FilterBuilder.Include<LocalTransform>().Build(); | ||||
|                 updateWorldTransform = UpdateWorldTransformByIndex; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public override void Update(TimeSpan delta) | ||||
|         { | ||||
|             if (rootEntitiesFilter.Empty) | ||||
|             { | ||||
|                 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<LocalToWorld>(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 @@ namespace Nerfed.Runtime.Systems | ||||
|                 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<Entity> childEntities = World.InRelations<ChildParentRelation>(entity); | ||||
|   | ||||
| @@ -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<int> forEntity; | ||||
| 
 | ||||
|         public LocalToWorldThreadedSystem(World world) : base(world) | ||||
|         { | ||||
|             rootEntitiesFilter = FilterBuilder.Include<LocalTransform>().Exclude<Child>().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<LocalTransform>(entity)) | ||||
|             { | ||||
|                 LocalTransform localTransform = Get<LocalTransform>(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<Entity> childEntities = World.InRelations<ChildParentRelation>(entity); | ||||
|             foreach (Entity childEntity in childEntities) | ||||
|             { | ||||
|                 UpdateWorldTransform(childEntity, localToWorldMatrix); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| //System.Random rnd = new System.Random(); | ||||
| 
 | ||||
| //World world = new World(); | ||||
| //Filter filter = world.FilterBuilder.Include<Test>().Build(); | ||||
| 
 | ||||
| //for (int i = 0; i < 100; i++) | ||||
| //{ | ||||
| //    Entity e = world.CreateEntity(i.ToString()); | ||||
| //    world.Set(e, new Test()); | ||||
| //} | ||||
| 
 | ||||
| //Action<int> 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; | ||||
| //} | ||||
| @@ -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<Parent>().Exclude<PreviousParent>().Build(); | ||||
|     //        parentsRemovedFilter = FilterBuilder.Include<PreviousParent>().Exclude<Parent>().Build(); | ||||
|     //        parentsFilter = FilterBuilder.Include<Parent>().Include<PreviousParent>().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<PreviousParent>(entity); | ||||
|     //            //World.Unrelate<ChildParentRelation>(previousParent.parentEntity, entity); | ||||
|     //            Remove<PreviousParent>(entity); | ||||
|     //        } | ||||
| 
 | ||||
|     //        // Update added parents. | ||||
|     //        foreach (Entity entity in parentsAddedFilter.Entities) | ||||
|     //        { | ||||
|     //            Parent parent = Get<Parent>(entity); | ||||
| 
 | ||||
|     //            if (Has<Parent>(parent.parentEntity) && Get<Parent>(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<Parent>(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<Parent>(entity); | ||||
|     //            PreviousParent previousParent = Get<PreviousParent>(entity); | ||||
| 
 | ||||
|     //            if(parent.parentEntity != previousParent.parentEntity) | ||||
|     //            { | ||||
|     //                World.Unrelate<ChildParentRelation>(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. | ||||
|     //    } | ||||
|     //} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user