Improve performance of copy-paste in map editor.

- EditorActorLayer now tracks previews on map with a SpatiallyPartitioned instead of a Dictionary. This allows the copy-paste logic to call an efficient PreviewsInCellRegion method, instead of asking for previews cell-by-cell.
- EditorActorPreview subscribes to the CellEntryChanged methods on the map. Previously the preview was refreshed regardless of which cell changed. Now the preview only regenerates if the preview's footprint has been affected.
This commit is contained in:
RoosterDragon
2024-06-14 16:55:50 +01:00
committed by Gustas
parent 34a68cd2ca
commit cf0e73e75e
7 changed files with 63 additions and 62 deletions

View File

@@ -64,6 +64,11 @@ namespace OpenRA
BottomRight = bottomRight; BottomRight = bottomRight;
} }
public bool Contains(CPos cell)
{
return cell.X >= TopLeft.X && cell.X <= BottomRight.X && cell.Y >= TopLeft.Y && cell.Y <= BottomRight.Y;
}
public override string ToString() public override string ToString()
{ {
return $"{TopLeft}->{BottomRight}"; return $"{TopLeft}->{BottomRight}";

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits
return false; return false;
// Cell is automatically valid if it contains a veinhole actor // Cell is automatically valid if it contains a veinhole actor
if (actorLayer.PreviewsAt(neighbour).Any(a => info.VeinholeActors.Contains(a.Info.Name))) if (actorLayer.PreviewsAtCell(neighbour).Any(a => info.VeinholeActors.Contains(a.Info.Name)))
return true; return true;
// Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope

View File

@@ -165,7 +165,7 @@ namespace OpenRA.Mods.Common.Widgets
tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayerContents, mapHeight[cell])); tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayerContents, mapHeight[cell]));
if (copyFilters.HasFlag(MapCopyFilters.Actors)) if (copyFilters.HasFlag(MapCopyFilters.Actors))
foreach (var preview in selection.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct()) foreach (var preview in editorActorLayer.PreviewsInCellRegion(selection.CellCoords))
previews.TryAdd(preview.ID, preview); previews.TryAdd(preview.ID, preview);
} }
@@ -187,7 +187,7 @@ namespace OpenRA.Mods.Common.Widgets
// Clear any existing actors in the paste cells. // Clear any existing actors in the paste cells.
var selectionSize = clipboard.CellRegion.BottomRight - clipboard.CellRegion.TopLeft; var selectionSize = clipboard.CellRegion.BottomRight - clipboard.CellRegion.TopLeft;
var pasteRegion = new CellRegion(map.Grid.Type, pastePosition, pastePosition + selectionSize); var pasteRegion = new CellRegion(map.Grid.Type, pastePosition, pastePosition + selectionSize);
foreach (var regionActor in pasteRegion.CellCoords.SelectMany(editorActorLayer.PreviewsAt).ToHashSet()) foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(pasteRegion.CellCoords).ToList())
editorActorLayer.Remove(regionActor); editorActorLayer.Remove(regionActor);
} }
@@ -244,7 +244,7 @@ namespace OpenRA.Mods.Common.Widgets
if (copyFilters.HasFlag(MapCopyFilters.Actors)) if (copyFilters.HasFlag(MapCopyFilters.Actors))
{ {
// Clear existing actors. // Clear existing actors.
foreach (var regionActor in undoClipboard.CellRegion.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct().ToList()) foreach (var regionActor in editorActorLayer.PreviewsInCellRegion(undoClipboard.CellRegion.CellCoords).ToList())
editorActorLayer.Remove(regionActor); editorActorLayer.Remove(regionActor);
} }

View File

@@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.Widgets
worldPixel = worldRenderer.Viewport.ViewToWorldPx(mi.Location); worldPixel = worldRenderer.Viewport.ViewToWorldPx(mi.Location);
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
var underCursor = editorLayer.PreviewsAt(worldPixel).MinByOrDefault(CalculateActorSelectionPriority); var underCursor = editorLayer.PreviewsAtWorldPixel(worldPixel).MinByOrDefault(CalculateActorSelectionPriority);
var resourceUnderCursor = resourceLayer?.GetResource(cell).Type; var resourceUnderCursor = resourceLayer?.GetResource(cell).Type;
if (underCursor != null) if (underCursor != null)

View File

