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:
committed by
Gustas Kažukauskas
parent
bcfaa44d66
commit
06e4a2e010
@@ -123,6 +123,9 @@ namespace OpenRA
|
||||
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
||||
bool created;
|
||||
|
||||
IEnumerable<IRenderable> renderables;
|
||||
WorldRenderer lastWorldRenderer;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
@@ -287,11 +290,18 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (lastWorldRenderer != wr)
|
||||
{
|
||||
// PERF: Cache the enumerable to reduce allocations.
|
||||
lastWorldRenderer = wr;
|
||||
renderables = Renderables(wr);
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
var renderables = Renderables(wr);
|
||||
var modifiedRenderables = renderables;
|
||||
foreach (var modifier in renderModifiers)
|
||||
renderables = modifier.ModifyRender(this, wr, renderables);
|
||||
return renderables;
|
||||
modifiedRenderables = modifier.ModifyRender(this, wr, modifiedRenderables);
|
||||
return modifiedRenderables;
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> Renderables(WorldRenderer wr)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -431,9 +432,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// Make sure that we can actually fire on the actor
|
||||
var armaments = ab.ChooseArmamentsForTarget(target, false);
|
||||
if (!allowMove)
|
||||
armaments = armaments.Where(arm =>
|
||||
target.IsInRange(self.CenterPosition, arm.MaxRange()) &&
|
||||
!target.IsInRange(self.CenterPosition, arm.Weapon.MinRange));
|
||||
{
|
||||
// PERF: This lambda captures, contain it within a local function to prevent
|
||||
// the compiler allocating the helper class at the top of the loop.
|
||||
static Func<Armament, bool> IsInRange(Actor self, Target target) =>
|
||||
arm =>
|
||||
target.IsInRange(self.CenterPosition, arm.MaxRange()) &&
|
||||
!target.IsInRange(self.CenterPosition, arm.Weapon.MinRange);
|
||||
|
||||
armaments = armaments.Where(IsInRange(self, target));
|
||||
}
|
||||
|
||||
if (!armaments.Any())
|
||||
continue;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user