bit field swapped for normal list at API boundaries

This commit is contained in:
Ian Woods 2026-07-07 23:43:15 -07:00
parent 157b77ebfa
commit bf3c008777

View file

@ -13,10 +13,12 @@ namespace TD.Gameplay
/// <see cref="PlayerBuffManager"/>. /// <see cref="PlayerBuffManager"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para><b>Bitmask, not a list.</b> A builder effect carries no per-grant state (no level, /// <para><b>List of granted kinds.</b> A builder effect carries no per-grant state (no
/// no enable/disable toggle) — it's either granted or it isn't — so the granted set is one /// level, no enable/disable toggle) — it's either granted or it isn't — so the granted set
/// <see cref="NetworkVariable{T}"/> bitmask keyed by <see cref="BuilderEffectKind"/> rather /// is a <see cref="NetworkList{T}"/> of <see cref="BuilderEffectKind"/> values. Stored as
/// than a <c>NetworkList</c>. "Has effect" and "grant effect" are both O(1) bit ops.</para> /// <c>byte</c> because <c>NetworkList&lt;T&gt;</c> requires <c>T : IEquatable&lt;T&gt;</c>
/// and plain enums don't implement that (see <see cref="TD.Gameplay.BuildJob"/> for the
/// same constraint on a struct); <c>byte</c> already does, for free.</para>
/// ///
/// <para><b>Granted via draft.</b> <see cref="TD.Gameplay.Draft.BuilderEffectDraftOption"/> /// <para><b>Granted via draft.</b> <see cref="TD.Gameplay.Draft.BuilderEffectDraftOption"/>
/// calls <see cref="ServerGrantEffect"/> when a player picks it. Effects are permanent for /// calls <see cref="ServerGrantEffect"/> when a player picks it. Effects are permanent for
@ -60,13 +62,10 @@ namespace TD.Gameplay
// ----- Networked state -------------------------------------------- // ----- Networked state --------------------------------------------
// One bit per BuilderEffectKind. readPerm Everyone (consistent with the other // Granted effect kinds, stored as byte (see class remarks for why not the enum
// per-player managers); writePerm Server. // directly). readPerm Everyone (consistent with the other per-player managers);
private readonly NetworkVariable<uint> grantedMask = new NetworkVariable<uint>( // writePerm Server — both are NetworkList's defaults.
value: 0, private readonly NetworkList<byte> grantedKinds = new NetworkList<byte>();
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server
);
/// <summary>Fired on every peer when a new effect is granted.</summary> /// <summary>Fired on every peer when a new effect is granted.</summary>
public event System.Action OnUpgradesChanged; public event System.Action OnUpgradesChanged;
@ -76,23 +75,23 @@ namespace TD.Gameplay
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
s_byClientId[OwnerClientId] = this; s_byClientId[OwnerClientId] = this;
grantedMask.OnValueChanged += HandleMaskChanged; grantedKinds.OnListChanged += HandleGrantedKindsChanged;
} }
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
grantedMask.OnValueChanged -= HandleMaskChanged; grantedKinds.OnListChanged -= HandleGrantedKindsChanged;
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this) if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
s_byClientId.Remove(OwnerClientId); s_byClientId.Remove(OwnerClientId);
} }
private void HandleMaskChanged(uint previous, uint current) => OnUpgradesChanged?.Invoke(); private void HandleGrantedKindsChanged(NetworkListEvent<byte> change) => OnUpgradesChanged?.Invoke();
// ----- Read API ----------------------------------------------------- // ----- Read API -----------------------------------------------------
/// <summary>True if this player has been granted the given effect.</summary> /// <summary>True if this player has been granted the given effect.</summary>
public bool PlayerHasEffect(BuilderEffectKind kind) => (grantedMask.Value & BitFor(kind)) != 0; public bool PlayerHasEffect(BuilderEffectKind kind) => grantedKinds.Contains((byte)kind);
/// <summary> /// <summary>
/// Returns this player's <see cref="BuilderEffectDefinition"/> for <paramref name="kind"/> /// Returns this player's <see cref="BuilderEffectDefinition"/> for <paramref name="kind"/>
@ -113,13 +112,11 @@ namespace TD.Gameplay
{ {
if (!IsServer) return false; if (!IsServer) return false;
uint bit = BitFor(kind); byte value = (byte)kind;
if ((grantedMask.Value & bit) != 0) return false; if (grantedKinds.Contains(value)) return false;
grantedMask.Value |= bit; grantedKinds.Add(value);
return true; return true;
} }
private static uint BitFor(BuilderEffectKind kind) => 1u << (int)kind;
} }
} }