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;
}
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()
{
return $"{TopLeft}->{BottomRight}";

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits
return false;
// 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;
// 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]));
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);
}
@@ -187,7 +187,7 @@ namespace OpenRA.Mods.Common.Widgets
// Clear any existing actors in the paste cells.
var selectionSize = clipboard.CellRegion.BottomRight - clipboard.CellRegion.TopLeft;
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);
}
@@ -244,7 +244,7 @@ namespace OpenRA.Mods.Common.Widgets
if (copyFilters.HasFlag(MapCopyFilters.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);
}

View File

@@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.Widgets
worldPixel = worldRenderer.Viewport.ViewToWorldPx(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;
if (underCursor != null)

View File

@@ -40,8 +40,9 @@ namespace OpenRA.Mods.Common.Traits
{
readonly EditorActorLayerInfo info;
readonly List<EditorActorPreview> previews = new();
readonly Dictionary<CPos, List<EditorActorPreview>> cellMap = new();
int2 cellOffset;
SpatiallyPartitioned<EditorActorPreview> cellMap;
SpatiallyPartitioned<EditorActorPreview> screenMap;
WorldRenderer worldRenderer;
@@ -74,6 +75,12 @@ namespace OpenRA.Mods.Common.Traits
foreach (var pr in Players.Players.Values)
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 width = world.Map.MapSize.X * ts.Width;
var height = world.Map.MapSize.Y * ts.Height;
@@ -102,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits
if (wr.World.Type != WorldType.Editor)
return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.Render());
}
@@ -117,7 +124,7 @@ namespace OpenRA.Mods.Common.Traits
if (wr.World.Type != WorldType.Editor)
return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
return PreviewsInScreenBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.RenderAnnotations());
}
@@ -147,13 +154,9 @@ namespace OpenRA.Mods.Common.Traits
if (!preview.Bounds.IsEmpty)
screenMap.Add(preview, preview.Bounds);
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
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)
AddPreviewLocation(preview, cell);
var cellFootprintBounds = Footprint(preview).Select(
cell => new Rectangle(cell.X - cellOffset.X, cell.Y - cellOffset.Y, 1, 1)).Union();
cellMap.Add(preview, cellFootprintBounds);
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)
{
previews.Remove(preview);
screenMap.Remove(preview);
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
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);
}
cellMap.Remove(preview);
preview.RemovedFromEditor();
UpdateNeighbours(preview.Footprint);
@@ -236,49 +233,46 @@ namespace OpenRA.Mods.Common.Traits
{
// Include actors inside the footprint too
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)));
}
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)
{
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);
}
public IEnumerable<EditorActorPreview> PreviewsAt(CPos cell)
public IEnumerable<EditorActorPreview> PreviewsInCellRegion(CellCoordsRegion region)
{
if (cellMap.TryGetValue(cell, out var list))
return list;
return cellMap.InBox(Rectangle.FromLTRB(
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)
{
var map = worldRenderer.World.Map;
var previews = PreviewsAt(cell).ToList();
var previews = PreviewsAtCell(cell).ToList();
if (previews.Count == 0)
return map.Grid.DefaultSubCell;
@@ -293,7 +287,7 @@ namespace OpenRA.Mods.Common.Traits
return SubCell.Invalid;
}
public IEnumerable<EditorActorPreview> PreviewsAt(int2 worldPx)
public IEnumerable<EditorActorPreview> PreviewsAtWorldPixel(int2 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)
{
foreach (var previewsForCell in cellMap)
foreach (var preview in previewsForCell.Value)
destinationBuffer.Add((previewsForCell.Key, preview.RadarColor));
foreach (var preview in cellMap.Items)
foreach (var cell in Footprint(preview))
destinationBuffer.Add((cell, preview.RadarColor));
}
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))
throw new InvalidDataException($"Actor {id} of unknown type {reference.Type.ToLowerInvariant()}");
UpdateFromCellChange();
GenerateFootprint();
UpdateFromCellChange(null);
tooltip = Info.TraitInfos<EditorOnlyTooltipInfo>().FirstOrDefault(info => info.EnabledByDefault) as TooltipInfoBase
?? Info.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
@@ -79,8 +79,7 @@ namespace OpenRA.Mods.Common.Traits
terrainRadarColorInfo = Info.TraitInfoOrDefault<RadarColorFromTerrainInfo>();
UpdateRadarColor();
// TODO: updating all actors on the map is not very efficient.
onCellEntryChanged = _ => UpdateFromCellChange();
onCellEntryChanged = cell => UpdateFromCellChange(cell);
}
public EditorActorPreview WithId(string id)
@@ -88,8 +87,11 @@ namespace OpenRA.Mods.Common.Traits
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);
GeneratePreviews();
GenerateBounds();
@@ -169,8 +171,8 @@ namespace OpenRA.Mods.Common.Traits
foreach (var notify in Info.TraitInfos<INotifyEditorPlacementInfo>())
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.Ramp.CellEntryChanged += onCellEntryChanged;
}
public void RemovedFromEditor()
@@ -179,6 +181,7 @@ namespace OpenRA.Mods.Common.Traits
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

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.EditorBrushes;
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]));
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);
}