@@ -40,8 +40,9 @@ namespace OpenRA.Mods.Common.Traits
{ {
readonly EditorActorLayerInfo info; readonly EditorActorLayerInfo info;
readonly List<EditorActorPreview> previews = new(); readonly List<EditorActorPreview> previews = new();
readonly Dictionary<CPos, List<EditorActorPreview>> cellMap = new();
int2 cellOffset;
SpatiallyPartitioned<EditorActorPreview> cellMap;
SpatiallyPartitioned<EditorActorPreview> screenMap; SpatiallyPartitioned<EditorActorPreview> screenMap;
WorldRenderer worldRenderer; WorldRenderer worldRenderer;
@@ -74,6 +75,12 @@ namespace OpenRA.Mods.Common.Traits
foreach (var pr in Players.Players.Values) foreach (var pr in Players.Players.Values)
wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false); wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false);
cellOffset = new int2(world.Map.AllCells.Min(c => c.X), world.Map.AllCells.Min((c) => c.Y));
var cellOffsetMax = new int2(world.Map.AllCells.Max(c => c.X), world.Map.AllCells.Max((c) => c.Y));
var mapCellSize = cellOffsetMax - cellOffset;
cellMap = new SpatiallyPartitioned<EditorActorPreview>(
mapCellSize.X, mapCellSize.Y, Exts.IntegerDivisionRoundingAwayFromZero(info.BinSize, world.Map.Grid.TileSize.Width));
var ts = world.Map.Grid.TileSize; var ts = world.Map.Grid.TileSize;
var width = world.Map.MapSize.X * ts.Width; var width = world.Map.MapSize.X * ts.Width;
var height = world.Map.MapSize.Y * ts.Height; var height = world.Map.MapSize.Y * ts.Height;
@@ -102,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits
if (wr.World.Type != WorldType.Editor) if (wr.World.Type != WorldType.Editor)
return NoRenderables; return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.Render()); .SelectMany(p => p.Render());
} }
@@ -117,7 +124,7 @@ namespace OpenRA.Mods.Common.Traits
if (wr.World.Type != WorldType.Editor) if (wr.World.Type != WorldType.Editor)
return NoRenderables; return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight) return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.RenderAnnotations()); .SelectMany(p => p.RenderAnnotations());
} }
@@ -147,13 +154,9 @@ namespace OpenRA.Mods.Common.Traits
if (!preview.Bounds.IsEmpty) if (!preview.Bounds.IsEmpty)
screenMap.Add(preview, preview.Bounds); screenMap.Add(preview, preview.Bounds);
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint var cellFootprintBounds = Footprint(preview).Select(
var footprint = preview.Footprint.Select(kv => kv.Key).ToArray(); cell => new Rectangle(cell.X - cellOffset.X, cell.Y - cellOffset.Y, 1, 1)).Union();
if (footprint.Length == 0) cellMap.Add(preview, cellFootprintBounds);
footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) };
foreach (var cell in footprint)
AddPreviewLocation(preview, cell);
preview.AddedToEditor(); preview.AddedToEditor();
@@ -166,26 +169,20 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
IEnumerable<CPos> Footprint(EditorActorPreview preview)
{
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
if (preview.Footprint.Count == 0)
return new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) };
return preview.Footprint.Keys;
}
public void Remove(EditorActorPreview preview) public void Remove(EditorActorPreview preview)
{ {
previews.Remove(preview); previews.Remove(preview);
screenMap.Remove(preview); screenMap.Remove(preview);
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint cellMap.Remove(preview);
var footprint = preview.Footprint.Select(kv => kv.Key).ToArray();
if (footprint.Length == 0)
footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) };
foreach (var cell in footprint)
{
if (!cellMap.TryGetValue(cell, out var list))
continue;
list.Remove(preview);
if (list.Count == 0)
cellMap.Remove(cell);
}
preview.RemovedFromEditor(); preview.RemovedFromEditor();
UpdateNeighbours(preview.Footprint); UpdateNeighbours(preview.Footprint);
@@ -236,49 +233,46 @@ namespace OpenRA.Mods.Common.Traits
{ {
// Include actors inside the footprint too // Include actors inside the footprint too
var cells = Util.ExpandFootprint(footprint.Keys, true); var cells = Util.ExpandFootprint(footprint.Keys, true);
foreach (var p in cells.SelectMany(c => PreviewsAt(c))) foreach (var p in cells.SelectMany(PreviewsAtCell))
p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint))); p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint)));
} }
void AddPreviewLocation(EditorActorPreview preview, CPos location)
{
if (!cellMap.TryGetValue(location, out var list))
{
list = new List<EditorActorPreview>();
cellMap.Add(location, list);
}
list.Add(preview);
}
Dictionary<CPos, string[]> NeighbouringPreviews(IReadOnlyDictionary<CPos, SubCell> footprint) Dictionary<CPos, string[]> NeighbouringPreviews(IReadOnlyDictionary<CPos, SubCell> footprint)
{ {
var cells = Util.ExpandFootprint(footprint.Keys, true).Except(footprint.Keys); var cells = Util.ExpandFootprint(footprint.Keys, true).Except(footprint.Keys);
return cells.ToDictionary(c => c, c => PreviewsAt(c).Select(p => p.Info.Name).ToArray()); return cells.ToDictionary(c => c, c => PreviewsAtCell(c).Select(p => p.Info.Name).ToArray());
} }
public IEnumerable<EditorActorPreview> PreviewsInBox(int2 a, int2 b) public IEnumerable<EditorActorPreview> PreviewsInScreenBox(int2 a, int2 b)
{ {
return screenMap.InBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y))); return PreviewsInScreenBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)));
} }
public IEnumerable<EditorActorPreview> PreviewsInBox(Rectangle r) public IEnumerable<EditorActorPreview> PreviewsInScreenBox(Rectangle r)
{ {
return screenMap.InBox(r); return screenMap.InBox(r);
} }
public IEnumerable<EditorActorPreview> PreviewsAt(CPos cell) public IEnumerable<EditorActorPreview> PreviewsInCellRegion(CellCoordsRegion region)
{ {
if (cellMap.TryGetValue(cell, out var list)) return cellMap.InBox(Rectangle.FromLTRB(
return list; region.TopLeft.X - cellOffset.X,
region.TopLeft.Y - cellOffset.Y,
region.BottomRight.X - cellOffset.X + 1,
region.BottomRight.Y - cellOffset.Y + 1))
.Where(p => Footprint(p).Any(region.Contains));
}
return Enumerable.Empty<EditorActorPreview>(); 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));
} }
public SubCell FreeSubCellAt(CPos cell) public SubCell FreeSubCellAt(CPos cell)
{ {
var map = worldRenderer.World.Map; var map = worldRenderer.World.Map;
var previews = PreviewsAt(cell).ToList(); var previews = PreviewsAtCell(cell).ToList();
if (previews.Count == 0) if (previews.Count == 0)
return map.Grid.DefaultSubCell; return map.Grid.DefaultSubCell;
@@ -293,7 +287,7 @@ namespace OpenRA.Mods.Common.Traits
return SubCell.Invalid; return SubCell.Invalid;
} }
public IEnumerable<EditorActorPreview> PreviewsAt(int2 worldPx) public IEnumerable<EditorActorPreview> PreviewsAtWorldPixel(int2 worldPx)
{ {
return screenMap.At(worldPx); return screenMap.At(worldPx);
} }
@@ -325,9 +319,9 @@ namespace OpenRA.Mods.Common.Traits
public void PopulateRadarSignatureCells(Actor self, List<(CPos Cell, Color Color)> destinationBuffer) public void PopulateRadarSignatureCells(Actor self, List<(CPos Cell, Color Color)> destinationBuffer)
{ {
foreach (var previewsForCell in cellMap) foreach (var preview in cellMap.Items)
foreach (var preview in previewsForCell.Value) foreach (var cell in Footprint(preview))
destinationBuffer.Add((previewsForCell.Key, preview.RadarColor)); destinationBuffer.Add((cell, preview.RadarColor));
} }
public EditorActorPreview this[string id] public EditorActorPreview this[string id]

