From 38f1f1e5c2391376dea774beb0b9f9747692e5ac Mon Sep 17 00:00:00 2001 From: Vapre Date: Sat, 4 Jul 2020 20:27:35 +0200 Subject: [PATCH] Replace TraitContainer.AllEnumerator with ApplyToAll. As proposed in the past in #13577. Replace TraitContainer.All() that uses the custom AllEnumerator with TraitContainer.ApplyToAllX() that takes an action as argument. The AllEnumerator.Current function show up in profiling reports since it is used each tick multiple times for multiple traits. The function is 'heavy' because it creates TraitPair's temporary objects for each actor trait combination. In the past about 20k ITick trait pairs were present during an average multi player game. Using an Apply function that takes an action avoid the need to create these temporary objects. To be able to still use 'DoTimed' somewhat efficiently the measurement was moved to inside the trait container method. Results in a 25% performance improvement in accessing all traits of a certain type. Apply function could be used for other TraitContainer functions as well for further improvements. Test result for calling on a dummy trait on 20k actors a 1000 times: 1315 ms traitcontainer.AllEnumerator (current) 989 ms traitcontainer.Apply (this commit) --- OpenRA.Game/TraitDictionary.cs | 38 ++++++++++++++++++++++++++++++++++ OpenRA.Game/World.cs | 9 ++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/OpenRA.Game/TraitDictionary.cs b/OpenRA.Game/TraitDictionary.cs index 774563c27c..4d91bba30d 100644 --- a/OpenRA.Game/TraitDictionary.cs +++ b/OpenRA.Game/TraitDictionary.cs @@ -11,8 +11,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using OpenRA.Primitives; +using OpenRA.Support; namespace OpenRA { @@ -122,6 +124,16 @@ namespace OpenRA t.Value.RemoveActor(a.ActorID); } + public void ApplyToActorsWithTrait(Action action) + { + InnerGet().ApplyToAll(action); + } + + public void ApplyToActorsWithTraitTimed(Action action, string text) + { + InnerGet().ApplyToAllTimed(action, text); + } + interface ITraitContainer { void Add(Actor actor, object trait); @@ -277,6 +289,32 @@ namespace OpenRA actors.RemoveRange(startIndex, count); traits.RemoveRange(startIndex, count); } + + public void ApplyToAll(Action action) + { + for (var i = 0; i < actors.Count; i++) + action(actors[i], traits[i]); + } + + public void ApplyToAllTimed(Action action, string text) + { + var longTickThresholdInStopwatchTicks = PerfTimer.LongTickThresholdInStopwatchTicks; + var start = Stopwatch.GetTimestamp(); + for (var i = 0; i < actors.Count; i++) + { + var actor = actors[i]; + var trait = traits[i]; + action(actor, trait); + var current = Stopwatch.GetTimestamp(); + if (current - start > longTickThresholdInStopwatchTicks) + { + PerfTimer.LogLongTick(start, current, text, trait); + start = Stopwatch.GetTimestamp(); + } + else + start = current; + } + } } } } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index d29aec8aef..59a0e536f6 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -465,7 +465,7 @@ namespace OpenRA foreach (var a in actors.Values) a.Tick(); - ActorsWithTrait().DoTimed(x => x.Trait.Tick(x.Actor), "Trait"); + ApplyToActorsWithTraitTimed((Actor actor, ITick trait) => trait.Tick(actor), "Trait"); effects.DoTimed(e => e.Tick(this), "Effect"); } @@ -477,7 +477,7 @@ namespace OpenRA // For things that want to update their render state once per tick, ignoring pause state public void TickRender(WorldRenderer wr) { - ActorsWithTrait().DoTimed(x => x.Trait.TickRender(wr, x.Actor), "Render"); + ApplyToActorsWithTraitTimed((Actor actor, ITickRender trait) => trait.TickRender(wr, actor), "Render"); ScreenMap.TickRender(); } @@ -536,6 +536,11 @@ namespace OpenRA return TraitDict.ActorsWithTrait(); } + public void ApplyToActorsWithTraitTimed(Action action, string text) + { + TraitDict.ApplyToActorsWithTraitTimed(action, text); + } + public IEnumerable ActorsHavingTrait() { return TraitDict.ActorsHavingTrait();