Tweaks for map generator performance.

The map generator is already performant, but we can remove a few rough edges from infrastructure it interacts with.

- Add TypeDictionary constructor to clone from an existing copy. The map editor infrastructure relies heavily on cloning ActorReferences and the underlying dictionaries. A dedicated clone method avoids reflection overhead for CreateTypeContainer on a fresh instance.
- Use ThrowIndexOutOfRangeException helper. This allows the Index method to be inlined.
- Reuse lists in FloodFill. This avoids allocating and resizing new lists on each loop when we can reuse the existing capacity.
- Manage CellEntryChanged at the layer rather than individual previews. We only need to attach/detach from the delegate once at the layer, rather than many times as previews are added and removed.
This commit is contained in:
RoosterDragon
2025-11-24 19:10:39 +00:00
committed by Gustas Kažukauskas
parent 3ea5b08848
commit a546ca2f92
10 changed files with 62 additions and 49 deletions

View File

@@ -54,13 +54,8 @@ namespace OpenRA
public ActorReference(string type, TypeDictionary inits) public ActorReference(string type, TypeDictionary inits)
{ {
Type = type; Type = type;
initDict = new Lazy<TypeDictionary>(() => var initsClone = new TypeDictionary(inits);
{ initDict = Exts.Lazy(() => initsClone);
var dict = new TypeDictionary();
foreach (var i in inits)
dict.Add(i);
return dict;
});
} }
static ActorInit LoadInit(string initName, MiniYaml initYaml) static ActorInit LoadInit(string initName, MiniYaml initYaml)
@@ -110,11 +105,7 @@ namespace OpenRA
public ActorReference Clone() public ActorReference Clone()
{ {
var clone = new ActorReference(Type); return new ActorReference(Type, initDict.Value);
foreach (var init in initDict.Value)
clone.initDict.Value.Add(init);
return clone;
} }
public void Add(ActorInit init) public void Add(ActorInit init)

View File

@@ -12,6 +12,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace OpenRA.Primitives namespace OpenRA.Primitives
{ {
@@ -20,7 +21,17 @@ namespace OpenRA.Primitives
static readonly Func<Type, ITypeContainer> CreateTypeContainer = t => static readonly Func<Type, ITypeContainer> CreateTypeContainer = t =>
(ITypeContainer)typeof(TypeContainer<>).MakeGenericType(t).GetConstructor(Type.EmptyTypes).Invoke(null); (ITypeContainer)typeof(TypeContainer<>).MakeGenericType(t).GetConstructor(Type.EmptyTypes).Invoke(null);
readonly Dictionary<Type, ITypeContainer> data = []; readonly Dictionary<Type, ITypeContainer> data;
public TypeDictionary()
{
data = [];
}
public TypeDictionary(TypeDictionary cloneFrom)
{
data = cloneFrom.data.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone());
}
ITypeContainer InnerGet(Type t) ITypeContainer InnerGet(Type t)
{ {
@@ -127,11 +138,12 @@ namespace OpenRA.Primitives
void Add(object value); void Add(object value);
void Remove(object value); void Remove(object value);
void TrimExcess(); void TrimExcess();
ITypeContainer Clone();
} }
sealed class TypeContainer<T> : ITypeContainer sealed class TypeContainer<T> : ITypeContainer
{ {
public List<T> Objects { get; } = []; public List<T> Objects { get; private init; } = [];
public int Count => Objects.Count; public int Count => Objects.Count;
@@ -149,6 +161,11 @@ namespace OpenRA.Primitives
{ {
Objects.TrimExcess(); Objects.TrimExcess();
} }
public ITypeContainer Clone()
{
return new TypeContainer<T>() { Objects = Objects.ToList() };
}
} }
} }

View File

