diff --git a/OpenRA.Game/Map/ActorReference.cs b/OpenRA.Game/Map/ActorReference.cs index 3e2f7e92fb..d6afd6a04e 100644 --- a/OpenRA.Game/Map/ActorReference.cs +++ b/OpenRA.Game/Map/ActorReference.cs @@ -54,13 +54,8 @@ namespace OpenRA public ActorReference(string type, TypeDictionary inits) { Type = type; - initDict = new Lazy(() => - { - var dict = new TypeDictionary(); - foreach (var i in inits) - dict.Add(i); - return dict; - }); + var initsClone = new TypeDictionary(inits); + initDict = Exts.Lazy(() => initsClone); } static ActorInit LoadInit(string initName, MiniYaml initYaml) @@ -110,11 +105,7 @@ namespace OpenRA public ActorReference Clone() { - var clone = new ActorReference(Type); - foreach (var init in initDict.Value) - clone.initDict.Value.Add(init); - - return clone; + return new ActorReference(Type, initDict.Value); } public void Add(ActorInit init) diff --git a/OpenRA.Game/Primitives/TypeDictionary.cs b/OpenRA.Game/Primitives/TypeDictionary.cs index 4c440ab17b..a82b2d2b30 100644 --- a/OpenRA.Game/Primitives/TypeDictionary.cs +++ b/OpenRA.Game/Primitives/TypeDictionary.cs @@ -12,6 +12,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace OpenRA.Primitives { @@ -20,7 +21,17 @@ namespace OpenRA.Primitives static readonly Func CreateTypeContainer = t => (ITypeContainer)typeof(TypeContainer<>).MakeGenericType(t).GetConstructor(Type.EmptyTypes).Invoke(null); - readonly Dictionary data = []; + readonly Dictionary data; + + public TypeDictionary() + { + data = []; + } + + public TypeDictionary(TypeDictionary cloneFrom) + { + data = cloneFrom.data.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); + } ITypeContainer InnerGet(Type t) { @@ -127,11 +138,12 @@ namespace OpenRA.Primitives void Add(object value); void Remove(object value); void TrimExcess(); + ITypeContainer Clone(); } sealed class TypeContainer : ITypeContainer { - public List Objects { get; } = []; + public List Objects { get; private init; } = []; public int Count => Objects.Count; @@ -149,6 +161,11 @@ namespace OpenRA.Primitives { Objects.TrimExcess(); } + + public ITypeContainer Clone() + { + return new TypeContainer() { Objects = Objects.ToList() }; + } } } diff --git a/OpenRA.Mods.Common/MapGenerator/CellLayerUtils.cs b/OpenRA.Mods.Common/MapGenerator/CellLayerUtils.cs index 20d3c04f67..f0930e6406 100644 --- a/OpenRA.Mods.Common/MapGenerator/CellLayerUtils.cs +++ b/OpenRA.Mods.Common/MapGenerator/CellLayerUtils.cs @@ -561,11 +561,12 @@ namespace OpenRA.Mods.Common.MapGenerator Func filler, ImmutableArray spread) where P : struct { + var current = new List<(CPos CPos, P Prop)>(); var next = seeds.ToList(); while (next.Count != 0) { - var current = next; - next = []; + (next, current) = (current, next); + next.Clear(); foreach (var (source, prop) in current) { var newProp = filler(source, prop); diff --git a/OpenRA.Mods.Common/MapGenerator/Matrix.cs b/OpenRA.Mods.Common/MapGenerator/Matrix.cs index 168417cd3e..5237200544 100644 --- a/OpenRA.Mods.Common/MapGenerator/Matrix.cs +++ b/OpenRA.Mods.Common/MapGenerator/Matrix.cs @@ -57,11 +57,16 @@ namespace OpenRA.Mods.Common.MapGenerator public int Index(int x, int y) { if (!ContainsXY(x, y)) - throw new IndexOutOfRangeException( - $"({x}, {y}) is out of bounds for a matrix of size ({Size.X}, {Size.Y})"); + ThrowIndexOutOfRangeException(x, y); 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})"); + } + /// /// Convert a Data index into a pair of coordinates. /// diff --git a/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs b/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs index f42c199a2d..fb71d7ffc9 100644 --- a/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs +++ b/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs @@ -345,11 +345,12 @@ namespace OpenRA.Mods.Common.MapGenerator Func filler, ImmutableArray spread) where P : struct { + var current = new List<(int2 XY, P Prop)>(); var next = seeds.ToList(); while (next.Count != 0) { - var current = next; - next = []; + (next, current) = (current, next); + next.Clear(); foreach (var (source, prop) in current) { var newProp = filler(source, prop); diff --git a/OpenRA.Mods.Common/Traits/Production.cs b/OpenRA.Mods.Common/Traits/Production.cs index b6e8c9cc57..138577ea52 100644 --- a/OpenRA.Mods.Common/Traits/Production.cs +++ b/OpenRA.Mods.Common/Traits/Production.cs @@ -54,9 +54,7 @@ namespace OpenRA.Mods.Common.Traits var exitLocations = new List(); // Clone the initializer dictionary for the new actor - var td = new TypeDictionary(); - foreach (var init in inits) - td.Add(init); + var td = new TypeDictionary(inits); if (exitinfo != null && self.OccupiesSpace != null && producee.HasTraitInfo()) { diff --git a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs index 3f1cd61797..b525ec1727 100644 --- a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs +++ b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs @@ -82,13 +82,12 @@ namespace OpenRA.Mods.Common.Traits self.World.AddFrameEndTask(w => { - var td = new TypeDictionary(); - foreach (var init in inits) - td.Add(init); - - td.Add(new LocationInit(location.Value)); - td.Add(new CenterPositionInit(pos)); - td.Add(new FacingInit(initialFacing)); + var td = new TypeDictionary(inits) + { + new LocationInit(location.Value), + new CenterPositionInit(pos), + new FacingInit(initialFacing) + }; var newUnit = self.World.CreateActor(producee.Name, td); diff --git a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs index e4a6ecedf5..6f52b0c4d8 100644 --- a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs +++ b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs @@ -122,9 +122,7 @@ namespace OpenRA.Mods.Common.Traits var altitude = self.World.Map.Rules.Actors[actorType].TraitInfo().CruiseAltitude; // Clone the initializer dictionary for the new actor - var td = new TypeDictionary(); - foreach (var init in inits) - td.Add(init); + var td = new TypeDictionary(inits); if (self.OccupiesSpace != null) { diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index 2fa55ad968..d6fddd0053 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits 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 PlayerSpawnName = "mpspawn"; @@ -101,6 +101,9 @@ namespace OpenRA.Mods.Common.Traits } AddRange(CollectionsMarshal.AsSpan(references), names); + + world.Map.Height.CellEntryChanged += UpdatePreviewsOnMapChange; + world.Map.Ramp.CellEntryChanged += UpdatePreviewsOnMapChange; } void ITickRender.TickRender(WorldRenderer wr, Actor self) @@ -400,7 +403,7 @@ namespace OpenRA.Mods.Common.Traits public IEnumerable PreviewsAtCell(CPos cell) { 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) @@ -426,6 +429,12 @@ namespace OpenRA.Mods.Common.Traits return screenMap.At(worldPx); } + void UpdatePreviewsOnMapChange(CPos changedCell) + { + foreach (var preview in PreviewsAtCell(changedCell)) + preview.UpdateFromCellChange(); + } + public Action OnPlayerRemoved = () => { }; static bool TryGetActorId(string name, out uint id) @@ -472,6 +481,12 @@ namespace OpenRA.Mods.Common.Traits 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] { get { return previews.FirstOrDefault(p => p.ID.Equals(id, StringComparison.OrdinalIgnoreCase)); } diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs index 84d0112482..926ff9d6cd 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs @@ -46,7 +46,6 @@ namespace OpenRA.Mods.Common.Traits readonly TooltipInfoBase tooltip; readonly ActorReference reference; readonly Dictionary editorData = []; - readonly Action onCellEntryChanged; SelectionBoxAnnotationRenderable selectionBox; IActorPreview[] previews; @@ -69,7 +68,7 @@ namespace OpenRA.Mods.Common.Traits throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}"); GenerateFootprint(); - UpdateFromCellChange(null); + UpdateFromCellChange(); tooltip = Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase ?? Info.TraitInfos().FirstOrDefault(info => info.EnabledByDefault); @@ -78,8 +77,6 @@ namespace OpenRA.Mods.Common.Traits terrainRadarColorInfo = Info.TraitInfoOrDefault(); UpdateRadarColor(); - - onCellEntryChanged = cell => UpdateFromCellChange(cell); } public EditorActorPreview WithId(string id) @@ -87,11 +84,8 @@ namespace OpenRA.Mods.Common.Traits 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); GeneratePreviews(); GenerateBounds(); @@ -183,18 +177,12 @@ namespace OpenRA.Mods.Common.Traits { foreach (var notify in Info.TraitInfos()) editorData[notify] = notify.AddedToEditor(this, worldRenderer.World); - - worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged; - worldRenderer.World.Map.Ramp.CellEntryChanged += onCellEntryChanged; } public void RemovedFromEditor() { foreach (var kv in editorData) kv.Key.RemovedFromEditor(this, worldRenderer.World, kv.Value); - - worldRenderer.World.Map.Height.CellEntryChanged -= onCellEntryChanged; - worldRenderer.World.Map.Ramp.CellEntryChanged -= onCellEntryChanged; } public void AddInit(T init) where T : ActorInit