Reduce allocations in main game loop.

Target a couple of hot paths:
- In Actor, cache the renderables enumerable by tracking the last used WorldRenderer. It's slightly uncouth but in practise the WorldRenderer will be the same for the lifetime of the Actor and outlive it anyway.
- In RenderSprites, cache the renderables enumerable. If we refresh the palettes directly, we don't need the WorldRenderer for anything else so can return a cached enumerable. This could result in iterating the animation list twice, so use a flag to only do it when required, which is rare.
- In AutoTarget, the compiler has an unfortunate behaviour where it allocates the helper class for a capturing lambda at the top of the loop, but it's on a rare path and so this allocation is usually unused. We can discourage the compiler from allocating until it's actually needed by wrapping the call in a local function.
This commit is contained in:
RoosterDragon
2025-12-19 19:05:36 +00:00
committed by Gustas Kažukauskas
parent bcfaa44d66
commit 06e4a2e010
3 changed files with 47 additions and 12 deletions

View File

@@ -143,6 +143,8 @@ namespace OpenRA.Mods.Common.Traits.Render
public readonly RenderSpritesInfo Info;
readonly string faction;
readonly List<AnimationWrapper> anims = [];
readonly IEnumerable<IRenderable> renderables;
bool shouldRefreshPalettes;
string cachedImage;
public static Func<WAngle> MakeFacingFunc(Actor self)
@@ -158,6 +160,7 @@ namespace OpenRA.Mods.Common.Traits.Render
{
Info = info;
faction = init.GetValue<FactionInit, string>(init.Self.Owner.Faction.InternalName);
renderables = RenderAnimations(anims, init.Self);
}
public string GetImage(Actor self)
@@ -170,6 +173,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public void UpdatePalette()
{
shouldRefreshPalettes = true;
foreach (var anim in anims)
anim.OwnerChanged();
}
@@ -178,18 +182,30 @@ namespace OpenRA.Mods.Common.Traits.Render
public void OnEffectiveOwnerChanged(Actor self, Player oldEffectiveOwner, Player newEffectiveOwner) { UpdatePalette(); }
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
if (shouldRefreshPalettes)
{
shouldRefreshPalettes = false;
foreach (var a in anims)
{
if (a.PaletteReference == null)
{
var owner = self.EffectiveOwner != null && self.EffectiveOwner.Disguised ? self.EffectiveOwner.Owner : self.Owner;
a.CachePalette(wr, owner);
}
}
}
return renderables;
}
static IEnumerable<IRenderable> RenderAnimations(List<AnimationWrapper> anims, Actor self)
{
foreach (var a in anims)
{
if (!a.IsVisible)
continue;
if (a.PaletteReference == null)
{
var owner = self.EffectiveOwner != null && self.EffectiveOwner.Disguised ? self.EffectiveOwner.Owner : self.Owner;
a.CachePalette(wr, owner);
}
foreach (var r in a.Animation.Render(self, a.PaletteReference))
yield return r;
}
@@ -226,6 +242,7 @@ namespace OpenRA.Mods.Common.Traits.Render
isPlayerPalette = Info.Palette == null;
}
shouldRefreshPalettes = true;
anims.Add(new AnimationWrapper(anim, palette, isPlayerPalette));
}