Add PlayerDictionary.

This custom collection allows other classes to implement a Player to value mapping, but also stores the values in an array for faster lookup by the player index in the world. For some code, this improved lookup time is important for performance.
This commit is contained in:
RoosterDragon
2015-12-22 23:15:32 +00:00
parent 611a928a47
commit 3a2139de26
4 changed files with 73 additions and 30 deletions

View File

@@ -153,6 +153,7 @@
<Compile Include="Orders\UnitOrderGenerator.cs" />
<Compile Include="Player.cs" />
<Compile Include="Primitives\MergedStream.cs" />
<Compile Include="Primitives\PlayerDictionary.cs" />
<Compile Include="Primitives\SegmentStream.cs" />
<Compile Include="Primitives\SpatiallyPartitioned.cs" />
<Compile Include="Primitives\ConcurrentCache.cs" />

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
namespace OpenRA.Primitives
{
/// <summary>
/// Provides a mapping of players to values, as well as fast lookup by player index.
/// </summary>
public struct PlayerDictionary<T> : IReadOnlyList<T>, IReadOnlyDictionary<Player, T> where T : class
{
readonly T[] valueByPlayerIndex;
readonly Dictionary<Player, T> valueByPlayer;
public PlayerDictionary(World world, Func<Player, T> valueFactory)
{
var players = world.Players;
if (players.Length == 0)
throw new InvalidOperationException("The players in the world have not yet been set.");
// The world players never change, so we can safely maintain an array of values.
// We need to enforce T is a class, so if it changes that change is available in both collections.
valueByPlayerIndex = new T[players.Length];
valueByPlayer = new Dictionary<Player, T>(players.Length);
for (var i = 0; i < players.Length; i++)
{
var player = players[i];
var state = valueFactory(player);
valueByPlayerIndex[i] = state;
valueByPlayer[player] = state;
}
}
/// <summary>Gets the value for the specified player. This is slower than specifying a player index.</summary>
public T this[Player player] { get { return valueByPlayer[player]; } }
/// <summary>Gets the value for the specified player index.</summary>
public T this[int playerIndex] { get { return valueByPlayerIndex[playerIndex]; } }
public int Count { get { return valueByPlayerIndex.Length; } }
public IEnumerator<T> GetEnumerator() { return ((IEnumerable<T>)valueByPlayerIndex).GetEnumerator(); }
ICollection<Player> IReadOnlyDictionary<Player, T>.Keys { get { return valueByPlayer.Keys; } }
ICollection<T> IReadOnlyDictionary<Player, T>.Values { get { return valueByPlayer.Values; } }
bool IReadOnlyDictionary<Player, T>.ContainsKey(Player key) { return valueByPlayer.ContainsKey(key); }
bool IReadOnlyDictionary<Player, T>.TryGetValue(Player key, out T value) { return valueByPlayer.TryGetValue(key, out value); }
IEnumerator<KeyValuePair<Player, T>> IEnumerable<KeyValuePair<Player, T>>.GetEnumerator() { return valueByPlayer.GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
}

View File

@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
@@ -35,9 +36,7 @@ namespace OpenRA.Mods.Common.Traits
readonly bool startsRevealed;
readonly PPos[] footprint;
readonly Dictionary<Player, FrozenState> stateByPlayer = new Dictionary<Player, FrozenState>();
FrozenState[] stateByPlayerIndex;
PlayerDictionary<FrozenState> frozenStates;
ITooltip tooltip;
Health health;
bool initialized;
@@ -71,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
if (!byPlayer.Shroud.FogEnabled)
return byPlayer.Shroud.AnyExplored(self.OccupiesSpace.OccupiedCells());
return initialized && stateByPlayer[byPlayer].IsVisible;
return initialized && frozenStates[byPlayer].IsVisible;
}
public bool IsVisible(Actor self, Player byPlayer)
@@ -89,41 +88,36 @@ namespace OpenRA.Mods.Common.Traits
return;
VisibilityHash = 0;
var players = self.World.Players;
if (!initialized)
{
// The world players never change, so we can safely index this collection.
stateByPlayerIndex = new FrozenState[players.Length];
frozenStates = new PlayerDictionary<FrozenState>(self.World, player =>
{
var frozenActor = new FrozenActor(self, footprint, player.Shroud, startsRevealed);
player.PlayerActor.Trait<FrozenActorLayer>().Add(frozenActor);
return new FrozenState(frozenActor) { IsVisible = startsRevealed };
});
tooltip = self.TraitsImplementing<ITooltip>().FirstOrDefault();
health = self.TraitOrDefault<Health>();
}
for (var i = 0; i < players.Length; i++)
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
{
FrozenActor frozenActor;
var state = frozenStates[playerIndex];
var frozenActor = state.FrozenActor;
bool isVisible;
if (!initialized)
{
var player = players[i];
frozenActor = new FrozenActor(self, footprint, player.Shroud, startsRevealed);
isVisible = startsRevealed;
var state = new FrozenState(frozenActor) { IsVisible = isVisible };
stateByPlayer.Add(player, state);
stateByPlayerIndex[i] = state;
player.PlayerActor.Trait<FrozenActorLayer>().Add(frozenActor);
isVisible = state.IsVisible;
}
else
{
// PERF: Minimize lookup cost by combining all state into one, and using an array rather than a dictionary.
var state = stateByPlayerIndex[i];
frozenActor = state.FrozenActor;
isVisible = !frozenActor.Visible;
state.IsVisible = isVisible;
}
if (isVisible)
VisibilityHash |= 1 << (i % 32);
VisibilityHash |= 1 << (playerIndex % 32);
else
continue;
@@ -151,9 +145,9 @@ namespace OpenRA.Mods.Common.Traits
return;
IRenderable[] renderables = null;
foreach (var player in self.World.Players)
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
{
var frozen = stateByPlayer[player].FrozenActor;
var frozen = frozenStates[playerIndex].FrozenActor;
if (!frozen.NeedRenderables)
continue;

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.RA.Effects
readonly GpsDotInfo info;
readonly Animation anim;
readonly Dictionary<Player, DotState> stateByPlayer = new Dictionary<Player, DotState>();
readonly PlayerDictionary<DotState> dotStates;
readonly Lazy<HiddenUnderFog> huf;
readonly Lazy<FrozenUnderFog> fuf;
readonly Lazy<Disguise> disguise;
@@ -70,13 +70,12 @@ namespace OpenRA.Mods.RA.Effects
cloak = Exts.Lazy(() => self.TraitOrDefault<Cloak>());
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
stateByPlayer = self.World.Players.ToDictionary(p => p, p => new DotState(p.PlayerActor.Trait<GpsWatcher>()));
dotStates = new PlayerDictionary<DotState>(self.World, player => new DotState(player.PlayerActor.Trait<GpsWatcher>()));
}
public bool IsDotVisible(Player toPlayer)
{
return stateByPlayer[toPlayer].IsTargetable;
return dotStates[toPlayer].IsTargetable;
}
bool IsTargetableBy(Player toPlayer, out bool shouldRenderIndicator)
@@ -122,11 +121,11 @@ namespace OpenRA.Mods.RA.Effects
if (!self.IsInWorld || self.IsDead)
return;
foreach (var player in self.World.Players)
for (var playerIndex = 0; playerIndex < dotStates.Count; playerIndex++)
{
var state = stateByPlayer[player];
var state = dotStates[playerIndex];
var shouldRender = false;
var targetable = (state.Gps.Granted || state.Gps.GrantedAllies) && IsTargetableBy(player, out shouldRender);
var targetable = (state.Gps.Granted || state.Gps.GrantedAllies) && IsTargetableBy(world.Players[playerIndex], out shouldRender);
state.IsTargetable = targetable;
state.ShouldRender = targetable && shouldRender;
}
@@ -134,7 +133,7 @@ namespace OpenRA.Mods.RA.Effects
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (self.World.RenderPlayer == null || !stateByPlayer[self.World.RenderPlayer].ShouldRender || self.Disposed)
if (self.World.RenderPlayer == null || !dotStates[self.World.RenderPlayer].ShouldRender || self.Disposed)
return SpriteRenderable.None;
var palette = wr.Palette(info.IndicatorPalettePrefix + self.Owner.InternalName);