Some improvements for SpatiallyPartitioned.
- Tweak the Update and Remove methods to reduce the number of dictionary lookups required. - Change the Update method to an indexer, this allows simplifying callers who wanted to AddOrUpdate a value. - Implement IDictionary<T, Rectangle>, since the class already implements these semantics by providing a backing store of the bounds for each item. - Clean up some naming to use the generic `item` instead of `actor`. - Make the MutateBins action methods static.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
1b2c119b58
commit
ce3ad6fbb3
@@ -10,17 +10,20 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Primitives
|
||||
{
|
||||
public sealed class SpatiallyPartitioned<T>
|
||||
public sealed class SpatiallyPartitioned<T> : IDictionary<T, Rectangle>
|
||||
{
|
||||
static readonly Action<Dictionary<T, Rectangle>, T, Rectangle> AddItem = (bin, item, bounds) => bin.Add(item, bounds);
|
||||
static readonly Action<Dictionary<T, Rectangle>, T, Rectangle> RemoveItem = (bin, item, bounds) => bin.Remove(item);
|
||||
|
||||
readonly int rows, cols, binSize;
|
||||
readonly Dictionary<T, Rectangle>[] itemBoundsBins;
|
||||
readonly Dictionary<T, Rectangle> itemBounds = new();
|
||||
readonly Action<Dictionary<T, Rectangle>, T, Rectangle> addItem = (bin, actor, bounds) => bin.Add(actor, bounds);
|
||||
readonly Action<Dictionary<T, Rectangle>, T, Rectangle> removeItem = (bin, actor, bounds) => bin.Remove(actor);
|
||||
|
||||
public SpatiallyPartitioned(int width, int height, int binSize)
|
||||
{
|
||||
@@ -30,41 +33,43 @@ namespace OpenRA.Primitives
|
||||
itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary<T, Rectangle>());
|
||||
}
|
||||
|
||||
static void ValidateBounds(T actor, Rectangle bounds)
|
||||
static void ValidateBounds(T item, Rectangle bounds)
|
||||
{
|
||||
if (bounds.Width == 0 || bounds.Height == 0)
|
||||
throw new ArgumentException($"Bounds of actor {actor} are empty.", nameof(bounds));
|
||||
throw new ArgumentException($"Bounds of {item} are empty.", nameof(bounds));
|
||||
}
|
||||
|
||||
public void Add(T item, Rectangle bounds)
|
||||
{
|
||||
ValidateBounds(item, bounds);
|
||||
itemBounds.Add(item, bounds);
|
||||
MutateBins(item, bounds, addItem);
|
||||
MutateBins(item, bounds, AddItem);
|
||||
}
|
||||
|
||||
public void Update(T item, Rectangle bounds)
|
||||
public Rectangle this[T item]
|
||||
{
|
||||
ValidateBounds(item, bounds);
|
||||
MutateBins(item, itemBounds[item], removeItem);
|
||||
MutateBins(item, itemBounds[item] = bounds, addItem);
|
||||
get => itemBounds[item];
|
||||
set
|
||||
{
|
||||
ValidateBounds(item, value);
|
||||
|
||||
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
|
||||
ref var bounds = ref CollectionsMarshal.GetValueRefOrAddDefault(itemBounds, item, out var exists);
|
||||
if (exists)
|
||||
MutateBins(item, bounds, RemoveItem);
|
||||
MutateBins(item, bounds = value, AddItem);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (!itemBounds.TryGetValue(item, out var bounds))
|
||||
if (!itemBounds.Remove(item, out var bounds))
|
||||
return false;
|
||||
|
||||
MutateBins(item, bounds, removeItem);
|
||||
itemBounds.Remove(item);
|
||||
MutateBins(item, bounds, RemoveItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return itemBounds.ContainsKey(item);
|
||||
}
|
||||
|
||||
Dictionary<T, Rectangle> BinAt(int row, int col)
|
||||
{
|
||||
return itemBoundsBins[row * cols + col];
|
||||
@@ -88,13 +93,13 @@ namespace OpenRA.Primitives
|
||||
maxCol = Math.Min(cols, Exts.IntegerDivisionRoundingAwayFromZero(right, binSize));
|
||||
}
|
||||
|
||||
void MutateBins(T actor, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action)
|
||||
void MutateBins(T item, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action)
|
||||
{
|
||||
BoundsToBinRowsAndCols(bounds, out var minRow, out var maxRow, out var minCol, out var maxCol);
|
||||
|
||||
for (var row = minRow; row < maxRow; row++)
|
||||
for (var col = minCol; col < maxCol; col++)
|
||||
action(BinAt(row, col), actor, bounds);
|
||||
action(BinAt(row, col), item, bounds);
|
||||
}
|
||||
|
||||
public IEnumerable<T> At(int2 location)
|
||||
@@ -135,8 +140,30 @@ namespace OpenRA.Primitives
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> ItemBounds => itemBounds.Values;
|
||||
public void Clear()
|
||||
{
|
||||
itemBounds.Clear();
|
||||
foreach (var bin in itemBoundsBins)
|
||||
bin.Clear();
|
||||
}
|
||||
|
||||
public IEnumerable<T> Items => itemBounds.Keys;
|
||||
public ICollection<T> Keys => itemBounds.Keys;
|
||||
public ICollection<Rectangle> Values => itemBounds.Values;
|
||||
public int Count => itemBounds.Count;
|
||||
public bool ContainsKey(T item) => itemBounds.ContainsKey(item);
|
||||
public bool TryGetValue(T key, out Rectangle value) => itemBounds.TryGetValue(key, out value);
|
||||
public IEnumerator<KeyValuePair<T, Rectangle>> GetEnumerator() => itemBounds.GetEnumerator();
|
||||
|
||||
bool ICollection<KeyValuePair<T, Rectangle>>.IsReadOnly => false;
|
||||
void ICollection<KeyValuePair<T, Rectangle>>.Add(KeyValuePair<T, Rectangle> item) =>
|
||||
((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Add(item);
|
||||
bool ICollection<KeyValuePair<T, Rectangle>>.Contains(KeyValuePair<T, Rectangle> item) =>
|
||||
((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Contains(item);
|
||||
void ICollection<KeyValuePair<T, Rectangle>>.CopyTo(KeyValuePair<T, Rectangle>[] array, int arrayIndex) =>
|
||||
((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).CopyTo(array, arrayIndex);
|
||||
bool ICollection<KeyValuePair<T, Rectangle>>.Remove(KeyValuePair<T, Rectangle> item) =>
|
||||
((ICollection<KeyValuePair<T, Rectangle>>)itemBounds).Remove(item);
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
((IEnumerable)itemBounds).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +217,7 @@ namespace OpenRA.Traits
|
||||
var mouseBounds = a.MouseBounds(worldRenderer);
|
||||
if (!mouseBounds.IsEmpty)
|
||||
{
|
||||
if (partitionedMouseActors.Contains(a))
|
||||
partitionedMouseActors.Update(a, mouseBounds.BoundingRect);
|
||||
else
|
||||
partitionedMouseActors.Add(a, mouseBounds.BoundingRect);
|
||||
|
||||
partitionedMouseActors[a] = mouseBounds.BoundingRect;
|
||||
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
|
||||
}
|
||||
else
|
||||
@@ -229,12 +225,7 @@ namespace OpenRA.Traits
|
||||
|
||||
var screenBounds = a.ScreenBounds(worldRenderer).Union();
|
||||
if (!screenBounds.Size.IsEmpty)
|
||||
{
|
||||
if (partitionedRenderableActors.Contains(a))
|
||||
partitionedRenderableActors.Update(a, screenBounds);
|
||||
else
|
||||
partitionedRenderableActors.Add(a, screenBounds);
|
||||
}
|
||||
partitionedRenderableActors[a] = screenBounds;
|
||||
else
|
||||
partitionedRenderableActors.Remove(a);
|
||||
}
|
||||
@@ -255,23 +246,13 @@ namespace OpenRA.Traits
|
||||
{
|
||||
var mouseBounds = fa.MouseBounds;
|
||||
if (!mouseBounds.IsEmpty)
|
||||
{
|
||||
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
|
||||
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds.BoundingRect);
|
||||
else
|
||||
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds.BoundingRect);
|
||||
}
|
||||
partitionedMouseFrozenActors[kv.Key][fa] = mouseBounds.BoundingRect;
|
||||
else
|
||||
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
||||
|
||||
var screenBounds = fa.ScreenBounds.Union();
|
||||
if (!screenBounds.Size.IsEmpty)
|
||||
{
|
||||
if (partitionedRenderableFrozenActors[kv.Key].Contains(fa))
|
||||
partitionedRenderableFrozenActors[kv.Key].Update(fa, screenBounds);
|
||||
else
|
||||
partitionedRenderableFrozenActors[kv.Key].Add(fa, screenBounds);
|
||||
}
|
||||
partitionedRenderableFrozenActors[kv.Key][fa] = screenBounds;
|
||||
else
|
||||
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
|
||||
}
|
||||
@@ -293,16 +274,16 @@ namespace OpenRA.Traits
|
||||
|
||||
public IEnumerable<Rectangle> RenderBounds(Player viewer)
|
||||
{
|
||||
var bounds = partitionedRenderableActors.ItemBounds
|
||||
.Concat(partitionedRenderableEffects.ItemBounds);
|
||||
var bounds = partitionedRenderableActors.Values
|
||||
.Concat(partitionedRenderableEffects.Values);
|
||||
|
||||
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
|
||||
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].Values) : bounds;
|
||||
}
|
||||
|
||||
public IEnumerable<Polygon> MouseBounds(Player viewer)
|
||||
{
|
||||
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
|
||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Items.Select(fa => fa.MouseBounds)) : bounds;
|
||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Keys.Select(fa => fa.MouseBounds)) : bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public void PopulateRadarSignatureCells(Actor self, List<(CPos Cell, Color Color)> destinationBuffer)
|
||||
{
|
||||
foreach (var preview in cellMap.Items)
|
||||
foreach (var preview in cellMap.Keys)
|
||||
foreach (var cell in Footprint(preview))
|
||||
destinationBuffer.Add((cell, preview.RadarColor));
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace OpenRA.Test
|
||||
Assert.That(partition.At(new int2(3, 2)), Does.Not.Contain(b), "b is present in the wrong location");
|
||||
Assert.That(partition.At(new int2(3, 3)), Does.Not.Contain(b), "b is present in the wrong location");
|
||||
|
||||
partition.Update(b, new Rectangle(4, 4, 1, 1));
|
||||
partition[b] = new Rectangle(4, 4, 1, 1);
|
||||
Assert.That(partition.At(new int2(0, 0)), Does.Contain(a), "a wrongly changed location when b was updated");
|
||||
Assert.That(partition.At(new int2(4, 4)), Does.Contain(b), "b is not present at the new location in the extreme corner of the partition");
|
||||
Assert.That(partition.At(new int2(1, 1)), Does.Not.Contain(b), "b is still present at the old location after update");
|
||||
@@ -79,7 +79,7 @@ namespace OpenRA.Test
|
||||
Assert.That(new[] { a, b }, Is.EquivalentTo(partition.InBox(new Rectangle(-10, -10, 25, 25))),
|
||||
"Searching an area larger than the partition did not return all items");
|
||||
|
||||
partition.Update(b, new Rectangle(4, 4, 1, 1));
|
||||
partition[b] = new Rectangle(4, 4, 1, 1);
|
||||
Assert.That(partition.InBox(new Rectangle(0, 0, 1, 1)), Does.Contain(a), "a wrongly changed location when b was updated");
|
||||
Assert.That(partition.InBox(new Rectangle(4, 4, 1, 1)), Does.Contain(b), "b is not present at the new location in the extreme corner of the partition");
|
||||
Assert.That(partition.InBox(new Rectangle(1, 1, 1, 1)), Does.Not.Contain(b), "b is still present at the old location after update");
|
||||
|
||||
Reference in New Issue
Block a user