Merge pull request #10272 from RoosterDragon/player-dict
Speed up dictionaries keyed on Player
This commit is contained in:
@@ -153,6 +153,7 @@
|
|||||||
<Compile Include="Orders\UnitOrderGenerator.cs" />
|
<Compile Include="Orders\UnitOrderGenerator.cs" />
|
||||||
<Compile Include="Player.cs" />
|
<Compile Include="Player.cs" />
|
||||||
<Compile Include="Primitives\MergedStream.cs" />
|
<Compile Include="Primitives\MergedStream.cs" />
|
||||||
|
<Compile Include="Primitives\PlayerDictionary.cs" />
|
||||||
<Compile Include="Primitives\SegmentStream.cs" />
|
<Compile Include="Primitives\SegmentStream.cs" />
|
||||||
<Compile Include="Primitives\SpatiallyPartitioned.cs" />
|
<Compile Include="Primitives\SpatiallyPartitioned.cs" />
|
||||||
<Compile Include="Primitives\ConcurrentCache.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(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
public void SetPlayers(IEnumerable<Player> players, Player localPlayer)
|
public void SetPlayers(IEnumerable<Player> players, Player localPlayer)
|
||||||
{
|
{
|
||||||
|
if (Players.Length > 0)
|
||||||
|
throw new InvalidOperationException("Players are fixed once they have been set.");
|
||||||
Players = players.ToArray();
|
Players = players.ToArray();
|
||||||
SetLocalPlayer(localPlayer);
|
SetLocalPlayer(localPlayer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.Traits
|
namespace OpenRA.Mods.Common.Traits
|
||||||
@@ -35,9 +36,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
readonly bool startsRevealed;
|
readonly bool startsRevealed;
|
||||||
readonly PPos[] footprint;
|
readonly PPos[] footprint;
|
||||||
|
|
||||||
readonly Dictionary<Player, FrozenState> stateByPlayer = new Dictionary<Player, FrozenState>();
|
PlayerDictionary<FrozenState> frozenStates;
|
||||||
|
|
||||||
FrozenState[] stateByPlayerIndex;
|
|
||||||
ITooltip tooltip;
|
ITooltip tooltip;
|
||||||
Health health;
|
Health health;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
@@ -71,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (!byPlayer.Shroud.FogEnabled)
|
if (!byPlayer.Shroud.FogEnabled)
|
||||||
return byPlayer.Shroud.AnyExplored(self.OccupiesSpace.OccupiedCells());
|
return byPlayer.Shroud.AnyExplored(self.OccupiesSpace.OccupiedCells());
|
||||||
|
|
||||||
return initialized && stateByPlayer[byPlayer].IsVisible;
|
return initialized && frozenStates[byPlayer].IsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsVisible(Actor self, Player byPlayer)
|
public bool IsVisible(Actor self, Player byPlayer)
|
||||||
@@ -89,41 +88,36 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
VisibilityHash = 0;
|
VisibilityHash = 0;
|
||||||
var players = self.World.Players;
|
|
||||||
|
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
// The world players never change, so we can safely index this collection.
|
frozenStates = new PlayerDictionary<FrozenState>(self.World, player =>
|
||||||
stateByPlayerIndex = new FrozenState[players.Length];
|
{
|
||||||
|
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();
|
tooltip = self.TraitsImplementing<ITooltip>().FirstOrDefault();
|
||||||
health = self.TraitOrDefault<Health>();
|
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;
|
bool isVisible;
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
var player = players[i];
|
isVisible = state.IsVisible;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
else
|
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;
|
isVisible = !frozenActor.Visible;
|
||||||
state.IsVisible = isVisible;
|
state.IsVisible = isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVisible)
|
if (isVisible)
|
||||||
VisibilityHash |= 1 << (i % 32);
|
VisibilityHash |= 1 << (playerIndex % 32);
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -151,9 +145,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
IRenderable[] renderables = null;
|
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)
|
if (!frozen.NeedRenderables)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
readonly GpsDotInfo info;
|
readonly GpsDotInfo info;
|
||||||
readonly Animation anim;
|
readonly Animation anim;
|
||||||
|
|
||||||
readonly Dictionary<Player, DotState> stateByPlayer = new Dictionary<Player, DotState>();
|
readonly PlayerDictionary<DotState> dotStates;
|
||||||
readonly Lazy<HiddenUnderFog> huf;
|
readonly Lazy<HiddenUnderFog> huf;
|
||||||
readonly Lazy<FrozenUnderFog> fuf;
|
readonly Lazy<FrozenUnderFog> fuf;
|
||||||
readonly Lazy<Disguise> disguise;
|
readonly Lazy<Disguise> disguise;
|
||||||
@@ -70,13 +70,12 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
cloak = Exts.Lazy(() => self.TraitOrDefault<Cloak>());
|
cloak = Exts.Lazy(() => self.TraitOrDefault<Cloak>());
|
||||||
|
|
||||||
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
|
frozen = new Cache<Player, FrozenActorLayer>(p => p.PlayerActor.Trait<FrozenActorLayer>());
|
||||||
|
dotStates = new PlayerDictionary<DotState>(self.World, player => new DotState(player.PlayerActor.Trait<GpsWatcher>()));
|
||||||
stateByPlayer = self.World.Players.ToDictionary(p => p, p => new DotState(p.PlayerActor.Trait<GpsWatcher>()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDotVisible(Player toPlayer)
|
public bool IsDotVisible(Player toPlayer)
|
||||||
{
|
{
|
||||||
return stateByPlayer[toPlayer].IsTargetable;
|
return dotStates[toPlayer].IsTargetable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsTargetableBy(Player toPlayer, out bool shouldRenderIndicator)
|
bool IsTargetableBy(Player toPlayer, out bool shouldRenderIndicator)
|
||||||
@@ -122,11 +121,11 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
if (!self.IsInWorld || self.IsDead)
|
if (!self.IsInWorld || self.IsDead)
|
||||||
return;
|
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 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.IsTargetable = targetable;
|
||||||
state.ShouldRender = targetable && shouldRender;
|
state.ShouldRender = targetable && shouldRender;
|
||||||
}
|
}
|
||||||
@@ -134,7 +133,7 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
|
|
||||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
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;
|
return SpriteRenderable.None;
|
||||||
|
|
||||||
var palette = wr.Palette(info.IndicatorPalettePrefix + self.Owner.InternalName);
|
var palette = wr.Palette(info.IndicatorPalettePrefix + self.Owner.InternalName);
|
||||||
|
|||||||
Reference in New Issue
Block a user