Files

400 lines
18 KiB
C#

using NUnit.Framework;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
namespace Robust.UnitTesting.Shared.GameState;
internal sealed partial class AutoNetworkingTests : RobustIntegrationTest
{
/// <summary>
/// Does basic testing for AutoNetworkedFieldAttribute and AutoGenerateComponentStateAttribute
/// to make sure the datafields are correctly networked to the client when dirtied.
/// </summary>
[Test]
public async Task AutoNetworkingTest()
{
var serverOpts = new ServerIntegrationOptions { Pool = false };
var clientOpts = new ClientIntegrationOptions { Pool = false };
using var server = StartServer(serverOpts);
using var client = StartClient(clientOpts);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
var netMan = client.ResolveDependency<IClientNetManager>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
// Set up map.
EntityUid map = default;
server.Post(() =>
{
map = server.System<SharedMapSystem>().CreateMap();
});
await RunTicks();
// Spawn entities.
var coords = new EntityCoordinates(map, default);
EntityUid player = default;
EntityUid cPlayer = default;
EntityUid clientEnt1 = default;
EntityUid clientEnt2 = default;
EntityUid clientEnt3 = default;
EntityUid clientEnt4 = default;
EntityUid clientEnt5 = default;
EntityUid clientEnt6 = default;
EntityUid serverEnt1 = default;
EntityUid serverEnt2 = default;
EntityUid serverEnt3 = default;
EntityUid serverEnt4 = default;
EntityUid serverEnt5 = default;
EntityUid serverEnt6 = default;
NetEntity netEnt1 = default;
NetEntity netEnt2 = default;
NetEntity netEnt3 = default;
NetEntity netEnt4 = default;
NetEntity netEnt5 = default;
NetEntity netEnt6 = default;
await server.WaitPost(() =>
{
// Attach player.
player = server.EntMan.SpawnAttachedTo(null, coords);
var session = server.PlayerMan.Sessions.First();
server.PlayerMan.SetAttachedEntity(session, player);
server.PlayerMan.JoinGame(session);
// Spawn test entities.
serverEnt1 = server.EntMan.SpawnAttachedTo(null, coords);
serverEnt2 = server.EntMan.SpawnAttachedTo(null, coords);
serverEnt3 = server.EntMan.SpawnAttachedTo(null, coords);
serverEnt4 = server.EntMan.SpawnAttachedTo(null, coords);
serverEnt5 = server.EntMan.SpawnAttachedTo(null, coords);
serverEnt6 = server.EntMan.SpawnAttachedTo(null, coords);
netEnt1 = server.EntMan.GetNetEntity(serverEnt1);
netEnt2 = server.EntMan.GetNetEntity(serverEnt2);
netEnt3 = server.EntMan.GetNetEntity(serverEnt3);
netEnt4 = server.EntMan.GetNetEntity(serverEnt4);
netEnt5 = server.EntMan.GetNetEntity(serverEnt5);
netEnt6 = server.EntMan.GetNetEntity(serverEnt6);
// Setup components.
server.EntMan.EnsureComponent<AutoNetworkingTestComponent>(serverEnt1);
server.EntMan.EnsureComponent<AutoNetworkingTestChildComponent>(serverEnt2);
server.EntMan.EnsureComponent<AutoNetworkingTestEmptyChildComponent>(serverEnt3);
server.EntMan.EnsureComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt4);
server.EntMan.EnsureComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt5);
server.EntMan.EnsureComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt6);
});
await RunTicks();
// Check client.
await client.WaitPost(() =>
{
// Get the client-side entities.
cPlayer = client.EntMan.GetEntity(server.EntMan.GetNetEntity(player));
clientEnt1 = client.EntMan.GetEntity(netEnt1);
clientEnt2 = client.EntMan.GetEntity(netEnt2);
clientEnt3 = client.EntMan.GetEntity(netEnt3);
clientEnt4 = client.EntMan.GetEntity(netEnt4);
clientEnt5 = client.EntMan.GetEntity(netEnt5);
clientEnt6 = client.EntMan.GetEntity(netEnt6);
// Check that the player got properly attached.
Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer));
Assert.That(client.EntMan.EntityExists(cPlayer));
// Get the client-side components.
Assert.That(client.EntMan.TryGetComponent(clientEnt1, out AutoNetworkingTestComponent? clientComp1));
Assert.That(client.EntMan.TryGetComponent(clientEnt2, out AutoNetworkingTestChildComponent? clientComp2));
Assert.That(client.EntMan.TryGetComponent(clientEnt3, out AutoNetworkingTestEmptyChildComponent? clientComp3));
Assert.That(client.EntMan.TryGetComponent(clientEnt4, out AutoNetworkingTestFieldDeltaComponent? clientComp4));
Assert.That(client.EntMan.TryGetComponent(clientEnt5, out AutoNetworkingTestFieldDeltaComponent? clientComp5));
Assert.That(client.EntMan.TryGetComponent(clientEnt6, out AutoNetworkingTestFieldDeltaComponent? clientComp6));
// All datafields should be the default value.
Assert.That(clientComp1?.IsNetworked, Is.EqualTo(1));
Assert.That(clientComp1?.NotNetworked, Is.EqualTo(2));
Assert.That(clientComp2?.ChildNetworked, Is.EqualTo(1));
Assert.That(clientComp2?.Child, Is.EqualTo(2));
Assert.That(clientComp2?.ParentNetworked, Is.EqualTo(3));
Assert.That(clientComp2?.Parent, Is.EqualTo(4));
Assert.That(clientComp3?.ParentNetworked, Is.EqualTo(3));
Assert.That(clientComp3?.Parent, Is.EqualTo(4));
Assert.That(clientComp4?.Field1, Is.EqualTo(1));
Assert.That(clientComp4?.Field2, Is.EqualTo(2));
Assert.That(clientComp4?.Field3, Is.EqualTo(3));
Assert.That(clientComp5?.Field1, Is.EqualTo(1));
Assert.That(clientComp5?.Field2, Is.EqualTo(2));
Assert.That(clientComp5?.Field3, Is.EqualTo(3));
Assert.That(clientComp6?.Field1, Is.EqualTo(1));
Assert.That(clientComp6?.Field2, Is.EqualTo(2));
Assert.That(clientComp6?.Field3, Is.EqualTo(3));
});
// Make changes on the server.
await server.WaitPost(() =>
{
// Get the server-side components.
var serverComp1 = server.EntMan.GetComponent<AutoNetworkingTestComponent>(serverEnt1);
var serverComp2 = server.EntMan.GetComponent<AutoNetworkingTestChildComponent>(serverEnt2);
var serverComp3 = server.EntMan.GetComponent<AutoNetworkingTestEmptyChildComponent>(serverEnt3);
var serverComp4 = server.EntMan.GetComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt4);
var serverComp5 = server.EntMan.GetComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt5);
var serverComp6 = server.EntMan.GetComponent<AutoNetworkingTestFieldDeltaComponent>(serverEnt6);
// All datafields should be the default value
Assert.That(serverComp1.IsNetworked, Is.EqualTo(1));
Assert.That(serverComp1.NotNetworked, Is.EqualTo(2));
Assert.That(serverComp2.ChildNetworked, Is.EqualTo(1));
Assert.That(serverComp2.Child, Is.EqualTo(2));
Assert.That(serverComp2.ParentNetworked, Is.EqualTo(3));
Assert.That(serverComp2.Parent, Is.EqualTo(4));
Assert.That(serverComp3.ParentNetworked, Is.EqualTo(3));
Assert.That(serverComp3.Parent, Is.EqualTo(4));
Assert.That(serverComp4?.Field1, Is.EqualTo(1));
Assert.That(serverComp4?.Field2, Is.EqualTo(2));
Assert.That(serverComp4?.Field3, Is.EqualTo(3));
Assert.That(serverComp5?.Field1, Is.EqualTo(1));
Assert.That(serverComp5?.Field2, Is.EqualTo(2));
Assert.That(serverComp5?.Field3, Is.EqualTo(3));
Assert.That(serverComp6?.Field1, Is.EqualTo(1));
Assert.That(serverComp6?.Field2, Is.EqualTo(2));
Assert.That(serverComp6?.Field3, Is.EqualTo(3));
// Test that a field with AutoNetworkedField gets networked.
serverComp1.IsNetworked = 101;
// Test that a field without AutoNetworkedField does not get networked.
serverComp1.NotNetworked = 102;
server.EntMan.Dirty(serverEnt1, serverComp1);
// Test that inherited autonetworked fields get networked.
serverComp2.ChildNetworked = 101;
serverComp2.Child = 102;
serverComp2.ParentNetworked = 103;
serverComp2.Parent = 104;
server.EntMan.Dirty(serverEnt2, serverComp2);
serverComp3.ParentNetworked = 103;
serverComp3.Parent = 104;
server.EntMan.Dirty(serverEnt3, serverComp3);
// Use an autogenerated delta state to only network a single field.
serverComp4.Field1 = 101;
serverComp4.Field2 = 102;
serverComp4.Field3 = 103;
server.EntMan.DirtyField(serverEnt4, serverComp4, nameof(AutoNetworkingTestFieldDeltaComponent.Field3));
// Check that calling both Dirty and then DirtyField will send a full state.
serverComp5.Field1 = 101;
serverComp5.Field2 = 102;
serverComp5.Field3 = 103;
server.EntMan.DirtyField(serverEnt5, serverComp5, nameof(AutoNetworkingTestFieldDeltaComponent.Field3));
server.EntMan.Dirty(serverEnt5, serverComp5);
// Check that calling both DirtyField and then Dirty will send a full state.
serverComp6.Field1 = 101;
serverComp6.Field2 = 102;
serverComp6.Field3 = 103;
server.EntMan.Dirty(serverEnt6, serverComp6);
server.EntMan.DirtyField(serverEnt6, serverComp6, nameof(AutoNetworkingTestFieldDeltaComponent.Field3));
});
await RunTicks();
// Check the client again.
await client.WaitPost(() =>
{
// Get the client-side components.
Assert.That(client.EntMan.TryGetComponent(clientEnt1, out AutoNetworkingTestComponent? cmpClient1));
Assert.That(client.EntMan.TryGetComponent(clientEnt2, out AutoNetworkingTestChildComponent? cmpClient2));
Assert.That(client.EntMan.TryGetComponent(clientEnt3, out AutoNetworkingTestEmptyChildComponent? cmpClient3));
Assert.That(client.EntMan.TryGetComponent(clientEnt4, out AutoNetworkingTestFieldDeltaComponent? cmpClient4));
Assert.That(client.EntMan.TryGetComponent(clientEnt5, out AutoNetworkingTestFieldDeltaComponent? cmpClient5));
Assert.That(client.EntMan.TryGetComponent(clientEnt6, out AutoNetworkingTestFieldDeltaComponent? cmpClient6));
// Check that the networked datafields have changed.
Assert.That(cmpClient1?.IsNetworked, Is.EqualTo(101));
Assert.That(cmpClient1?.NotNetworked, Is.EqualTo(2)); // unchanged
Assert.That(cmpClient2?.ChildNetworked, Is.EqualTo(101));
Assert.That(cmpClient2?.Child, Is.EqualTo(2)); // unchanged
Assert.That(cmpClient2?.ParentNetworked, Is.EqualTo(103));
Assert.That(cmpClient2?.Parent, Is.EqualTo(4)); // unchanged
Assert.That(cmpClient3?.ParentNetworked, Is.EqualTo(103));
Assert.That(cmpClient3?.Parent, Is.EqualTo(4)); // unchanged
Assert.That(cmpClient4?.Field1, Is.EqualTo(1)); // not dirtied
Assert.That(cmpClient4?.Field2, Is.EqualTo(2)); // not networked
Assert.That(cmpClient4?.Field3, Is.EqualTo(103)); // changed from delta state
Assert.That(cmpClient5?.Field1, Is.EqualTo(101)); // changed from full state
Assert.That(cmpClient5?.Field2, Is.EqualTo(2)); // not networked
Assert.That(cmpClient5?.Field3, Is.EqualTo(103)); // changed from full state
Assert.That(cmpClient6?.Field1, Is.EqualTo(101)); // changed from full state
Assert.That(cmpClient6?.Field2, Is.EqualTo(2)); // not networked
Assert.That(cmpClient6?.Field3, Is.EqualTo(103)); // changed from full state
});
await server.WaitPost(() =>
{
var ent = server.EntMan.Spawn(null, MapCoordinates.Nullspace);
var noExclude = server.EntMan.EnsureComponent<AutoNetworkingTestNoExcludeReplaysComponent>(ent);
var ev = new ComponentGetState(null, GameTick.Zero);
server.EntMan.EventBus.RaiseComponentEvent(ent, noExclude, ref ev);
// No exclusion comp returns a state for replay states (no player)
Assert.That(ev.ReplayState, Is.True);
Assert.That(ev.State, Is.Not.Null);
Assert.That(ev.ExcludeReplays, Is.False);
var exclude = server.EntMan.EnsureComponent<AutoNetworkingTestExcludeReplaysComponent>(ent);
ev = new ComponentGetState(null, GameTick.Zero);
server.EntMan.EventBus.RaiseComponentEvent(ent, exclude, ref ev);
// Exclusion comp returns null state for replay states (no player)
Assert.That(ev.ReplayState, Is.True);
Assert.That(ev.State, Is.Null);
Assert.That(ev.ExcludeReplays, Is.True);
});
async Task RunTicks()
{
for (int i = 0; i < 10; i++)
{
await server!.WaitRunTicks(1);
await client!.WaitRunTicks(1);
}
}
await client.WaitPost(() => netMan.ClientDisconnect(""));
await server.WaitRunTicks(5);
await client.WaitRunTicks(5);
}
/// <summary>
/// Tests that field-delta generation treats the from-tick as an already acknowledged state boundary.
/// A field dirtied exactly on the from-tick should not be included in the next delta mask.
/// </summary>
[Test]
public async Task AutoNetworkingFieldDeltaFromTickBoundaryTest()
{
using var server = StartServer();
await server.WaitIdleAsync();
EntityUid entity = default;
await server.WaitPost(() =>
{
entity = server.EntMan.Spawn();
server.EntMan.EnsureComponent<AutoNetworkingTestFieldDeltaComponent>(entity);
});
await server.WaitRunTicks(1);
var acknowledgedTick = GameTick.Zero;
await server.WaitPost(() =>
{
var component = server.EntMan.GetComponent<AutoNetworkingTestFieldDeltaComponent>(entity);
component.Field1 = 101;
server.EntMan.DirtyField(entity, component, nameof(AutoNetworkingTestFieldDeltaComponent.Field1));
acknowledgedTick = server.Timing.CurTick;
});
await server.WaitRunTicks(1);
IComponentState? state = null;
await server.WaitPost(() =>
{
var component = server.EntMan.GetComponent<AutoNetworkingTestFieldDeltaComponent>(entity);
component.Field3 = 303;
server.EntMan.DirtyField(entity, component, nameof(AutoNetworkingTestFieldDeltaComponent.Field3));
state = server.EntMan.GetComponentState(server.EntMan.EventBus, component, null, acknowledgedTick);
});
Assert.That(state, Is.TypeOf<AutoNetworkingTestFieldDeltaComponent.AutoNetworkingTestFieldDeltaComponent_AutoDeltaState>());
var delta = (IAutoGeneratedComponentDeltaState) state!;
Assert.That(delta.ChangedFields, Is.EqualTo(1UL << 1));
}
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AutoNetworkingTestComponent : Component
{
[DataField, AutoNetworkedField]
public int IsNetworked = 1;
[DataField]
public int NotNetworked = 2;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AutoNetworkingTestChildComponent : AutoNetworkingTestParentComponent
{
[DataField, AutoNetworkedField]
public int ChildNetworked = 1;
[DataField]
public int Child = 2;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AutoNetworkingTestEmptyChildComponent : AutoNetworkingTestParentComponent
{
}
public abstract partial class AutoNetworkingTestParentComponent : Component
{
[DataField, AutoNetworkedField]
public int ParentNetworked = 3;
[DataField]
public int Parent = 4;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)]
public sealed partial class AutoNetworkingTestFieldDeltaComponent : Component
{
[DataField, AutoNetworkedField]
public int Field1 = 1;
[DataField] // Add one field that is not networked to see if the next one get indexed correctly.
public int Field2 = 2;
[DataField, AutoNetworkedField]
public int Field3 = 3;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AutoNetworkingTestNoExcludeReplaysComponent : Component
{
[DataField, AutoNetworkedField]
public int DummyField;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(excludeReplays: true)]
public sealed partial class AutoNetworkingTestExcludeReplaysComponent : Component
{
[DataField, AutoNetworkedField]
public int DummyField;
}