Significantly improve actor placement and removal speeds
This commit is contained in:
@@ -572,6 +572,11 @@ namespace OpenRA
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this uint i)
|
||||
{
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this float f)
|
||||
{
|
||||
return f.ToString(NumberFormatInfo.InvariantInfo);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -89,6 +90,31 @@ namespace OpenRA
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
||||
public static CellCoordsRegion BoundingRegion(IReadOnlyCollection<CPos> cells)
|
||||
{
|
||||
if (cells == null || cells.Count == 0)
|
||||
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
||||
|
||||
var minX = int.MaxValue;
|
||||
var minY = int.MaxValue;
|
||||
var maxX = int.MinValue;
|
||||
var maxY = int.MinValue;
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (minX > cell.X)
|
||||
minX = cell.X;
|
||||
if (maxX < cell.X)
|
||||
maxX = cell.X;
|
||||
if (minY > cell.Y)
|
||||
minY = cell.Y;
|
||||
if (maxY < cell.Y)
|
||||
maxY = cell.Y;
|
||||
}
|
||||
|
||||
return new CellCoordsRegion(new CPos(minX, minY), new CPos(maxX, maxY));
|
||||
}
|
||||
|
||||
public CPos TopLeft { get; }
|
||||
public CPos BottomRight { get; }
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.EditorBrushes
|
||||
{
|
||||
@@ -130,8 +132,7 @@ namespace OpenRA.Mods.Common.EditorBrushes
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Clear any existing actors in the paste cells.
|
||||
var regionActors = editorActorLayer.PreviewsInCellRegion(blitRegion.CellCoords).ToList();
|
||||
|
||||
//
|
||||
// revertBlitSource's mask may be a superset of the commitBlitSource's mask if
|
||||
// - Its a sparse blit; and
|
||||
// - The revert actors removed by the commit are partially outside of the commit mask.
|
||||
@@ -146,9 +147,8 @@ namespace OpenRA.Mods.Common.EditorBrushes
|
||||
// This means we use the commit mask, not the revert one.
|
||||
var commitBlitVec = blitPosition - commitBlitSource.CellRegion.TopLeft;
|
||||
var mask = GetBlitSourceMask(commitBlitSource, commitBlitVec);
|
||||
foreach (var regionActor in regionActors)
|
||||
if (regionActor.Footprint.Any(kv => mask.Contains(kv.Key)))
|
||||
editorActorLayer.Remove(regionActor);
|
||||
using (new PerfTimer("RemoveActors", 1))
|
||||
editorActorLayer.RemoveRegion(blitRegion.CellCoords, mask);
|
||||
}
|
||||
|
||||
foreach (var tileKeyValuePair in source.Tiles)
|
||||
@@ -181,12 +181,13 @@ namespace OpenRA.Mods.Common.EditorBrushes
|
||||
if (isRevert)
|
||||
{
|
||||
// For reverts, just place the original actors back exactly how they were.
|
||||
foreach (var actor in source.Actors.Values)
|
||||
editorActorLayer.Add(actor);
|
||||
using (new PerfTimer("AddActors", 1))
|
||||
editorActorLayer.AddRange(source.Actors.Values.ToArray().AsSpan());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create copies of the original actors, update their locations, and place.
|
||||
var copies = new List<ActorReference>(source.Actors.Count);
|
||||
foreach (var actorKeyValuePair in source.Actors)
|
||||
{
|
||||
var copy = actorKeyValuePair.Value.Export();
|
||||
@@ -201,8 +202,11 @@ namespace OpenRA.Mods.Common.EditorBrushes
|
||||
copy.Add(new LocationInit(actorPosition));
|
||||
}
|
||||
|
||||
editorActorLayer.Add(copy);
|
||||
copies.Add(copy);
|
||||
}
|
||||
|
||||
using (new PerfTimer("AddActors", 1))
|
||||
editorActorLayer.AddRange(CollectionsMarshal.AsSpan(copies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.EditorBrushes;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
@@ -384,8 +385,8 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Clear any existing actors in the paste cells.
|
||||
foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(area.CellCoords).ToList())
|
||||
editorActorLayer.Remove(regionActor);
|
||||
using (new PerfTimer("RemoveActors", 1))
|
||||
editorActorLayer.RemoveRegion(area.CellCoords);
|
||||
}
|
||||
|
||||
foreach (var tileKeyValuePair in editorBlitSource.Tiles)
|
||||
@@ -432,6 +433,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
if (blitFilters.HasFlag(MapBlitFilters.Actors))
|
||||
{
|
||||
// Create copies of the original actors, update their locations, and place.
|
||||
var copies = new List<ActorReference>(editorBlitSource.Actors.Count);
|
||||
foreach (var actorKeyValuePair in editorBlitSource.Actors)
|
||||
{
|
||||
var copy = actorKeyValuePair.Value.Export();
|
||||
@@ -445,8 +447,10 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
copy.Add(new LocationInit(locationInit.Value));
|
||||
}
|
||||
|
||||
editorActorLayer.Add(copy);
|
||||
copies.Add(copy);
|
||||
}
|
||||
|
||||
editorActorLayer.AddRange(CollectionsMarshal.AsSpan(copies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Network;
|
||||
@@ -46,6 +48,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public readonly EditorActorLayerInfo Info;
|
||||
readonly List<EditorActorPreview> previews = [];
|
||||
readonly HashSet<uint> previewIds = [];
|
||||
|
||||
int2 cellOffset;
|
||||
SpatiallyPartitioned<EditorActorPreview> cellMap;
|
||||
@@ -86,12 +89,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var height = world.Map.MapSize.Height * ts.Height;
|
||||
screenMap = new SpatiallyPartitioned<EditorActorPreview>(width, height, Info.BinSize);
|
||||
|
||||
foreach (var kv in world.Map.ActorDefinitions)
|
||||
Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.ToDictionary()), true);
|
||||
var names = new string[world.Map.ActorDefinitions.Count];
|
||||
var references = new List<ActorReference>(world.Map.ActorDefinitions.Count);
|
||||
|
||||
// Update neighbours in one pass
|
||||
foreach (var p in previews)
|
||||
UpdateNeighbours(p.Footprint);
|
||||
for (var i = 0; i < world.Map.ActorDefinitions.Count; i++)
|
||||
{
|
||||
var kv = world.Map.ActorDefinitions.ElementAt(i);
|
||||
names[i] = kv.Key;
|
||||
references.Add(new ActorReference(kv.Value.Value, kv.Value.ToDictionary()));
|
||||
}
|
||||
|
||||
AddRange(CollectionsMarshal.AsSpan(references), names);
|
||||
}
|
||||
|
||||
void ITickRender.TickRender(WorldRenderer wr, Actor self)
|
||||
@@ -121,9 +129,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
bool IRenderAnnotations.SpatiallyPartitionable => false;
|
||||
|
||||
public EditorActorPreview Add(ActorReference reference) { return Add(NextActorName(), reference); }
|
||||
IEnumerable<CPos> OccupiedCells(EditorActorPreview preview)
|
||||
{
|
||||
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
|
||||
if (preview.Footprint.Count == 0)
|
||||
return [worldRenderer.World.Map.CellContaining(preview.CenterPosition)];
|
||||
return preview.Footprint.Keys;
|
||||
}
|
||||
|
||||
public EditorActorPreview Add(string id, ActorReference reference, bool initialSetup = false)
|
||||
PlayerReference GetOrAddOwner(ActorReference reference)
|
||||
{
|
||||
// If an actor's doesn't have a valid owner transfer ownership to neutral
|
||||
var ownerInit = reference.Get<OwnerInit>();
|
||||
@@ -134,43 +148,97 @@ namespace OpenRA.Mods.Common.Traits
|
||||
reference.Add(new OwnerInit(worldOwner.Name));
|
||||
}
|
||||
|
||||
var preview = new EditorActorPreview(worldRenderer, id, reference, owner);
|
||||
Add(preview, initialSetup);
|
||||
return owner;
|
||||
}
|
||||
|
||||
public EditorActorPreview Add(ActorReference reference)
|
||||
{
|
||||
var owner = GetOrAddOwner(reference);
|
||||
var preview = new EditorActorPreview(worldRenderer, NextActorName(), reference, owner);
|
||||
Add(preview);
|
||||
return preview;
|
||||
}
|
||||
|
||||
public void Add(EditorActorPreview preview, bool initialSetup = false)
|
||||
public void AddRange(ReadOnlySpan<ActorReference> references, ReadOnlySpan<string> names)
|
||||
{
|
||||
if (names.Length != references.Length)
|
||||
throw new ArgumentException("Member name count must match reference count.");
|
||||
|
||||
var newPreviews = new EditorActorPreview[names.Length];
|
||||
using (new PerfTimer("CreatePreviews"))
|
||||
{
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var id = names[i];
|
||||
var reference = references[i];
|
||||
var owner = GetOrAddOwner(reference);
|
||||
|
||||
newPreviews[i] = new EditorActorPreview(worldRenderer, id, reference, owner);
|
||||
}
|
||||
}
|
||||
|
||||
AddRange(newPreviews);
|
||||
}
|
||||
|
||||
public void AddRange(ReadOnlySpan<ActorReference> references)
|
||||
{
|
||||
AddRange(references, NextActorNames(references.Length));
|
||||
}
|
||||
|
||||
public void Add(EditorActorPreview preview)
|
||||
{
|
||||
previews.Add(preview);
|
||||
if (TryGetActorId(preview.ID, out var id))
|
||||
previewIds.Add(id);
|
||||
|
||||
if (!preview.Bounds.IsEmpty)
|
||||
screenMap.Add(preview, preview.Bounds);
|
||||
|
||||
var cellFootprintBounds = Footprint(preview).Select(
|
||||
var cellFootprintBounds = OccupiedCells(preview).Select(
|
||||
cell => new Rectangle(cell.X - cellOffset.X, cell.Y - cellOffset.Y, 1, 1)).Union();
|
||||
|
||||
cellMap.Add(preview, cellFootprintBounds);
|
||||
|
||||
preview.AddedToEditor();
|
||||
UpdateNeighbours(preview.Footprint);
|
||||
|
||||
if (!initialSetup)
|
||||
{
|
||||
UpdateNeighbours(preview.Footprint);
|
||||
|
||||
if (preview.Type == PlayerSpawnName)
|
||||
SyncMultiplayerCount();
|
||||
}
|
||||
if (preview.Type == PlayerSpawnName)
|
||||
SyncMultiplayerCount();
|
||||
}
|
||||
|
||||
IEnumerable<CPos> Footprint(EditorActorPreview preview)
|
||||
public void AddRange(ReadOnlySpan<EditorActorPreview> newPreviews)
|
||||
{
|
||||
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
|
||||
if (preview.Footprint.Count == 0)
|
||||
return [worldRenderer.World.Map.CellContaining(preview.CenterPosition)];
|
||||
return preview.Footprint.Keys;
|
||||
previews.AddRange(newPreviews);
|
||||
previewIds.EnsureCapacity(previews.Count * 2);
|
||||
|
||||
foreach (var preview in newPreviews)
|
||||
{
|
||||
if (TryGetActorId(preview.ID, out var id))
|
||||
previewIds.Add(id);
|
||||
|
||||
if (!preview.Bounds.IsEmpty)
|
||||
screenMap.Add(preview, preview.Bounds);
|
||||
|
||||
var cellFootprintBounds = OccupiedCells(preview)
|
||||
.Select(cell => new Rectangle(cell.X - cellOffset.X, cell.Y - cellOffset.Y, 1, 1)).Union();
|
||||
|
||||
cellMap.Add(preview, cellFootprintBounds);
|
||||
|
||||
preview.AddedToEditor();
|
||||
}
|
||||
|
||||
using (new PerfTimer("UpdateNeighbours"))
|
||||
UpdateNeighbours(newPreviews);
|
||||
|
||||
SyncMultiplayerCount();
|
||||
}
|
||||
|
||||
public void Remove(EditorActorPreview preview)
|
||||
{
|
||||
previews.Remove(preview);
|
||||
if (TryGetActorId(preview.ID, out var id))
|
||||
previewIds.Remove(id);
|
||||
|
||||
screenMap.Remove(preview);
|
||||
|
||||
cellMap.Remove(preview);
|
||||
@@ -182,6 +250,38 @@ namespace OpenRA.Mods.Common.Traits
|
||||
SyncMultiplayerCount();
|
||||
}
|
||||
|
||||
public void RemoveRange(ReadOnlySpan<EditorActorPreview> removePreviews)
|
||||
{
|
||||
foreach (var preview in removePreviews)
|
||||
{
|
||||
previews.Remove(preview);
|
||||
if (TryGetActorId(preview.ID, out var id))
|
||||
previewIds.Remove(id);
|
||||
|
||||
screenMap.Remove(preview);
|
||||
cellMap.Remove(preview);
|
||||
}
|
||||
|
||||
using (new PerfTimer("RemovedFromEditor", 1))
|
||||
foreach (var preview in removePreviews)
|
||||
preview.RemovedFromEditor();
|
||||
|
||||
using (new PerfTimer("UpdateNeighbours", 1))
|
||||
UpdateNeighbours(removePreviews);
|
||||
|
||||
SyncMultiplayerCount();
|
||||
}
|
||||
|
||||
public void RemoveRegion(CellCoordsRegion region)
|
||||
{
|
||||
RemoveRange(PreviewsInCellRegion(region).ToArray().AsSpan());
|
||||
}
|
||||
|
||||
public void RemoveRegion(CellCoordsRegion region, HashSet<CPos> mask)
|
||||
{
|
||||
RemoveRange(PreviewsInCellRegion(region).Where(p => mask.Overlaps(p.Footprint.Keys)).ToArray().AsSpan());
|
||||
}
|
||||
|
||||
public void MoveActor(EditorActorPreview preview, CPos location)
|
||||
{
|
||||
Remove(preview);
|
||||
@@ -203,9 +303,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
void SyncMultiplayerCount()
|
||||
{
|
||||
var newCount = previews.Count(p => p.Info.Name == PlayerSpawnName);
|
||||
var mp = Players.Players.Where(p => p.Key.StartsWith("Multi", StringComparison.Ordinal)).ToList();
|
||||
foreach (var kv in mp)
|
||||
var playersChanged = false;
|
||||
foreach (var kv in Players.Players)
|
||||
{
|
||||
if (!kv.Key.StartsWith("Multi", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var name = kv.Key;
|
||||
var index = Exts.ParseInt32Invariant(name[5..]);
|
||||
|
||||
@@ -213,6 +316,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
Players.Players.Remove(name);
|
||||
OnPlayerRemoved();
|
||||
playersChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,13 +335,34 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
Players.Players.Add(pr.Name, pr);
|
||||
worldRenderer.UpdatePalettesForPlayer(pr.Name, pr.Color, true);
|
||||
playersChanged = true;
|
||||
}
|
||||
|
||||
if (!playersChanged)
|
||||
return;
|
||||
|
||||
var creeps = Players.Players.Keys.FirstOrDefault(p => p == "Creeps");
|
||||
if (!string.IsNullOrEmpty(creeps))
|
||||
Players.Players[creeps].Enemies = Players.Players.Keys.Where(p => !Players.Players[p].NonCombatant).ToArray();
|
||||
}
|
||||
|
||||
void UpdateNeighbours(ReadOnlySpan<EditorActorPreview> previews)
|
||||
{
|
||||
var cells = new HashSet<CPos>(previews.Length * 6);
|
||||
foreach (var preview in previews)
|
||||
cells.UnionWith(Util.ExpandFootprint(preview.Footprint.Keys, true));
|
||||
|
||||
if (cells.Count == 0)
|
||||
return;
|
||||
|
||||
var bounds = CellCoordsRegion.BoundingRegion(cells);
|
||||
var touchedPreviews = PreviewsInCellRegion(bounds)
|
||||
.Where(p => cells.Overlaps(p.Footprint.Keys));
|
||||
|
||||
foreach (var p in touchedPreviews)
|
||||
p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint)));
|
||||
}
|
||||
|
||||
void UpdateNeighbours(IReadOnlyDictionary<CPos, SubCell> footprint)
|
||||
{
|
||||
// Include actors inside the footprint too
|
||||
@@ -269,20 +394,20 @@ namespace OpenRA.Mods.Common.Traits
|
||||
region.TopLeft.Y - cellOffset.Y,
|
||||
region.BottomRight.X - cellOffset.X + 1,
|
||||
region.BottomRight.Y - cellOffset.Y + 1))
|
||||
.Where(p => Footprint(p).Any(region.Contains));
|
||||
.Where(p => OccupiedCells(p).Any(region.Contains));
|
||||
}
|
||||
|
||||
public IEnumerable<EditorActorPreview> PreviewsAtCell(CPos cell)
|
||||
{
|
||||
return cellMap.At(new int2(cell.X - cellOffset.X, cell.Y - cellOffset.Y))
|
||||
.Where(p => Footprint(p).Any(fp => fp == cell));
|
||||
.Where(p => OccupiedCells(p).Any(fp => fp == cell));
|
||||
}
|
||||
|
||||
public SubCell FreeSubCellAt(CPos cell)
|
||||
{
|
||||
var map = worldRenderer.World.Map;
|
||||
var previews = PreviewsAtCell(cell).ToList();
|
||||
if (previews.Count == 0)
|
||||
var previews = PreviewsAtCell(cell).ToArray();
|
||||
if (previews.Length == 0)
|
||||
return map.Grid.DefaultSubCell;
|
||||
|
||||
for (var i = (byte)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++)
|
||||
@@ -303,18 +428,32 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public Action OnPlayerRemoved = () => { };
|
||||
|
||||
static bool TryGetActorId(string name, out uint id)
|
||||
{
|
||||
id = 0;
|
||||
return name.StartsWith(ActorPrefix, StringComparison.Ordinal)
|
||||
&& uint.TryParse(name.AsSpan(5), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out id);
|
||||
}
|
||||
|
||||
string NextActorName()
|
||||
{
|
||||
var id = previews.Count;
|
||||
var possibleName = ActorPrefix + id.ToStringInvariant();
|
||||
var currentId = 0u;
|
||||
while (previewIds.Contains(currentId))
|
||||
currentId++;
|
||||
|
||||
while (previews.Any(p => p.ID == possibleName))
|
||||
{
|
||||
id++;
|
||||
possibleName = ActorPrefix + id.ToStringInvariant();
|
||||
}
|
||||
return ActorPrefix + currentId.ToStringInvariant();
|
||||
}
|
||||
|
||||
return possibleName;
|
||||
ReadOnlySpan<string> NextActorNames(int count)
|
||||
{
|
||||
var newNamesCount = 0u;
|
||||
var newNames = new string[count];
|
||||
|
||||
for (var currentId = 0u; newNamesCount < count; currentId++)
|
||||
if (!previewIds.Contains(currentId))
|
||||
newNames[newNamesCount++] = ActorPrefix + currentId.ToStringInvariant();
|
||||
|
||||
return newNames;
|
||||
}
|
||||
|
||||
public List<MiniYamlNode> Save()
|
||||
@@ -329,7 +468,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public void PopulateRadarSignatureCells(Actor self, List<(CPos Cell, Color Color)> destinationBuffer)
|
||||
{
|
||||
foreach (var preview in cellMap.Keys)
|
||||
foreach (var cell in Footprint(preview))
|
||||
foreach (var cell in OccupiedCells(preview))
|
||||
destinationBuffer.Add((cell, preview.RadarColor));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user