using System; using NUnit.Framework; using Robust.Client.GameStates; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Network; using Robust.Shared.Serialization; using System.Linq; using System.Threading.Tasks; using static Robust.UnitTesting.Shared.GameState.ExampleAutogeneratedComponent; namespace Robust.UnitTesting.Shared.GameState; /// /// This is a test of test engine . Not a test of game engine. /// public sealed partial class NoSharedReferencesTest : RobustIntegrationTest { /// /// The test performs a basic check to ensure that there is no issue with server's object references leaking to client. /// It accomplishments this by testing two things: 1) That the reference object on both sides is not the same; and /// 2) That the client-side copy of server's component state (used for prediction resetting) is not the same. /// [Test] public async Task ReferencesAreNotShared() { var serverOpts = new ServerIntegrationOptions { Pool = false }; var clientOpts = new ClientIntegrationOptions { Pool = false }; var server = StartServer(serverOpts); var client = StartClient(clientOpts); await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); var netMan = client.ResolveDependency(); var clientGameStateManager = client.ResolveDependency(); Assert.DoesNotThrow(() => client.SetConnectTarget(server)); client.Post(() => netMan.ClientConnect(null!, 0, null!)); // Set up map. server.Post(() => { server.System().CreateMap(); }); await RunTicks(); EntityUid sPlayer = default; EntityUid cPlayer = default; ExampleObject serverObject = default!; var sEntMan = server.EntMan; // Set up test entity (player). await server.WaitPost(() => { sPlayer = sEntMan.Spawn(); serverObject = new ExampleObject(5); var comp = new ExampleAutogeneratedComponent { ReferenceObject = serverObject }; sEntMan.AddComponent(sPlayer, comp); var session = server.PlayerMan.Sessions.First(); server.PlayerMan.SetAttachedEntity(session, sPlayer); server.PlayerMan.JoinGame(session); }); // Let Client sync state with Server await RunTicks(); // Assert that Client's object and client-side server state objects are different to Server's object Assert.Multiple(() => { // Player attached assertions var cEntMan = client.EntMan; cPlayer = cEntMan.GetEntity(server.EntMan.GetNetEntity(sPlayer)); Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer)); Assert.That(cEntMan.EntityExists(cPlayer)); // Assert client and server have different objects of same values Assert.That(cEntMan.TryGetComponent(cPlayer, out ExampleAutogeneratedComponent? comp)); var clientObject = comp?.ReferenceObject; Assert.That(clientObject, Is.EqualTo(serverObject)); Assert.That(ReferenceEquals(clientObject, serverObject), Is.False); // Assert that client-side dictionary of server component state also isn't contaminated by server references var componentStates = clientGameStateManager.GetFullRep()[cEntMan.GetNetEntity(cPlayer)]; var clientLastTickStateObject = ((ExampleAutogeneratedComponent_AutoState)componentStates.First(x => x.Value is ExampleAutogeneratedComponent_AutoState).Value!).ReferenceObject; Assert.That(clientLastTickStateObject, Is.Not.Null); Assert.That(ReferenceEquals(clientLastTickStateObject, serverObject), Is.False); }); // wait for errors. await RunTicks(); async Task RunTicks() { for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); await client.WaitRunTicks(1); } } } } [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ExampleAutogeneratedComponent : Component { [AutoNetworkedField] public ExampleObject ReferenceObject; [Serializable, NetSerializable] public sealed record ExampleObject(int Value); }