mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 09:37:03 +02:00
237 lines
9.8 KiB
C#
237 lines
9.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using NUnit.Framework;
|
|
using Robust.Client.GameStates;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Timing;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Robust.Shared.Analyzers;
|
|
using static Robust.UnitTesting.Shared.GameState.ExampleAutogeneratedComponent;
|
|
|
|
namespace Robust.UnitTesting.Shared.GameState;
|
|
|
|
/// <summary>
|
|
/// This is a test of test engine <see cref="RobustIntegrationTest"/>. Not a test of game engine.
|
|
/// </summary>
|
|
internal sealed partial class NoSharedReferencesTest : RobustIntegrationTest
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ReferencesAreNotShared()
|
|
{
|
|
var serverOpts = new ServerIntegrationOptions { Pool = false };
|
|
var clientOpts = new ClientIntegrationOptions { Pool = false };
|
|
await using var pair = await StartConnectedPair(serverOpts, clientOpts);
|
|
var server = pair.Server;
|
|
var client = pair.Client;
|
|
var clientGameStateManager = client.ResolveDependency<IClientGameStateManager>();
|
|
|
|
// Set up map.
|
|
server.Post(() =>
|
|
{
|
|
server.System<SharedMapSystem>().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();
|
|
|
|
Task RunTicks() => RunTicksSync(server, client, 10);
|
|
}
|
|
|
|
[Test]
|
|
public async Task DeltaStateCollectionReferencesAreNotShared()
|
|
{
|
|
var serverOpts = new ServerIntegrationOptions { Pool = false };
|
|
var clientOpts = new ClientIntegrationOptions { Pool = false };
|
|
await using var pair = await StartConnectedPair(serverOpts, clientOpts);
|
|
var server = pair.Server;
|
|
var client = pair.Client;
|
|
var clientGameStateManager = client.ResolveDependency<IClientGameStateManager>();
|
|
|
|
server.Post(() =>
|
|
{
|
|
server.System<SharedMapSystem>().CreateMap();
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
EntityUid sPlayer = default;
|
|
EntityUid cPlayer = default;
|
|
List<int> clientValues = default!;
|
|
DeltaCollectionAutogeneratedComponent.DeltaCollectionAutogeneratedComponent_AutoState oldState = default!;
|
|
var sEntMan = server.EntMan;
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
sPlayer = sEntMan.Spawn();
|
|
sEntMan.AddComponent(sPlayer, new DeltaCollectionAutogeneratedComponent());
|
|
sEntMan.AddComponent(sPlayer, new SingleDeltaCollectionAutogeneratedComponent());
|
|
|
|
var session = server.PlayerMan.Sessions.First();
|
|
server.PlayerMan.SetAttachedEntity(session, sPlayer);
|
|
server.PlayerMan.JoinGame(session);
|
|
});
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
var comp = sEntMan.GetComponent<DeltaCollectionAutogeneratedComponent>(sPlayer);
|
|
var state = (DeltaCollectionAutogeneratedComponent.DeltaCollectionAutogeneratedComponent_AutoState) sEntMan
|
|
.GetComponentState(sEntMan.EventBus, comp, null, GameTick.Zero)!;
|
|
|
|
Assert.That(state.Values, Is.EqualTo(comp.Values));
|
|
Assert.That(ReferenceEquals(state.Values, comp.Values), Is.True);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var cEntMan = client.EntMan;
|
|
cPlayer = cEntMan.GetEntity(server.EntMan.GetNetEntity(sPlayer));
|
|
Assert.That(cEntMan.TryGetComponent(cPlayer, out DeltaCollectionAutogeneratedComponent? comp));
|
|
|
|
var componentStates = clientGameStateManager.GetFullRep()[cEntMan.GetNetEntity(cPlayer)];
|
|
oldState = (DeltaCollectionAutogeneratedComponent.DeltaCollectionAutogeneratedComponent_AutoState) componentStates
|
|
.First(x => x.Value is DeltaCollectionAutogeneratedComponent.DeltaCollectionAutogeneratedComponent_AutoState)
|
|
.Value!;
|
|
|
|
Assert.That(comp!.Values, Is.EqualTo(oldState.Values));
|
|
Assert.That(ReferenceEquals(comp.Values, oldState.Values), Is.False);
|
|
clientValues = comp.Values;
|
|
|
|
var localState = (DeltaCollectionAutogeneratedComponent.DeltaCollectionAutogeneratedComponent_AutoState) cEntMan
|
|
.GetComponentState(cEntMan.EventBus, comp, null, GameTick.Zero)!;
|
|
|
|
Assert.That(localState.Values, Is.EqualTo(comp.Values));
|
|
Assert.That(ReferenceEquals(localState.Values, comp.Values), Is.False);
|
|
|
|
Assert.That(cEntMan.TryGetComponent(cPlayer, out SingleDeltaCollectionAutogeneratedComponent? singleComp));
|
|
var singleState = (SingleDeltaCollectionAutogeneratedComponent.SingleDeltaCollectionAutogeneratedComponent_AutoState) componentStates
|
|
.First(x => x.Value is SingleDeltaCollectionAutogeneratedComponent.SingleDeltaCollectionAutogeneratedComponent_AutoState)
|
|
.Value!;
|
|
|
|
Assert.That(singleComp!.Values, Is.EqualTo(singleState.Values));
|
|
Assert.That(ReferenceEquals(singleComp.Values, singleState.Values), Is.False);
|
|
|
|
var singleLocalState = (SingleDeltaCollectionAutogeneratedComponent.SingleDeltaCollectionAutogeneratedComponent_AutoState) cEntMan
|
|
.GetComponentState(cEntMan.EventBus, singleComp, null, GameTick.Zero)!;
|
|
|
|
Assert.That(singleLocalState.Values, Is.EqualTo(singleComp.Values));
|
|
Assert.That(ReferenceEquals(singleLocalState.Values, singleComp.Values), Is.False);
|
|
});
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var deltaState = new DeltaCollectionAutogeneratedComponent.Counter_FieldComponentState
|
|
{
|
|
Counter = 1
|
|
};
|
|
var newState = deltaState.CreateNewFullState(oldState);
|
|
|
|
Assert.That(newState, Is.Not.SameAs(oldState));
|
|
Assert.That(newState.Counter, Is.EqualTo(1));
|
|
Assert.That(newState.Values, Is.EqualTo(oldState.Values));
|
|
Assert.That(ReferenceEquals(newState.Values, oldState.Values), Is.False);
|
|
});
|
|
|
|
await server.WaitPost(() =>
|
|
{
|
|
var comp = sEntMan.GetComponent<DeltaCollectionAutogeneratedComponent>(sPlayer);
|
|
comp.Values.Clear();
|
|
comp.Values.Add(4);
|
|
sEntMan.Dirty(sPlayer, comp);
|
|
});
|
|
|
|
await RunTicks();
|
|
|
|
await client.WaitPost(() =>
|
|
{
|
|
var comp = client.EntMan.GetComponent<DeltaCollectionAutogeneratedComponent>(cPlayer);
|
|
Assert.That(comp.Values, Is.SameAs(clientValues));
|
|
Assert.That(comp.Values, Is.EqualTo(new[] { 4 }));
|
|
});
|
|
|
|
Task RunTicks() => RunTicksSync(server, client, 10);
|
|
}
|
|
}
|
|
|
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
|
public sealed partial class ExampleAutogeneratedComponent : Component
|
|
{
|
|
[AutoNetworkedField]
|
|
public ExampleObject ReferenceObject;
|
|
|
|
[Serializable, NetSerializable]
|
|
public sealed record ExampleObject(int Value);
|
|
}
|
|
|
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
|
|
public sealed partial class DeltaCollectionAutogeneratedComponent : Component
|
|
{
|
|
[AutoNetworkedField]
|
|
public List<int> Values = new() { 1, 2, 3 };
|
|
|
|
[AutoNetworkedField]
|
|
public int Counter;
|
|
}
|
|
|
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
|
|
public sealed partial class SingleDeltaCollectionAutogeneratedComponent : Component
|
|
{
|
|
[AutoNetworkedField]
|
|
public List<int> Values = new() { 1, 2, 3 };
|
|
}
|