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:
@@ -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" />
|
||||
|
||||
49
OpenRA.Game/Primitives/PlayerDictionary.cs
Normal file
49
OpenRA.Game/Primitives/PlayerDictionary.cs
Normal 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(); }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user