View File

@@ -68,8 +68,8 @@ namespace OpenRA.Mods.Common.Traits
if (!world.Map.Rules.Actors.TryGetValue(reference.Type.ToLowerInvariant(), out Info)) if (!world.Map.Rules.Actors.TryGetValue(reference.Type.ToLowerInvariant(), out Info))
throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}"); throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}");
UpdateFromCellChange();
GenerateFootprint(); GenerateFootprint();
UpdateFromCellChange(null);
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);
@@ -79,8 +79,7 @@ namespace OpenRA.Mods.Common.Traits
terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>(); terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>();
UpdateRadarColor(); UpdateRadarColor();
// TODO: updating all actors on the map is not very efficient. onCellEntryChanged = cell => UpdateFromCellChange(cell);
onCellEntryChanged = _ => UpdateFromCellChange();
} }
public EditorActorPreview WithId(string id) public EditorActorPreview WithId(string id)
@@ -88,8 +87,11 @@ namespace OpenRA.Mods.Common.Traits
return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner); return new EditorActorPreview(worldRenderer, id, reference.Clone(), Owner);
} }
void UpdateFromCellChange() void UpdateFromCellChange(CPos? cellChanged)
{ {
if (cellChanged != null && !Footprint.ContainsKey(cellChanged.Value))
return;
CenterPosition = PreviewPosition(worldRenderer.World, reference); CenterPosition = PreviewPosition(worldRenderer.World, reference);
GeneratePreviews(); GeneratePreviews();
GenerateBounds(); GenerateBounds();
@@ -169,8 +171,8 @@ 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);
// TODO: this should subscribe to ramp cell map as well.
worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged; worldRenderer.World.Map.Height.CellEntryChanged += onCellEntryChanged;
worldRenderer.World.Map.Ramp.CellEntryChanged += onCellEntryChanged;
} }
public void RemovedFromEditor() public void RemovedFromEditor()
@@ -179,6 +181,7 @@ namespace OpenRA.Mods.Common.Traits
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.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

View File

@@ -11,7 +11,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.EditorBrushes; using OpenRA.Mods.Common.EditorBrushes;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
@@ -123,7 +122,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayer?.GetResource(cell), mapHeight[cell])); tiles.Add(cell, new ClipboardTile(mapTiles[cell], mapResources[cell], resourceLayer?.GetResource(cell), mapHeight[cell]));
if (copyFilters.HasFlag(MapCopyFilters.Actors)) if (copyFilters.HasFlag(MapCopyFilters.Actors))
foreach (var preview in selection.CellCoords.SelectMany(editorActorLayer.PreviewsAt).Distinct()) foreach (var preview in editorActorLayer.PreviewsInCellRegion(selection.CellCoords))
previews.TryAdd(preview.ID, preview); previews.TryAdd(preview.ID, preview);
} }