mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-16 02:09:20 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
417 lines
12 KiB
C#
417 lines
12 KiB
C#
using System;
|
|
using Sandbox.Internal;
|
|
using Sandbox.Network;
|
|
using Sandbox.SceneTests;
|
|
|
|
namespace GameObjects;
|
|
|
|
using static GlobalGameNamespace;
|
|
|
|
[TestClass]
|
|
public class NetworkTests
|
|
{
|
|
private TypeLibrary _oldTypeLibrary;
|
|
|
|
[TestInitialize]
|
|
public void TestInitialize()
|
|
{
|
|
_oldTypeLibrary = Game.TypeLibrary;
|
|
|
|
Game.TypeLibrary = new Sandbox.Internal.TypeLibrary();
|
|
Game.TypeLibrary.AddAssembly( typeof( PrefabFile ).Assembly, false );
|
|
Game.TypeLibrary.AddAssembly( typeof( ModelRenderer ).Assembly, false );
|
|
Game.TypeLibrary.AddAssembly( typeof( NetworkTestComponent ).Assembly, false );
|
|
|
|
JsonUpgrader.UpdateUpgraders( Game.TypeLibrary );
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void TestCleanup()
|
|
{
|
|
Game.TypeLibrary = _oldTypeLibrary;
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RegisterSyncProps()
|
|
{
|
|
Assert.IsNotNull( Game.TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var testComponentType = Game.TypeLibrary.GetType<NetworkTestComponent>();
|
|
Assert.IsNotNull( testComponentType );
|
|
|
|
var testSyncPropertyType = testComponentType.GetProperty( "SyncInt" );
|
|
Assert.IsNotNull( testSyncPropertyType );
|
|
|
|
var testPropertyId = testSyncPropertyType.Identity;
|
|
|
|
var go = new GameObject();
|
|
var comp1 = go.Components.Create<NetworkTestComponent>();
|
|
comp1.SyncInt = 1;
|
|
|
|
var prop1Id = NetworkObject.GetPropertySlot( testPropertyId, comp1.Id );
|
|
|
|
var go2 = new GameObject();
|
|
go2.Parent = go;
|
|
var comp2 = go2.Components.Create<NetworkTestComponent>();
|
|
comp2.SyncInt = 2;
|
|
|
|
var prop2Id = NetworkObject.GetPropertySlot( testPropertyId, comp2.Id );
|
|
|
|
var go3 = new GameObject();
|
|
go3.Parent = go2;
|
|
var comp3 = go3.Components.Create<NetworkTestComponent>();
|
|
comp3.SyncInt = 3;
|
|
|
|
var prop3Id = NetworkObject.GetPropertySlot( testPropertyId, comp3.Id );
|
|
|
|
go.NetworkSpawn();
|
|
|
|
Assert.IsTrue( go._net.dataTable.IsRegistered( prop1Id ) );
|
|
Assert.IsTrue( go._net.dataTable.IsRegistered( prop2Id ) );
|
|
Assert.IsTrue( go._net.dataTable.IsRegistered( prop3Id ) );
|
|
|
|
Assert.AreEqual( 1, comp1.SyncInt );
|
|
Assert.AreEqual( 2, comp2.SyncInt );
|
|
Assert.AreEqual( 3, comp3.SyncInt );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkRefreshWithParentChangeHasCorrectPosition()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var client = new NetworkSystem( "client", TypeLibrary );
|
|
Networking.System = client;
|
|
|
|
var sceneSystem = new SceneNetworkSystem( TypeLibrary, client );
|
|
client.GameSystem = sceneSystem;
|
|
|
|
var client1 = new MockConnection( Guid.NewGuid() );
|
|
var client2 = new MockConnection( Guid.NewGuid() );
|
|
|
|
Connection.Local = client1;
|
|
|
|
var go1 = new GameObject();
|
|
var go2 = new GameObject( go1 )
|
|
{
|
|
WorldPosition = new Vector3( 100f, 100f, 100f )
|
|
};
|
|
|
|
go1.NetworkSpawn( Connection.Local );
|
|
|
|
var go3 = new GameObject();
|
|
go3.NetworkSpawn( Connection.Local );
|
|
|
|
go3.Parent = go2;
|
|
|
|
var refreshMsg = go3._net.GetRefreshMessage();
|
|
|
|
Connection.Local = client2;
|
|
|
|
// Reset the transform to default as it would be when client first constructs it
|
|
go3.SetParentFromNetwork( null, new Transform() );
|
|
|
|
// Now simulate the refresh message from the owner
|
|
go3._net.OnRefreshMessage( client1, refreshMsg );
|
|
|
|
Assert.AreEqual( go2, go3.Parent );
|
|
Assert.AreEqual( go2.WorldPosition, go3.WorldPosition );
|
|
Assert.AreEqual( Vector3.Zero, go3.LocalPosition );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RemoteObjectChildSpawnShouldHaveCorrectTransform()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var client = new NetworkSystem( "client", TypeLibrary );
|
|
Networking.System = client;
|
|
|
|
var sceneSystem = new SceneNetworkSystem( TypeLibrary, client );
|
|
client.GameSystem = sceneSystem;
|
|
|
|
var client1 = new MockConnection( Guid.NewGuid() );
|
|
var client2 = new MockConnection( Guid.NewGuid() );
|
|
|
|
Connection.Local = client1;
|
|
|
|
var go1 = new GameObject();
|
|
var go2 = new GameObject( go1 )
|
|
{
|
|
WorldPosition = new Vector3( 100f, 100f, 100f )
|
|
};
|
|
|
|
go1.NetworkSpawn( Connection.Local );
|
|
|
|
var go3 = new GameObject( go2 );
|
|
go3.NetworkSpawn( Connection.Local );
|
|
|
|
var createMsg = go3._net.GetCreateMessage();
|
|
|
|
Connection.Local = client2;
|
|
|
|
// Reset the transform to default as it would be when client first constructs it
|
|
go3.SetParentFromNetwork( null, new Transform() );
|
|
|
|
// Now simulate the creation message from the owner
|
|
go3._net.OnCreateMessage( createMsg );
|
|
|
|
Assert.AreEqual( go2.WorldPosition, go3.WorldPosition );
|
|
Assert.AreEqual( Vector3.Zero, go3.LocalPosition );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HostCanParentToAnything()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var server = new NetworkSystem( "server", TypeLibrary );
|
|
server.InitializeHost();
|
|
|
|
Networking.System = server;
|
|
server.GameSystem = new SceneNetworkSystem( TypeLibrary, server );
|
|
|
|
var go = new GameObject();
|
|
go.NetworkSpawn( Connection.Local );
|
|
|
|
var go2 = new GameObject();
|
|
go2.NetworkSpawn( new MockConnection( Guid.NewGuid() ) );
|
|
|
|
var go3 = new GameObject();
|
|
go3.NetworkSpawn( Connection.Local );
|
|
|
|
// We should be able to parent to go3 because we're the host.
|
|
go.Parent = go3;
|
|
Assert.AreEqual( go3, go.Parent );
|
|
|
|
// We should be able to parent to go2, even though we don't own it, because we're the host.
|
|
go.Parent = go2;
|
|
Assert.AreEqual( go2, go.Parent );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkChildShouldReplicateWhenParentIsDisabled()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var client = new NetworkSystem( "client", TypeLibrary );
|
|
Networking.System = client;
|
|
|
|
var sceneSystem = new SceneNetworkSystem( TypeLibrary, client );
|
|
client.GameSystem = sceneSystem;
|
|
|
|
var parentObject = new GameObject
|
|
{
|
|
NetworkMode = NetworkMode.Object
|
|
};
|
|
|
|
var childObject = new GameObject( parentObject )
|
|
{
|
|
NetworkMode = NetworkMode.Object
|
|
};
|
|
|
|
parentObject.NetworkSpawn( new NetworkSpawnOptions
|
|
{
|
|
StartEnabled = false
|
|
} );
|
|
|
|
Assert.IsFalse( parentObject.Enabled );
|
|
Assert.IsTrue( childObject.Enabled );
|
|
|
|
Assert.IsNotNull( childObject._net );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SnapshotVersionBlocksOldSnapshots()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var client = new NetworkSystem( "client", TypeLibrary );
|
|
Networking.System = client;
|
|
|
|
var sceneSystem = new SceneNetworkSystem( TypeLibrary, client );
|
|
client.GameSystem = sceneSystem;
|
|
|
|
var client1 = new MockConnection( Guid.NewGuid() );
|
|
var client2 = new MockConnection( Guid.NewGuid() );
|
|
|
|
// Become client1
|
|
Connection.Local = client1;
|
|
|
|
var go = new GameObject();
|
|
go.Network.SetOwnerTransfer( OwnerTransfer.Takeover );
|
|
|
|
// Disable interpolation for this test to help prove the bug, because
|
|
// otherwise when we test the position later, it'll be in an interpolation
|
|
// buffer
|
|
go.Network.DisableInterpolation();
|
|
|
|
go.NetworkSpawn( client2 );
|
|
|
|
var go2 = new GameObject();
|
|
IDeltaSnapshot networkObject = go._net;
|
|
|
|
// Become client2
|
|
Connection.Local = client2;
|
|
|
|
// client2 now owns it, let's have it record a snapshot in this state
|
|
var state = networkObject.WriteSnapshotState();
|
|
var snapshot = new DeltaSnapshot();
|
|
snapshot.CopyFrom( state, 2 );
|
|
snapshot.LocalState = state;
|
|
snapshot.Source = networkObject;
|
|
|
|
// Become client1 again
|
|
Connection.Local = client1;
|
|
|
|
// Assume control of the network object
|
|
go.Network.TakeOwnership();
|
|
|
|
// Change its parent and set position
|
|
go.Parent = go2;
|
|
go.WorldPosition = new Vector3( 0f, 0f, 100f );
|
|
|
|
// Drop control of the object, give it back to client2
|
|
go.Network.AssignOwnership( client2 );
|
|
|
|
// Now we'll process that old snapshot from client2
|
|
using ( var reader = ByteStream.CreateReader( SerializeSnapshot( snapshot ) ) )
|
|
{
|
|
sceneSystem.DeltaSnapshots.OnDeltaSnapshot( client2, reader );
|
|
}
|
|
|
|
// These should be equal, because the old snapshot did NOT apply
|
|
Assert.AreEqual( new Vector3( 0f, 0f, 100f ), go.WorldPosition );
|
|
}
|
|
|
|
byte[] SerializeSnapshot( DeltaSnapshot snapshot )
|
|
{
|
|
using var writer = new ByteStream( DeltaSnapshotCluster.MaxSize * 4 );
|
|
|
|
writer.Write( snapshot.ObjectId );
|
|
writer.Write( snapshot.Version );
|
|
writer.Write( snapshot.SnapshotId );
|
|
writer.Write( (ushort)snapshot.Entries.Count );
|
|
|
|
foreach ( var entry in snapshot.Entries )
|
|
{
|
|
writer.Write( entry.Slot );
|
|
writer.WriteArray( entry.Value );
|
|
}
|
|
|
|
return writer.ToArray();
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ClientCanOnlyParentToObjectsTheyOwn()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var server = new NetworkSystem( "client", TypeLibrary );
|
|
Networking.System = server;
|
|
server.GameSystem = new SceneNetworkSystem( TypeLibrary, server );
|
|
|
|
var go = new GameObject();
|
|
go.NetworkSpawn( Connection.Local );
|
|
|
|
var go2 = new GameObject();
|
|
go2.NetworkSpawn( new MockConnection( Guid.NewGuid() ) );
|
|
|
|
var go3 = new GameObject();
|
|
go3.NetworkSpawn( Connection.Local );
|
|
|
|
// We should be able to parent to go3 because we own it also.
|
|
go.Parent = go3;
|
|
Assert.AreEqual( go3, go.Parent );
|
|
|
|
// We should still be equal to go3, because we don't own go2.
|
|
go.Parent = go2;
|
|
Assert.AreEqual( go3, go.Parent );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ObjectRefreshRegister()
|
|
{
|
|
Assert.IsNotNull( TypeLibrary.GetType<ModelRenderer>(), "TypeLibrary hasn't been given the game assembly" );
|
|
|
|
using var scope = new Scene().Push();
|
|
|
|
var testComponentType = TypeLibrary.GetType<NetworkTestComponent>();
|
|
Assert.IsNotNull( testComponentType );
|
|
|
|
var testSyncPropertyType = testComponentType.GetProperty( "SyncInt" );
|
|
Assert.IsNotNull( testSyncPropertyType );
|
|
|
|
var testPropertyId = testSyncPropertyType.Identity;
|
|
|
|
var go = new GameObject();
|
|
var comp1 = go.Components.Create<NetworkTestComponent>();
|
|
comp1.SyncInt = 1;
|
|
|
|
var prop1Id = NetworkObject.GetPropertySlot( testPropertyId, comp1.Id );
|
|
|
|
go.NetworkSpawn();
|
|
|
|
var go2 = new GameObject();
|
|
go2.Parent = go;
|
|
var comp2 = go2.Components.Create<NetworkTestComponent>();
|
|
comp2.SyncInt = 2;
|
|
|
|
var prop2Id = NetworkObject.GetPropertySlot( testPropertyId, comp2.Id );
|
|
|
|
Assert.IsTrue( go._net.dataTable.IsRegistered( prop1Id ) );
|
|
Assert.IsFalse( go._net.dataTable.IsRegistered( prop2Id ) );
|
|
|
|
go.Network.Refresh();
|
|
|
|
Assert.IsTrue( go._net.dataTable.IsRegistered( prop2Id ) );
|
|
|
|
Assert.AreEqual( 1, comp1.SyncInt );
|
|
Assert.AreEqual( 2, comp2.SyncInt );
|
|
}
|
|
|
|
/// <summary>
|
|
/// When loading a scene with networked objects, those objects must not emit <see cref="ObjectCreateMsg"/>
|
|
/// inside a <see cref="SceneNetworkSystem.SuppressSpawnMessages"/> scope.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void TestSuppressSpawnMessages()
|
|
{
|
|
using var testSystem = Helpers.InitializeHostWithTestConnection();
|
|
using var _ = SceneNetworkSystem.SuppressSpawnMessages();
|
|
|
|
// Scene contains a networked game object
|
|
|
|
Helpers.LoadSceneFromJson( "example.scene",
|
|
"""
|
|
{
|
|
"__guid": "86b89011-9646-4ee7-ad30-c0e11d258674",
|
|
"Name": "Networked Object",
|
|
"Enabled": true,
|
|
"NetworkMode": 1
|
|
}
|
|
""" );
|
|
|
|
Assert.AreEqual( 0, testSystem.GetMessageCount<ObjectCreateMsg>() );
|
|
}
|
|
|
|
private class NetworkTestComponent : Component
|
|
{
|
|
[Sync] public int SyncInt { get; set; }
|
|
}
|
|
}
|