Files
OpenRA/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs
RoosterDragon 0899d02377 Avoid allocations when generating RadarSignatureCells.
The RadarWidget can supply a reusable buffer to each trait to avoid individual traits having to return new enumerables. Additionally, this allows the two traits to avoid LINQ and further allocations as they can manually enumerate and populate the buffer themselves.
2017-12-12 00:00:51 +01:00

283 lines
7.7 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Required for the map editor to work. Attach this to the world actor.")]
public class EditorActorLayerInfo : ITraitInfo
{
[Desc("Size of partition bins (world pixels)")]
public readonly int BinSize = 250;
public object Create(ActorInitializer init) { return new EditorActorLayer(init.Self, this); }
}
public class EditorActorLayer : IWorldLoaded, ITickRender, IRender, IRadarSignature, ICreatePlayers
{
readonly EditorActorLayerInfo info;
readonly List<EditorActorPreview> previews = new List<EditorActorPreview>();
readonly Dictionary<CPos, List<EditorActorPreview>> cellMap = new Dictionary<CPos, List<EditorActorPreview>>();
SpatiallyPartitioned<EditorActorPreview> screenMap;
WorldRenderer worldRenderer;
public MapPlayers Players { get; private set; }
public EditorActorLayer(Actor self, EditorActorLayerInfo info)
{
this.info = info;
}
void ICreatePlayers.CreatePlayers(World w)
{
if (w.Type != WorldType.Editor)
return;
Players = new MapPlayers(w.Map.PlayerDefinitions);
var worldOwner = Players.Players.Select(kvp => kvp.Value).First(p => !p.Playable && p.OwnsWorld);
w.SetWorldOwner(new Player(w, null, worldOwner));
}
public void WorldLoaded(World world, WorldRenderer wr)
{
if (world.Type != WorldType.Editor)
return;
worldRenderer = wr;
foreach (var pr in Players.Players.Values)
wr.UpdatePalettesForPlayer(pr.Name, pr.Color, false);
var ts = world.Map.Grid.TileSize;
var width = world.Map.MapSize.X * ts.Width;
var height = world.Map.MapSize.Y * 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);
// Update neighbours in one pass
foreach (var p in previews)
UpdateNeighbours(p.Footprint);
}
void ITickRender.TickRender(WorldRenderer wr, Actor self)
{
if (wr.World.Type != WorldType.Editor)
return;
foreach (var p in previews)
p.Tick();
}
static readonly IEnumerable<IRenderable> NoRenderables = Enumerable.Empty<IRenderable>();
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
if (wr.World.Type != WorldType.Editor)
return NoRenderables;
return PreviewsInBox(wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.SelectMany(p => p.Render());
}
IEnumerable<Rectangle> IRender.ScreenBounds(Actor self, WorldRenderer wr)
{
// World-actor render traits don't require screen bounds
yield break;
}
public EditorActorPreview Add(ActorReference reference) { return Add(NextActorName(), reference); }
EditorActorPreview Add(string id, ActorReference reference, bool initialSetup = false)
{
var owner = Players.Players[reference.InitDict.Get<OwnerInit>().PlayerName];
var preview = new EditorActorPreview(worldRenderer, id, reference, owner);
previews.Add(preview);
if (!preview.Bounds.IsEmpty)
screenMap.Add(preview, preview.Bounds);
foreach (var kv in preview.Footprint)
{
List<EditorActorPreview> list;
if (!cellMap.TryGetValue(kv.Key, out list))
{
list = new List<EditorActorPreview>();
cellMap.Add(kv.Key, list);
}
list.Add(preview);
}
if (!initialSetup)
{
UpdateNeighbours(preview.Footprint);
if (reference.Type == "mpspawn")
SyncMultiplayerCount();
}
return preview;
}
public void Remove(EditorActorPreview preview)
{
previews.Remove(preview);
screenMap.Remove(preview);
foreach (var kv in preview.Footprint)
{
List<EditorActorPreview> list;
if (!cellMap.TryGetValue(kv.Key, out list))
continue;
list.Remove(preview);
if (!list.Any())
cellMap.Remove(kv.Key);
}
UpdateNeighbours(preview.Footprint);
if (preview.Info.Name == "mpspawn")
SyncMultiplayerCount();
}
void SyncMultiplayerCount()
{
var newCount = previews.Count(p => p.Info.Name == "mpspawn");
var mp = Players.Players.Where(p => p.Key.StartsWith("Multi")).ToList();
foreach (var kv in mp)
{
var name = kv.Key;
var index = int.Parse(name.Substring(5));
if (index >= newCount)
Players.Players.Remove(name);
}
for (var index = 0; index < newCount; index++)
{
if (Players.Players.ContainsKey("Multi{0}".F(index)))
continue;
var pr = new PlayerReference
{
Name = "Multi{0}".F(index),
Faction = "Random",
Playable = true,
Enemies = new[] { "Creeps" }
};
Players.Players.Add(pr.Name, pr);
worldRenderer.UpdatePalettesForPlayer(pr.Name, pr.Color, true);
}
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(IReadOnlyDictionary<CPos, SubCell> footprint)
{
// Include actors inside the footprint too
var cells = Util.ExpandFootprint(footprint.Keys, true);
foreach (var p in cells.SelectMany(c => PreviewsAt(c)))
p.ReplaceInit(new RuntimeNeighbourInit(NeighbouringPreviews(p.Footprint)));
}
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());
}
public IEnumerable<EditorActorPreview> PreviewsInBox(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)));
}
public IEnumerable<EditorActorPreview> PreviewsInBox(Rectangle r)
{
return screenMap.InBox(r);
}
public IEnumerable<EditorActorPreview> PreviewsAt(CPos cell)
{
List<EditorActorPreview> list;
if (cellMap.TryGetValue(cell, out list))
return list;
return Enumerable.Empty<EditorActorPreview>();
}
public SubCell FreeSubCellAt(CPos cell)
{
var map = worldRenderer.World.Map;
var previews = PreviewsAt(cell).ToList();
if (!previews.Any())
return map.Grid.DefaultSubCell;
for (var i = (int)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++)
if (!previews.Any(p => p.Footprint[cell] == (SubCell)i))
return (SubCell)i;
return SubCell.Invalid;
}
public IEnumerable<EditorActorPreview> PreviewsAt(int2 worldPx)
{
return screenMap.At(worldPx);
}
string NextActorName()
{
var id = previews.Count();
var possibleName = "Actor" + id.ToString();
while (previews.Any(p => p.ID == possibleName))
{
id++;
possibleName = "Actor" + id.ToString();
}
return possibleName;
}
public List<MiniYamlNode> Save()
{
var nodes = new List<MiniYamlNode>();
foreach (var a in previews)
nodes.Add(new MiniYamlNode(a.ID, a.Save()));
return nodes;
}
public void PopulateRadarSignatureCells(Actor self, List<Pair<CPos, Color>> destinationBuffer)
{
foreach (var previewsForCell in cellMap)
foreach (var preview in previewsForCell.Value)
destinationBuffer.Add(Pair.New(previewsForCell.Key, preview.Owner.Color.RGB));
}
}
}