@@ -561,11 +561,12 @@ namespace OpenRA.Mods.Common.MapGenerator
Func<CPos, P, P?> filler, Func<CPos, P, P?> filler,
ImmutableArray<CVec> spread) where P : struct ImmutableArray<CVec> spread) where P : struct
{ {
var current = new List<(CPos CPos, P Prop)>();
var next = seeds.ToList(); var next = seeds.ToList();
while (next.Count != 0) while (next.Count != 0)
{ {
var current = next; (next, current) = (current, next);
next = []; next.Clear();
foreach (var (source, prop) in current) foreach (var (source, prop) in current)
{ {
var newProp = filler(source, prop); var newProp = filler(source, prop);

View File

@@ -57,11 +57,16 @@ namespace OpenRA.Mods.Common.MapGenerator
public int Index(int x, int y) public int Index(int x, int y)
{ {
if (!ContainsXY(x, y)) if (!ContainsXY(x, y))
throw new IndexOutOfRangeException( ThrowIndexOutOfRangeException(x, y);
$"({x}, {y}) is out of bounds for a matrix of size ({Size.X}, {Size.Y})");
return y * Size.X + x; return y * Size.X + x;
} }
void ThrowIndexOutOfRangeException(int x, int y)
{
throw new IndexOutOfRangeException(
$"({x}, {y}) is out of bounds for a matrix of size ({Size.X}, {Size.Y})");
}
/// <summary> /// <summary>
/// Convert a Data index into a pair of coordinates. /// Convert a Data index into a pair of coordinates.
/// </summary> /// </summary>

View File

@@ -345,11 +345,12 @@ namespace OpenRA.Mods.Common.MapGenerator
Func<int2, P, P?> filler, Func<int2, P, P?> filler,
ImmutableArray<int2> spread) where P : struct ImmutableArray<int2> spread) where P : struct
{ {
var current = new List<(int2 XY, P Prop)>();
var next = seeds.ToList(); var next = seeds.ToList();
while (next.Count != 0) while (next.Count != 0)
{ {
var current = next; (next, current) = (current, next);
next = []; next.Clear();
foreach (var (source, prop) in current) foreach (var (source, prop) in current)
{ {
var newProp = filler(source, prop); var newProp = filler(source, prop);

View File

@@ -54,9 +54,7 @@ namespace OpenRA.Mods.Common.Traits
var exitLocations = new List<CPos>(); var exitLocations = new List<CPos>();
// Clone the initializer dictionary for the new actor // Clone the initializer dictionary for the new actor
var td = new TypeDictionary(); var td = new TypeDictionary(inits);
foreach (var init in inits)
td.Add(init);
if (exitinfo != null && self.OccupiesSpace != null && producee.HasTraitInfo<IOccupySpaceInfo>()) if (exitinfo != null && self.OccupiesSpace != null && producee.HasTraitInfo<IOccupySpaceInfo>())
{ {

View File

@@ -82,13 +82,12 @@ namespace OpenRA.Mods.Common.Traits
self.World.AddFrameEndTask(w => self.World.AddFrameEndTask(w =>
{ {
var td = new TypeDictionary(); var td = new TypeDictionary(inits)
foreach (var init in inits) {
td.Add(init); new LocationInit(location.Value),
new CenterPositionInit(pos),
td.Add(new LocationInit(location.Value)); new FacingInit(initialFacing)
td.Add(new CenterPositionInit(pos)); };
td.Add(new FacingInit(initialFacing));
var newUnit = self.World.CreateActor(producee.Name, td); var newUnit = self.World.CreateActor(producee.Name, td);

View File

@@ -122,9 +122,7 @@ namespace OpenRA.Mods.Common.Traits
var altitude = self.World.Map.Rules.Actors[actorType].TraitInfo<AircraftInfo>().CruiseAltitude; var altitude = self.World.Map.Rules.Actors[actorType].TraitInfo<AircraftInfo>().CruiseAltitude;
// Clone the initializer dictionary for the new actor // Clone the initializer dictionary for the new actor
var td = new TypeDictionary(); var td = new TypeDictionary(inits);
foreach (var init in inits)
td.Add(init);
if (self.OccupiesSpace != null) if (self.OccupiesSpace != null)
{ {

View File

@@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new EditorActorLayer(this); } public override object Create(ActorInitializer init) { return new EditorActorLayer(this); }
} }
public class EditorActorLayer : IWorldLoaded, ITickRender, IRender, IRadarSignature, ICreatePlayers, IRenderAnnotations public class EditorActorLayer : IWorldLoaded, ITickRender, IRender, IRadarSignature, ICreatePlayers, IRenderAnnotations, INotifyActorDisposing
{ {
const string ActorPrefix = "Actor"; const string ActorPrefix = "Actor";
const string PlayerSpawnName = "mpspawn"; const string PlayerSpawnName = "mpspawn";
@@ -101,6 +101,9 @@ namespace OpenRA.Mods.Common.Traits
} }
AddRange(CollectionsMarshal.AsSpan(references), names); AddRange(CollectionsMarshal.AsSpan(references), names);
world.Map.Height.CellEntryChanged += UpdatePreviewsOnMapChange;
world.Map.Ramp.CellEntryChanged += UpdatePreviewsOnMapChange;
} }
void ITickRender.TickRender(WorldRenderer wr, Actor self) void ITickRender.TickRender(WorldRenderer wr, Actor self)
@@ -400,7 +403,7 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<EditorActorPreview> PreviewsAtCell(CPos cell) public IEnumerable<EditorActorPreview> PreviewsAtCell(CPos cell)
{ {
return cellMap.At(new int2(cell.X - cellOffset.X, cell.Y - cellOffset.Y)) return cellMap.At(new int2(cell.X - cellOffset.X, cell.Y - cellOffset.Y))
.Where(p => OccupiedCells(p).Any(fp => fp == cell)); .Where(p => OccupiedCells(p).Contains(cell));
} }
public SubCell FreeSubCellAt(CPos cell) public SubCell FreeSubCellAt(CPos cell)
@@ -426,6 +429,12 @@ namespace OpenRA.Mods.Common.Traits
return screenMap.At(worldPx); return screenMap.At(worldPx);
} }
void UpdatePreviewsOnMapChange(CPos changedCell)
{
foreach (var preview in PreviewsAtCell(changedCell))
preview.UpdateFromCellChange();
}
public Action OnPlayerRemoved = () => { }; public Action OnPlayerRemoved = () => { };
static bool TryGetActorId(string name, out uint id) static bool TryGetActorId(string name, out uint id)
@@ -472,6 +481,12 @@ namespace OpenRA.Mods.Common.Traits
destinationBuffer.Add((cell, preview.RadarColor)); destinationBuffer.Add((cell, preview.RadarColor));
} }
void INotifyActorDisposing.Disposing(Actor self)
{
self.World.Map.Height.CellEntryChanged -= UpdatePreviewsOnMapChange;
self.World.Map.Ramp.CellEntryChanged -= UpdatePreviewsOnMapChange;
}
public EditorActorPreview this[string id] public EditorActorPreview this[string id]
{ {
get { return previews.FirstOrDefault(p => p.ID.Equals(id, StringComparison.OrdinalIgnoreCase)); } get { return previews.FirstOrDefault(p => p.ID.Equals(id, StringComparison.OrdinalIgnoreCase)); }

View File

@@ -46,7 +46,6 @@ namespace OpenRA.Mods.Common.Traits
readonly TooltipInfoBase tooltip; readonly TooltipInfoBase tooltip;
readonly ActorReference reference; readonly ActorReference reference;
readonly Dictionary<INotifyEditorPlacementInfo, object> editorData = []; readonly Dictionary<INotifyEditorPlacementInfo, object> editorData = [];
readonly Action<CPos> onCellEntryChanged;
SelectionBoxAnnotationRenderable selectionBox; SelectionBoxAnnotationRenderable selectionBox;
IActorPreview[] previews; IActorPreview[] previews;
@@ -69,7 +68,7 @@ namespace OpenRA.Mods.Common.Traits
throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}"); throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}");
GenerateFootprint(); GenerateFootprint();
UpdateFromCellChange(null); UpdateFromCellChange();
tooltip = Info.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase tooltip = Info.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase
?? Info.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault); ?? Info.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
@@ -78,8 +77,6 @@ namespace OpenRA.Mods.Common.Traits
terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>(); terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>();
UpdateRadarColor(); UpdateRadarColor();
onCellEntryChanged = cell => UpdateFromCellChange(cell);
} }
public EditorActorPreview WithId(string id) public EditorActorPreview WithId(string id)
@@ -87,11 +84,8 @@ namespace OpenRA.Mods.Common.Traits
return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner); return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner);
} }
void UpdateFromCellChange(CPos? cellChanged) public void UpdateFromCellChange()
{ {
if (cellChanged != null && !Footprint.ContainsKey(cellChanged.Value))
return;
CenterPosition = PreviewPosition(worldRenderer.World, reference); CenterPosition = PreviewPosition(worldRenderer.World, reference);
GeneratePreviews(); GeneratePreviews();
GenerateBounds(); GenerateBounds();
@@ -183,18 +177,12 @@ namespace OpenRA.Mods.Common.Traits
{ {
foreach (var notify in Info.TraitInfos<INotifyEditorPlacementInfo>()) foreach (var notify in Info.TraitInfos<INotifyEditorPlacementInfo>())
editorData[notify] = notify.AddedToEditor(this, worldRenderer.World); editorData[notify] = notify.AddedToEditor(this, worldRenderer.World);
worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged;
worldRenderer.World.Map.Ramp.CellEntryChanged += onCellEntryChanged;
} }
public void RemovedFromEditor() public void RemovedFromEditor()
{ {
foreach (var kv in editorData) foreach (var kv in editorData)
kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value); kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value);
worldRenderer.World.Map.Height.CellEntryChanged -= onCellEntryChanged;
worldRenderer.World.Map.Ramp.CellEntryChanged -= onCellEntryChanged;
} }
public void AddInit<T>(T init) where T : ActorInit public void AddInit<T>(T init) where T : ActorInit