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:
@@ -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}";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user