diff --git a/src/SpacetimeDBNetworkManager.cs b/src/SpacetimeDBNetworkManager.cs index ae5e2d4f..9ebdfa1c 100644 --- a/src/SpacetimeDBNetworkManager.cs +++ b/src/SpacetimeDBNetworkManager.cs @@ -6,38 +6,48 @@ namespace SpacetimeDB { - // This class is only used in Unity projects. - // Attach this to a gameobject in your scene to use SpacetimeDB. - public class SpacetimeDBNetworkManager : MonoBehaviour - { - private static bool _alreadyInitialized; + // This class is only used in Unity projects. + // Attach this to a GameObject in your scene to use SpacetimeDB. + public class SpacetimeDBNetworkManager : MonoBehaviour + { + private static SpacetimeDBNetworkManager? _instance; - public void Awake() - { - // Ensure that users don't create several SpacetimeDBNetworkManager instances. - // We're using a global (static) list of active connections and we don't want several instances to walk over it several times. - if (_alreadyInitialized) - { - throw new InvalidOperationException("SpacetimeDBNetworkManager is a singleton and should only be attached once."); - } - else - { - _alreadyInitialized = true; - } - } + public void Awake() + { + // Ensure that users don't create several SpacetimeDBNetworkManager instances. + // We're using a global (static) list of active connections and we don't want several instances to walk over it several times. + if (_instance != null) + { + throw new InvalidOperationException("SpacetimeDBNetworkManager is a singleton and should only be attached once."); + } + else + { + _instance = this; + } + } - internal static HashSet ActiveConnections = new(); + internal static HashSet ActiveConnections = new(); - private void ForEachConnection(Action action) - { - foreach (var conn in ActiveConnections) - { - action(conn); - } - } + private List cache = new List(); - private void Update() => ForEachConnection(conn => conn.FrameTick()); - private void OnDestroy() => ForEachConnection(conn => conn.Disconnect()); - } + private void ForEachConnection(Action action) + { + // TODO(jdetter): We're doing this for now because we can't break the API during a minor release but + // in the future we should just change ActiveConnections to be a list so that we can reverse-iterate + // through it. + cache.Clear(); + cache.AddRange(ActiveConnections); + + // It's common to call disconnect from Update, which will then modify the ActiveConnections collection, + // therefore we must reverse-iterate the list of connections. + for (var x = cache.Count - 1; x >= 0; x--) + { + action(cache[x]); + } + } + + private void Update() => ForEachConnection(conn => conn.FrameTick()); + private void OnDestroy() => ForEachConnection(conn => conn.Disconnect()); + } } #endif diff --git a/unity-tests~/client/Assets/PlayModeTests/PlayModeExampleTest.cs b/unity-tests~/client/Assets/PlayModeTests/PlayModeExampleTest.cs index 416d22cf..6fd7b94e 100644 --- a/unity-tests~/client/Assets/PlayModeTests/PlayModeExampleTest.cs +++ b/unity-tests~/client/Assets/PlayModeTests/PlayModeExampleTest.cs @@ -16,7 +16,7 @@ public class PlayModeExampleTest { - // [UnityTest] - This won't work until we have reconnections + [UnityTest] public IEnumerator SimpleConnectionTest() { PlayerPrefs.DeleteAll(); @@ -138,7 +138,7 @@ public IEnumerator CreatePlayerAndTestDecay() Debug.Assert(massEnd < massStart, "Mass should have decayed"); } - // [UnityTest] - This won't work until we have reconnections + [UnityTest] public IEnumerator OneOffTest1() { var connected = false; @@ -183,7 +183,7 @@ public IEnumerator OneOffTest1() Debug.Log($"id: {players[0].PlayerId} Username: {players[0].Name}"); } - // [UnityTest] - This won't work until we have reconnections + [UnityTest] public IEnumerator OneOffTest2() { var connected = false; diff --git a/unity-tests~/client/Assets/Scripts/GameManager.cs b/unity-tests~/client/Assets/Scripts/GameManager.cs index 92068ab0..d55027d4 100644 --- a/unity-tests~/client/Assets/Scripts/GameManager.cs +++ b/unity-tests~/client/Assets/Scripts/GameManager.cs @@ -38,7 +38,7 @@ public class GameManager : MonoBehaviour public static GameManager instance; public static Camera localCamera; - public static Dictionary playerIdToPlayerController = + public Dictionary playerIdToPlayerController = new Dictionary(); public static Identity localIdentity = default; diff --git a/unity-tests~/client/Assets/Scripts/LeaderboardController.cs b/unity-tests~/client/Assets/Scripts/LeaderboardController.cs index b73e77eb..d238eca5 100644 --- a/unity-tests~/client/Assets/Scripts/LeaderboardController.cs +++ b/unity-tests~/client/Assets/Scripts/LeaderboardController.cs @@ -33,7 +33,7 @@ void UpdateRowEnabled(int count) private void Update() { - var players = GameManager.playerIdToPlayerController.Values.Select( + var players = GameManager.instance.playerIdToPlayerController.Values.Select( a => (a, a.TotalMass())).OrderByDescending(a => a.Item2).Take(10); var index = 0; diff --git a/unity-tests~/client/Assets/Scripts/autogen/_Globals/SpacetimeDBClient.cs b/unity-tests~/client/Assets/Scripts/autogen/_Globals/SpacetimeDBClient.cs index 50d290d8..bdfb4915 100644 --- a/unity-tests~/client/Assets/Scripts/autogen/_Globals/SpacetimeDBClient.cs +++ b/unity-tests~/client/Assets/Scripts/autogen/_Globals/SpacetimeDBClient.cs @@ -15,7 +15,7 @@ public sealed class RemoteTables { public class CircleHandle : RemoteTableHandle { - private static Dictionary EntityId_Index = new(16); + private Dictionary EntityId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -30,15 +30,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct EntityIdUniqueIndex { + readonly CircleHandle Handle; + internal EntityIdUniqueIndex(CircleHandle handle) => Handle = handle; + public Circle? Find(uint value) { - EntityId_Index.TryGetValue(value, out var r); + Handle.EntityId_Index.TryGetValue(value, out var r); return r; } } - public EntityIdUniqueIndex EntityId => new(); + public EntityIdUniqueIndex EntityId => new(this); public class PlayerIdIndex { @@ -62,7 +65,7 @@ internal CircleHandle() public class CircleDecayTimerHandle : RemoteTableHandle { - private static Dictionary ScheduledId_Index = new(16); + private Dictionary ScheduledId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -77,15 +80,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct ScheduledIdUniqueIndex { + readonly CircleDecayTimerHandle Handle; + internal ScheduledIdUniqueIndex(CircleDecayTimerHandle handle) => Handle = handle; + public CircleDecayTimer? Find(ulong value) { - ScheduledId_Index.TryGetValue(value, out var r); + Handle.ScheduledId_Index.TryGetValue(value, out var r); return r; } } - public ScheduledIdUniqueIndex ScheduledId => new(); + public ScheduledIdUniqueIndex ScheduledId => new(this); internal CircleDecayTimerHandle() { @@ -98,7 +104,7 @@ internal CircleDecayTimerHandle() public class ConfigHandle : RemoteTableHandle { - private static Dictionary Id_Index = new(16); + private Dictionary Id_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -113,15 +119,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct IdUniqueIndex { + readonly ConfigHandle Handle; + internal IdUniqueIndex(ConfigHandle handle) => Handle = handle; + public Config? Find(uint value) { - Id_Index.TryGetValue(value, out var r); + Handle.Id_Index.TryGetValue(value, out var r); return r; } } - public IdUniqueIndex Id => new(); + public IdUniqueIndex Id => new(this); internal ConfigHandle() { @@ -134,7 +143,7 @@ internal ConfigHandle() public class EntityHandle : RemoteTableHandle { - private static Dictionary Id_Index = new(16); + private Dictionary Id_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -149,15 +158,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct IdUniqueIndex { + readonly EntityHandle Handle; + internal IdUniqueIndex(EntityHandle handle) => Handle = handle; + public Entity? Find(uint value) { - Id_Index.TryGetValue(value, out var r); + Handle.Id_Index.TryGetValue(value, out var r); return r; } } - public IdUniqueIndex Id => new(); + public IdUniqueIndex Id => new(this); internal EntityHandle() { @@ -170,7 +182,7 @@ internal EntityHandle() public class FoodHandle : RemoteTableHandle { - private static Dictionary EntityId_Index = new(16); + private Dictionary EntityId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -185,15 +197,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct EntityIdUniqueIndex { + readonly FoodHandle Handle; + internal EntityIdUniqueIndex(FoodHandle handle) => Handle = handle; + public Food? Find(uint value) { - EntityId_Index.TryGetValue(value, out var r); + Handle.EntityId_Index.TryGetValue(value, out var r); return r; } } - public EntityIdUniqueIndex EntityId => new(); + public EntityIdUniqueIndex EntityId => new(this); internal FoodHandle() { @@ -206,7 +221,7 @@ internal FoodHandle() public class LoggedOutCircleHandle : RemoteTableHandle { - private static Dictionary LoggedOutId_Index = new(16); + private Dictionary LoggedOutId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -221,15 +236,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct LoggedOutIdUniqueIndex { + readonly LoggedOutCircleHandle Handle; + internal LoggedOutIdUniqueIndex(LoggedOutCircleHandle handle) => Handle = handle; + public LoggedOutCircle? Find(uint value) { - LoggedOutId_Index.TryGetValue(value, out var r); + Handle.LoggedOutId_Index.TryGetValue(value, out var r); return r; } } - public LoggedOutIdUniqueIndex LoggedOutId => new(); + public LoggedOutIdUniqueIndex LoggedOutId => new(this); public class PlayerIdIndex { @@ -253,7 +271,7 @@ internal LoggedOutCircleHandle() public class LoggedOutPlayerHandle : RemoteTableHandle { - private static Dictionary Identity_Index = new(16); + private Dictionary Identity_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -268,15 +286,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct IdentityUniqueIndex { + readonly LoggedOutPlayerHandle Handle; + internal IdentityUniqueIndex(LoggedOutPlayerHandle handle) => Handle = handle; + public LoggedOutPlayer? Find(SpacetimeDB.Identity value) { - Identity_Index.TryGetValue(value, out var r); + Handle.Identity_Index.TryGetValue(value, out var r); return r; } } - public IdentityUniqueIndex Identity => new(); + public IdentityUniqueIndex Identity => new(this); internal LoggedOutPlayerHandle() { @@ -289,7 +310,7 @@ internal LoggedOutPlayerHandle() public class MoveAllPlayersTimerHandle : RemoteTableHandle { - private static Dictionary ScheduledId_Index = new(16); + private Dictionary ScheduledId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -304,15 +325,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct ScheduledIdUniqueIndex { + readonly MoveAllPlayersTimerHandle Handle; + internal ScheduledIdUniqueIndex(MoveAllPlayersTimerHandle handle) => Handle = handle; + public MoveAllPlayersTimer? Find(ulong value) { - ScheduledId_Index.TryGetValue(value, out var r); + Handle.ScheduledId_Index.TryGetValue(value, out var r); return r; } } - public ScheduledIdUniqueIndex ScheduledId => new(); + public ScheduledIdUniqueIndex ScheduledId => new(this); internal MoveAllPlayersTimerHandle() { @@ -325,8 +349,8 @@ internal MoveAllPlayersTimerHandle() public class PlayerHandle : RemoteTableHandle { - private static Dictionary Identity_Index = new(16); - private static Dictionary PlayerId_Index = new(16); + private Dictionary Identity_Index = new(16); + private Dictionary PlayerId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -343,27 +367,33 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct IdentityUniqueIndex { + readonly PlayerHandle Handle; + internal IdentityUniqueIndex(PlayerHandle handle) => Handle = handle; + public Player? Find(SpacetimeDB.Identity value) { - Identity_Index.TryGetValue(value, out var r); + Handle.Identity_Index.TryGetValue(value, out var r); return r; } } - public IdentityUniqueIndex Identity => new(); + public IdentityUniqueIndex Identity => new(this); public readonly ref struct PlayerIdUniqueIndex { + readonly PlayerHandle Handle; + internal PlayerIdUniqueIndex(PlayerHandle handle) => Handle = handle; + public Player? Find(uint value) { - PlayerId_Index.TryGetValue(value, out var r); + Handle.PlayerId_Index.TryGetValue(value, out var r); return r; } } - public PlayerIdUniqueIndex PlayerId => new(); + public PlayerIdUniqueIndex PlayerId => new(this); internal PlayerHandle() { @@ -376,7 +406,7 @@ internal PlayerHandle() public class SpawnFoodTimerHandle : RemoteTableHandle { - private static Dictionary ScheduledId_Index = new(16); + private Dictionary ScheduledId_Index = new(16); public override void InternalInvokeValueInserted(IDatabaseRow row) { @@ -391,15 +421,18 @@ public override void InternalInvokeValueDeleted(IDatabaseRow row) public readonly ref struct ScheduledIdUniqueIndex { + readonly SpawnFoodTimerHandle Handle; + internal ScheduledIdUniqueIndex(SpawnFoodTimerHandle handle) => Handle = handle; + public SpawnFoodTimer? Find(ulong value) { - ScheduledId_Index.TryGetValue(value, out var r); + Handle.ScheduledId_Index.TryGetValue(value, out var r); return r; } } - public ScheduledIdUniqueIndex ScheduledId => new(); + public ScheduledIdUniqueIndex ScheduledId => new(this); internal SpawnFoodTimerHandle() { diff --git a/unity-tests~/server/generate.sh b/unity-tests~/server/generate.sh index 6c592e72..a2dbde0e 100644 --- a/unity-tests~/server/generate.sh +++ b/unity-tests~/server/generate.sh @@ -2,4 +2,6 @@ set -euo pipefail +cd "$(dirname "$0")" + spacetime generate --out-dir ../client/Assets/Scripts/autogen --lang cs $@