public void UnintializedGhostOwnerThrowsException() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new InvalidUsageConverter(); ghostGameObject.AddComponent <GhostAuthoringComponent>().DefaultGhostMode = GhostAuthoringComponent.GhostMode.OwnerPredicted; Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); LogAssert.Expect(LogType.Error, new Regex("Trying to spawn an owner predicted ghost which does not have a valid owner set. When using owner prediction you must set GhostOwnerComponent.NetworkId when spawning the ghost. If the ghost is not owned by a player you can set NetworkId to -1.")); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } } }
void SetupBasicTest(NetCodeTestWorld testWorld, int entitiesToSpawn = 1) { var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new StaticOptimizationTestConverter(); var ghostConfig = ghostGameObject.AddComponent <GhostAuthoringComponent>(); ghostConfig.OptimizationMode = GhostAuthoringComponent.GhostOptimizationMode.Static; Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); for (int i = 0; i < entitiesToSpawn; ++i) { var serverEnt = testWorld.SpawnOnServer(ghostGameObject); Assert.AreNotEqual(Entity.Null, serverEnt); } // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 16; ++i) { testWorld.Tick(frameTime); } }
public void ExtrapolationProduceSmoothValues() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(MoveExtrapolated), typeof(CheckExtrapolate)); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostExtrapolationConverter(); ghostGameObject.AddComponent <GhostAuthoringComponent>(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); var tickRate = testWorld.ServerWorld.EntityManager.CreateEntity(); // Set low net tick rate to make sure interpolation is used testWorld.ServerWorld.EntityManager.AddComponentData(tickRate, new ClientServerTickRate { NetworkTickRate = 1 }); var clientTickRate = testWorld.ClientWorlds[0].EntityManager.CreateEntity(); // Disable interpolation time to make sure extrapolation is used var tr = NetworkTimeSystem.DefaultClientTickRate; tr.InterpolationTimeNetTicks = 0; tr.MaxExtrapolationTimeSimTicks = 120; testWorld.ClientWorlds[0].EntityManager.AddComponentData(clientTickRate, tr); testWorld.SpawnOnServer(ghostGameObject); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the simulation run for a bit since extrapolation requires two snapshots to be received before it does anything for (int i = 0; i < 256; ++i) { testWorld.Tick(frameTime); } // Enable the checks var clientEnt = testWorld.TryGetSingletonEntity <TestExtrapolated>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, clientEnt); testWorld.ClientWorlds[0].EntityManager.AddComponentData(clientEnt, new ExtrapolateBackup { Value = 0 }); // Let the game run for a bit more and verify that they are extrapolated for (int i = 0; i < 256; ++i) { testWorld.Tick(frameTime); } } }
public void CanRecoverFromDeletingGhostOnClient() { using (var testWorld = new NetCodeTestWorld()) { DeleteGhostOnClientSystem.s_DeleteCount = 1; testWorld.Bootstrap(true, typeof(DeleteGhostOnClientSystem)); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new InvalidUsageConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); var serverEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ServerWorld); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostOwnerComponent { NetworkId = 42 }); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); LogAssert.Expect(LogType.Error, new Regex("Found a ghost in the ghost map which does not have an entity connected to it. This can happen if you delete ghost entities on the client.")); LogAssert.Expect(LogType.Error, new Regex("Ghost ID \\d+ has already been added to the spawned ghost map")); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Validate that the ghost was deleted on the cliet Assert.AreEqual(0, DeleteGhostOnClientSystem.s_DeleteCount); // Check that the client world has the right thing and value var clientEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, clientEnt); Assert.AreEqual(42, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostOwnerComponent>(clientEnt).NetworkId); // Delete on server testWorld.ServerWorld.EntityManager.DestroyEntity(serverEnt); for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } clientEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); Assert.AreEqual(Entity.Null, clientEnt); } }
public void EntityMarkedAsChildIsSentAsPartOfGroup() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.name = "ParentGhost"; ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostGroupGhostConverter(); var childGhostGameObject = new GameObject(); childGhostGameObject.name = "ChildGhost"; childGhostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostGroupGhostConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject, childGhostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); testWorld.SpawnOnServer(childGhostGameObject); var serverEnt = testWorld.TryGetSingletonEntity <GhostGroupRoot>(testWorld.ServerWorld); var serverChildEnt = testWorld.TryGetSingletonEntity <GhostChildEntityComponent>(testWorld.ServerWorld); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostOwnerComponent { NetworkId = 42 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverChildEnt, new GhostOwnerComponent { NetworkId = 43 }); testWorld.ServerWorld.EntityManager.GetBuffer <GhostGroup>(serverEnt).Add(new GhostGroup { Value = serverChildEnt }); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Check that the client world has the right thing and value var clientEnt = testWorld.TryGetSingletonEntity <GhostGroupRoot>(testWorld.ClientWorlds[0]); var clientChildEnt = testWorld.TryGetSingletonEntity <GhostChildEntityComponent>(testWorld.ClientWorlds[0]); Assert.AreEqual(42, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostOwnerComponent>(clientEnt).NetworkId); Assert.AreNotEqual(Entity.Null, clientChildEnt); Assert.AreEqual(43, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostOwnerComponent>(clientChildEnt).NetworkId); } }
public void ChildEntityDataIsReplicated() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new MultiEntityGhostConverter(); var childGhost = new GameObject(); childGhost.transform.parent = ghostGameObject.transform; childGhost.AddComponent <TestNetCodeAuthoring>().Converter = new MultiEntityGhostConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); var serverEnt = testWorld.TryGetSingletonEntity <TopLevelGhostEntity>(testWorld.ServerWorld); Assert.IsTrue(testWorld.ServerWorld.EntityManager.HasComponent <LinkedEntityGroup>(serverEnt)); var serverEntityGroup = testWorld.ServerWorld.EntityManager.GetBuffer <LinkedEntityGroup>(serverEnt); Assert.AreEqual(2, serverEntityGroup.Length); testWorld.ServerWorld.EntityManager.SetComponentData(serverEntityGroup[0].Value, new GhostOwnerComponent { NetworkId = 42 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEntityGroup[1].Value, new GhostOwnerComponent { NetworkId = 42 }); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Check that the client world has the right thing and value var clientEnt = testWorld.TryGetSingletonEntity <TopLevelGhostEntity>(testWorld.ClientWorlds[0]); Assert.IsTrue(testWorld.ClientWorlds[0].EntityManager.HasComponent <LinkedEntityGroup>(clientEnt)); var clientEntityGroup = testWorld.ClientWorlds[0].EntityManager.GetBuffer <LinkedEntityGroup>(clientEnt); Assert.AreEqual(2, clientEntityGroup.Length); Assert.AreEqual(42, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostOwnerComponent>(clientEntityGroup[0].Value).NetworkId); Assert.AreEqual(42, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostOwnerComponent>(clientEntityGroup[1].Value).NetworkId); } }
public void GhostsWithSameArchetypeAreDifferent() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(GhostTypeIndex0TestGhostSpawnSystem), typeof(GhostTypeIndex0TestGhostUpdateSystem), typeof(GhostTypeIndex1TestGhostSpawnSystem), typeof(GhostTypeIndex1TestGhostUpdateSystem), typeof(GhostTypeTestGhostSendSystem), typeof(GhostTypeTestGhostReceiveSystem)); var ghostGameObject0 = new GameObject(); ghostGameObject0.AddComponent <TestNetCodeAuthoring>().Converter = new GhostTypeIndexConverter(); ghostGameObject0.name = "GhostTypeIndex0Test"; var ghostGameObject1 = new GameObject(); ghostGameObject1.AddComponent <TestNetCodeAuthoring>().Converter = new GhostTypeIndexConverter(); ghostGameObject1.name = "GhostTypeIndex1Test"; Assert.IsTrue(testWorld.CreateGhostCollection( "/../Packages/com.unity.netcode/Tests/Editor/Generated/", "GhostTypeTest", ghostGameObject0, ghostGameObject1)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject0); testWorld.SpawnOnServer(ghostGameObject0); testWorld.SpawnOnServer(ghostGameObject1); testWorld.SpawnOnServer(ghostGameObject1); VerifyGhostTypes(testWorld.ServerWorld); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Assert that replicated version is correct VerifyGhostTypes(testWorld.ClientWorlds[0]); } }
public void GhostCollectionGenerateSameHashOnClientAndServer() { using (var testWorld = new NetCodeTestWorld()) { var ghost1 = new GameObject(); ghost1.AddComponent <TestNetCodeAuthoring>().Converter = new TestConverter(); ghost1.AddComponent <GhostAuthoringComponent>().DefaultGhostMode = GhostAuthoringComponent.GhostMode.Predicted; var ghost2 = new GameObject(); ghost2.AddComponent <TestNetCodeAuthoring>().Converter = new TestConverter(); ghost2.AddComponent <GhostAuthoringComponent>().DefaultGhostMode = GhostAuthoringComponent.GhostMode.Interpolated; testWorld.Bootstrap(true); testWorld.CreateGhostCollection(ghost1, ghost2); testWorld.CreateWorlds(true, 1); float frameTime = 1.0f / 60.0f; var serverCollectionSystem = testWorld.ServerWorld.GetExistingSystem <GhostCollectionSystem>(); var clientCollectionSystem = testWorld.ClientWorlds[0].GetExistingSystem <GhostCollectionSystem>(); //First tick: compute on both client and server the ghost collection hash testWorld.Tick(frameTime); Assert.AreEqual(serverCollectionSystem.CalculateComponentCollectionHash(), clientCollectionSystem.CalculateComponentCollectionHash()); // compare the list of loaded prefabs var serverCollectionSingleton = testWorld.TryGetSingletonEntity <GhostCollection>(testWorld.ServerWorld); var clientCollectionSingleton = testWorld.TryGetSingletonEntity <GhostCollection>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, serverCollectionSingleton); Assert.AreNotEqual(Entity.Null, clientCollectionSingleton); var serverCollection = testWorld.ServerWorld.EntityManager.GetBuffer <GhostCollectionPrefab>(serverCollectionSingleton); var clientCollection = testWorld.ClientWorlds[0].EntityManager.GetBuffer <GhostCollectionPrefab>(clientCollectionSingleton); Assert.AreEqual(serverCollection.Length, clientCollection.Length); for (int i = 0; i < serverCollection.Length; ++i) { Assert.AreEqual(serverCollection[i].GhostType, clientCollection[i].GhostType); Assert.AreEqual(serverCollection[i].Hash, clientCollection[i].Hash); } //Check that and server can connect (same component hash) Assert.IsTrue(testWorld.Connect(frameTime, 4)); testWorld.GoInGame(); for (int i = 0; i < 10; ++i) { testWorld.Tick(frameTime); } Assert.IsTrue(testWorld.ClientWorlds[0].GetExistingSystem <NetworkStreamReceiveSystem>() .HasSingleton <NetworkIdComponent>()); } }
public void ServerGhostCountIsVisibleOnClient() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new LateJoinCompletionConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); for (int i = 0; i < 8; ++i) { testWorld.SpawnOnServer(ghostGameObject); } float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } var ghostReceiveSystem = testWorld.ClientWorlds[0].GetExistingSystem <GhostReceiveSystem>(); // Validate that the ghost was deleted on the cliet Assert.AreEqual(8, ghostReceiveSystem.GhostCountOnServer); Assert.AreEqual(8, ghostReceiveSystem.GhostCountOnClient); // Spawn a few more and verify taht the count is updated for (int i = 0; i < 8; ++i) { testWorld.SpawnOnServer(ghostGameObject); } for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } Assert.AreEqual(16, ghostReceiveSystem.GhostCountOnServer); Assert.AreEqual(16, ghostReceiveSystem.GhostCountOnClient); } }
public void EntityReferenceSetAtSpawnIsResolved() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostValueSerializerConverter(); var referencedGameObject = new GameObject(); referencedGameObject.AddComponent <GhostOwnerComponentAuthoring>(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject, referencedGameObject)); testWorld.CreateWorlds(true, 1); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } var serverRefEntity = testWorld.SpawnOnServer(referencedGameObject); var serverEnt = testWorld.SpawnOnServer(ghostGameObject); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostValueSerializer { EntityValue = serverRefEntity }); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 8; ++i) { testWorld.Tick(frameTime); var clientRefEntity = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); var clientEntity = testWorld.TryGetSingletonEntity <GhostValueSerializer>(testWorld.ClientWorlds[0]); if (clientEntity != Entity.Null) { // Make sure the reference always exist if the ghost exists Assert.AreEqual(clientRefEntity, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostValueSerializer>(clientEntity).EntityValue); } } // Verify that we did get the referenced entity at some point Assert.AreNotEqual(Entity.Null, testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0])); } }
public void MaxSmoothingDistanceIsUsed() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(MoveExtrapolated), typeof(CheckInterpolationDistance)); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostExtrapolationConverter(); ghostGameObject.AddComponent <GhostAuthoringComponent>(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); var tickRate = testWorld.ServerWorld.EntityManager.CreateEntity(); // Set low net tick rate to make sure interpolation is used testWorld.ServerWorld.EntityManager.AddComponentData(tickRate, new ClientServerTickRate { NetworkTickRate = 30 }); testWorld.SpawnOnServer(ghostGameObject); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); for (int i = 0; i < 8; ++i) { testWorld.Tick(frameTime); } var clientEnt = testWorld.TryGetSingletonEntity <TestExtrapolated>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, clientEnt); Assert.Less(testWorld.ClientWorlds[0].EntityManager.GetComponentData <TestExtrapolated>(clientEnt).Value, 100); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 256; ++i) { testWorld.Tick(frameTime); } Assert.Greater(testWorld.ClientWorlds[0].EntityManager.GetComponentData <TestExtrapolated>(clientEnt).Value, 200); } }
public void StructWithLargeNumberOfFields() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostGenBigStructConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); var serverEntity = testWorld.SpawnOnServer(ghostGameObject); //Use reflection.. just because if is faster var data = default(GhostGenBigStruct); unsafe { var values = (int *)UnsafeUtility.AddressOf(ref data); for (int i = 0; i < 100; ++i) { values[i] = i; } } testWorld.ServerWorld.EntityManager.SetComponentData(serverEntity, data); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 16; ++i) { testWorld.Tick(frameTime); } var clientEntity = testWorld.TryGetSingletonEntity <GhostGenBigStruct>(testWorld.ClientWorlds[0]); var clientData = testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostGenBigStruct>(clientEntity); var serverData = testWorld.ServerWorld.EntityManager.GetComponentData <GhostGenBigStruct>(serverEntity); Assert.AreEqual(serverData, clientData); } }
public void GhostValuesAreSerialized() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostValueSerializerConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); SetGhostValues(testWorld, 42); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } VerifyGhostValues(testWorld); SetGhostValues(testWorld, 43); for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Assert that replicated version is correct VerifyGhostValues(testWorld); } }
public void EntityReferenceUnavailableGhostIsResolved() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostValueSerializerConverter(); var referencedGameObject = new GameObject(); referencedGameObject.AddComponent <GhostOwnerComponentAuthoring>(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject, referencedGameObject)); testWorld.CreateWorlds(true, 1); var ghostSendSystem = testWorld.ServerWorld.GetExistingSystem <GhostSendSystem>(); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); ghostSendSystem.GhostRelevancyMode = GhostRelevancyMode.SetIsRelevant; // Go in-game testWorld.GoInGame(); for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } var con = testWorld.TryGetSingletonEntity <NetworkIdComponent>(testWorld.ServerWorld); Assert.AreNotEqual(Entity.Null, con); var serverConnectionId = testWorld.ServerWorld.EntityManager.GetComponentData <NetworkIdComponent>(con).Value; var serverRefEntity = testWorld.SpawnOnServer(referencedGameObject); var serverEnt = testWorld.SpawnOnServer(ghostGameObject); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostValueSerializer { EntityValue = serverRefEntity }); testWorld.Tick(frameTime); var serverGhostId = testWorld.ServerWorld.EntityManager.GetComponentData <GhostComponent>(serverEnt).ghostId; var serverRefGhostId = testWorld.ServerWorld.EntityManager.GetComponentData <GhostComponent>(serverRefEntity).ghostId; // only mark the entity with the ref as relevant so that arrived before the referenced entity exists ghostSendSystem.GhostRelevancySet.TryAdd(new RelevantGhostForConnection(serverConnectionId, serverGhostId), 1); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 8; ++i) { testWorld.Tick(frameTime); var clientRefEntity = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); var clientEntity = testWorld.TryGetSingletonEntity <GhostValueSerializer>(testWorld.ClientWorlds[0]); if (clientEntity != Entity.Null) { // Make sure the reference always exist if the ghost exists Assert.AreEqual(clientRefEntity, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostValueSerializer>(clientEntity).EntityValue); } } // Verify that we did not the referenced entity since it is irrelevant Assert.AreEqual(Entity.Null, testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0])); ghostSendSystem.GhostRelevancySet.TryAdd(new RelevantGhostForConnection(serverConnectionId, serverRefGhostId), 1); for (int i = 0; i < 8; ++i) { testWorld.Tick(frameTime); var clientRefEntity = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); var clientEntity = testWorld.TryGetSingletonEntity <GhostValueSerializer>(testWorld.ClientWorlds[0]); if (clientEntity != Entity.Null) { // Make sure the reference always exist if the ghost exists Assert.AreEqual(clientRefEntity, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostValueSerializer>(clientEntity).EntityValue); } } Assert.AreNotEqual(Entity.Null, testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0])); // Delete the referenced entity and make sure the ref is updated testWorld.ServerWorld.EntityManager.DestroyEntity(serverRefEntity); int mismatchFrames = 0; for (int i = 0; i < 8; ++i) { testWorld.Tick(frameTime); var clientRefEntity = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); var clientEntity = testWorld.TryGetSingletonEntity <GhostValueSerializer>(testWorld.ClientWorlds[0]); if (clientEntity != Entity.Null) { // The desapwn order might not be the same between client and server, if the server has despawned the entity there will be no reference, // but the client despawns at the end of the frame it was destroyed so it might still exist for one frame Assert.IsFalse(clientRefEntity == Entity.Null && testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostValueSerializer>(clientEntity).EntityValue != Entity.Null); if (clientRefEntity != testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostValueSerializer>(clientEntity).EntityValue) { ++mismatchFrames; } } } Assert.LessOrEqual(mismatchFrames, 1); Assert.AreEqual(Entity.Null, testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0])); } }
public void SendToOwner_Clients_ReceiveTheCorrectData(GhostAuthoringComponent.GhostModeMask modeMask, GhostAuthoringComponent.GhostMode mode) { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(InputSystem)); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new TestComponentConverter(); var ghostConfig = ghostGameObject.AddComponent <GhostAuthoringComponent>(); ghostConfig.SupportedGhostModes = modeMask; ghostConfig.DefaultGhostMode = mode; //Some context about where owner make sense: //interpolated ghost: does even make sense that a ghost has an owner? Yes, it does and it is usually the server. // Can be a player ??? Yes it can. In that case, the player can still control the ghost via command but it will not predict the // ghost movement. Only the server will compute the correct position. The client will always see a delayed and interpolated replica. //Predicted ghost: owner make absolutely sense. //OwnerPredicted: by definition Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 2); //Here I do a trick: I will wait until the CollectionSystem is run and the component collection built. //Then I will change the serializer flags a little to make them behave the way I want. //This is a temporary hack, can be remove whe override per prefab will be available. var queryServer = testWorld.ServerWorld.EntityManager.CreateEntityQuery(ComponentType.ReadOnly <GhostCollection>()); var queryClient0 = testWorld.ClientWorlds[0].EntityManager.CreateEntityQuery(ComponentType.ReadOnly <GhostCollection>()); var queryClient1 = testWorld.ClientWorlds[1].EntityManager.CreateEntityQuery(ComponentType.ReadOnly <GhostCollection>()); while (true) { testWorld.Tick(1.0f / 60f); if (queryServer.IsEmptyIgnoreFilter || queryClient0.IsEmptyIgnoreFilter || queryClient1.IsEmptyIgnoreFilter) { continue; } if (testWorld.ServerWorld.EntityManager.GetBuffer <GhostComponentSerializer.State>(queryServer.GetSingletonEntity()).Length == 0 || testWorld.ServerWorld.EntityManager.GetBuffer <GhostComponentSerializer.State>(queryServer.GetSingletonEntity()).Length == 0 || testWorld.ServerWorld.EntityManager.GetBuffer <GhostComponentSerializer.State>(queryServer.GetSingletonEntity()).Length == 0) { continue; } ChangeSendToOwnerOption(testWorld.ServerWorld); ChangeSendToOwnerOption(testWorld.ClientWorlds[0]); ChangeSendToOwnerOption(testWorld.ClientWorlds[1]); break; } Assert.IsTrue(testWorld.Connect(1.0f / 60.0f, 64)); testWorld.GoInGame(); var net1 = testWorld.TryGetSingletonEntity <NetworkIdComponent>(testWorld.ClientWorlds[0]); var netId1 = testWorld.ClientWorlds[0].EntityManager.GetComponentData <NetworkIdComponent>(net1); var serverEnt = testWorld.SpawnOnServer(ghostGameObject); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostGen_IntStruct { IntValue = 10000 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostTypeIndex { Value = 20000 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostPredictedOnly { Value = 30000 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostInterpolatedOnly { Value = 40000 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostOwnerComponent { NetworkId = netId1.Value }); var serverBuffer1 = testWorld.ServerWorld.EntityManager.GetBuffer <GhostGenBuffer_ByteBuffer>(serverEnt); serverBuffer1.Capacity = 10; for (int i = 0; i < 10; ++i) { serverBuffer1.Add(new GhostGenBuffer_ByteBuffer { Value = (byte)(10 + i) }); } var serverBuffer2 = testWorld.ServerWorld.EntityManager.GetBuffer <GhostGenTest_Buffer>(serverEnt); serverBuffer2.Capacity = 10; for (int i = 0; i < 10; ++i) { serverBuffer2.Add(new GhostGenTest_Buffer()); } for (int i = 0; i < 16; ++i) { testWorld.Tick(1.0f / 60.0f); } serverBuffer1 = testWorld.ServerWorld.EntityManager.GetBuffer <GhostGenBuffer_ByteBuffer>(serverEnt); serverBuffer2 = testWorld.ServerWorld.EntityManager.GetBuffer <GhostGenTest_Buffer>(serverEnt); var serverComp1 = testWorld.ServerWorld.EntityManager.GetComponentData <GhostGen_IntStruct>(serverEnt); var serverComp2 = testWorld.ServerWorld.EntityManager.GetComponentData <GhostTypeIndex>(serverEnt); var predictedOnly = testWorld.ServerWorld.EntityManager.GetComponentData <GhostPredictedOnly>(serverEnt); var interpOnly = testWorld.ServerWorld.EntityManager.GetComponentData <GhostInterpolatedOnly>(serverEnt); for (int i = 0; i < 2; ++i) { var clientEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[i]); var clientComp1_ToOwner = testWorld.ClientWorlds[i].EntityManager.GetComponentData <GhostGen_IntStruct>(clientEnt); var clientComp2_NonOwner = testWorld.ClientWorlds[i].EntityManager.GetComponentData <GhostTypeIndex>(clientEnt); var clientPredOnly = testWorld.ClientWorlds[i].EntityManager.GetComponentData <GhostPredictedOnly>(clientEnt); var clientInterpOnly = testWorld.ClientWorlds[i].EntityManager.GetComponentData <GhostInterpolatedOnly>(clientEnt); var clientBuffer1 = testWorld.ClientWorlds[i].EntityManager.GetBuffer <GhostGenBuffer_ByteBuffer>(clientEnt); var clientBuffer2 = testWorld.ClientWorlds[i].EntityManager.GetBuffer <GhostGenTest_Buffer>(clientEnt); Assert.AreEqual(i == 0, serverComp1.IntValue == clientComp1_ToOwner.IntValue, $"Client {i}"); Assert.AreEqual(i == 1, serverComp2.Value == clientComp2_NonOwner.Value, $"Client {i}"); //The component are sent to all the clients and only the SendToOwner matter if (mode == GhostAuthoringComponent.GhostMode.Predicted) { Assert.AreEqual(i == 0, predictedOnly.Value == clientPredOnly.Value, $"Client {i}"); Assert.AreEqual(false, interpOnly.Value == clientInterpOnly.Value, $"Client {i}"); } else if (mode == GhostAuthoringComponent.GhostMode.Interpolated) { Assert.AreEqual(false, predictedOnly.Value == clientPredOnly.Value, $"Client {i}"); Assert.AreEqual(i == 1, interpOnly.Value == clientInterpOnly.Value, $"Client {i}"); } else if (mode == GhostAuthoringComponent.GhostMode.OwnerPredicted) { Assert.AreEqual(i == 0, predictedOnly.Value == clientPredOnly.Value, $"Client {i}"); Assert.AreEqual(i == 1, interpOnly.Value == clientInterpOnly.Value, $"Client {i}"); } Assert.AreEqual(true, 10 == clientBuffer1.Length); Assert.AreEqual(i == 1, 10 == clientBuffer2.Length); Assert.AreEqual(i == 0, 0 == clientBuffer2.Length); for (int k = 0; k < clientBuffer1.Length; ++k) { Assert.AreEqual(serverBuffer1[k].Value, clientBuffer1[k].Value, $"Client {i}"); } for (int k = 0; k < clientBuffer2.Length; ++k) { Assert.AreEqual(serverBuffer2[k].IntValue, clientBuffer2[k].IntValue, $"Client {i}"); } } } }
public void OverrideComponentSendType_ChildEntity() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var names = new[] { "All", "Interpolated", "Predicted", "None" }; var sendTypes = new[] { GhostSendType.All, GhostSendType.Interpolated, GhostSendType.Predicted, (GhostSendType)0 }; var collection = CreatePrefabs(names); for (int i = 0; i < sendTypes.Length; ++i) { var authoring = collection[i].GetComponent <GhostAuthoringComponent>(); authoring.ComponentOverrides = new List <GhostAuthoringComponent.ComponentOverride> { new GhostAuthoringComponent.ComponentOverride { fullTypeName = typeof(GhostGen_IntStruct).FullName, gameObject = collection[i].transform.GetChild(0).gameObject, PrefabType = (int)GhostPrefabType.All, OwnerPredictedSendType = (int)sendTypes[i], SendToChild = GhostAuthoringComponent.ComponentOverride.UseDefaultValue, ComponentVariant = 0 } }; } Assert.IsTrue(testWorld.CreateGhostCollection(collection)); testWorld.CreateWorlds(true, 1); //Register serializers and setup all the system for (int i = 0; i < 16; ++i) { testWorld.Tick(1.0f / 60.0f); } //In order to get the collection setup I need to enter in game testWorld.Connect(1.0f / 60f, 16); testWorld.GoInGame(); for (int i = 0; i < collection.Length; ++i) { testWorld.SpawnOnServer(collection[i]); } for (int i = 0; i < 16; ++i) { testWorld.Tick(1.0f / 60.0f); } //Then check the expected results var collectionEntity = testWorld.TryGetSingletonEntity <GhostCollection>(testWorld.ServerWorld); var ghostCollection = testWorld.ServerWorld.EntityManager.GetBuffer <GhostCollectionComponentIndex>(collectionEntity); var ghostComponentCollection = testWorld.ServerWorld.EntityManager.GetBuffer <GhostCollectionComponentType>(collectionEntity); var type = TypeManager.GetTypeIndex(typeof(GhostGen_IntStruct)); var index = 0; while (index < ghostCollection.Length && ghostComponentCollection[ghostCollection[index].ComponentIndex].Type.TypeIndex != type) { ++index; } var serializerIndex = ghostCollection[index].SerializerIndex; CheckCollection(testWorld.ServerWorld, serializerIndex, 1); CheckCollection(testWorld.ClientWorlds[0], serializerIndex, 1); } }
public void Rpc_CanSendEntityFromClientAndServer() { void SendRpc(World world, Entity entity) { var req = world.EntityManager.CreateEntity(); world.EntityManager.AddComponentData(req, new RpcWithEntity { entity = entity }); world.EntityManager.AddComponentData(req, new SendRpcCommandRequestComponent { TargetConnection = Entity.Null }); } RpcWithEntity RecvRpc(World world) { var query = world.EntityManager.CreateEntityQuery(ComponentType.ReadOnly <RpcWithEntity>()); Assert.AreEqual(1, query.CalculateEntityCount()); var rpcReceived = query.GetSingleton <RpcWithEntity>(); world.EntityManager.DestroyEntity(query); return(rpcReceived); } using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(RpcWithEntityRpcCommandRequestSystem)); var ghostGameObject = new GameObject("SimpleGhost"); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostConverter(); testWorld.CreateGhostCollection(ghostGameObject); testWorld.CreateWorlds(true, 1); float frameTime = 1.0f / 60.0f; Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); var serverEntity = testWorld.SpawnOnServer(ghostGameObject); //Wait some frame so it is spawned also on the client for (int i = 0; i < 8; ++i) { testWorld.Tick(16f / 1000f); } // Retrieve the client entity testWorld.ClientWorlds[0].GetExistingSystem <GhostSimulationSystemGroup>().LastGhostMapWriter.Complete(); var ghost = testWorld.ServerWorld.EntityManager.GetComponentData <GhostComponent>(serverEntity); Assert.IsTrue(testWorld.ClientWorlds[0].GetExistingSystem <GhostSimulationSystemGroup>().SpawnedGhostEntityMap .TryGetValue(new SpawnedGhost { ghostId = ghost.ghostId, spawnTick = ghost.spawnTick }, out var clientEntity)); //Send the rpc to the server SendRpc(testWorld.ClientWorlds[0], clientEntity); for (int i = 0; i < 8; ++i) { testWorld.Tick(16f / 1000f); } var rpcReceived = RecvRpc(testWorld.ServerWorld); Assert.IsTrue(rpcReceived.entity != Entity.Null); Assert.IsTrue(rpcReceived.entity == serverEntity); // Server send the rpc to the client SendRpc(testWorld.ServerWorld, serverEntity); for (int i = 0; i < 8; ++i) { testWorld.Tick(16f / 1000f); } rpcReceived = RecvRpc(testWorld.ClientWorlds[0]); Assert.IsTrue(rpcReceived.entity != Entity.Null); Assert.IsTrue(rpcReceived.entity == clientEntity); // Client try to send a client-only entity -> result in a Entity.Null reference //Send the rpc to the server var clientOnlyEntity = testWorld.ClientWorlds[0].EntityManager.CreateEntity(); SendRpc(testWorld.ClientWorlds[0], clientOnlyEntity); for (int i = 0; i < 8; ++i) { testWorld.Tick(16f / 1000f); } rpcReceived = RecvRpc(testWorld.ServerWorld); Assert.IsTrue(rpcReceived.entity == Entity.Null); // Some Edge cases: // 1 - Entity has been or going to be despawned on the client. Expected: server will receive an Entity.Null in the rpc // 2 - Entity has been despawn on the server but the client. Server will not be able to resolve the entity correctly // in that window, since the ghost mapping is reset //Destroy the entity on the server testWorld.ServerWorld.EntityManager.DestroyEntity(serverEntity); //Let the client try to send an rpc for it (this mimic sort of latency) SendRpc(testWorld.ClientWorlds[0], clientEntity); //Entity is destroyed on the server (so no GhostComponent). If server try to send an rpc, the entity will be translated to null SendRpc(testWorld.ServerWorld, serverEntity); for (int i = 0; i < 4; ++i) { testWorld.Tick(16f / 1000f); } //Server should not be able to resolve the reference rpcReceived = RecvRpc(testWorld.ServerWorld); Assert.IsTrue(rpcReceived.entity == Entity.Null); //On the client must but null rpcReceived = RecvRpc(testWorld.ClientWorlds[0]); Assert.IsTrue(rpcReceived.entity == Entity.Null); //If client send the rpc now (the entity should not exists anymore and the mapping should be reset on both client and server now) Assert.IsFalse(testWorld.ClientWorlds[0].GetExistingSystem <GhostSimulationSystemGroup>().SpawnedGhostEntityMap .TryGetValue(new SpawnedGhost { ghostId = ghost.ghostId, spawnTick = ghost.spawnTick }, out var _)); Assert.IsFalse(testWorld.ServerWorld.GetExistingSystem <GhostSimulationSystemGroup>().SpawnedGhostEntityMap .TryGetValue(new SpawnedGhost { ghostId = ghost.ghostId, spawnTick = ghost.spawnTick }, out var _)); SendRpc(testWorld.ClientWorlds[0], clientEntity); for (int i = 0; i < 4; ++i) { testWorld.Tick(16f / 1000f); } //The received entity must be null rpcReceived = RecvRpc(testWorld.ServerWorld); Assert.IsTrue(rpcReceived.entity == Entity.Null); } }
private void TestHelper(bool predicted, bool ownerPrediction) { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new GhostPredictedOnlyConverter(); var ghostConfig = ghostGameObject.AddComponent <GhostAuthoringComponent>(); if (ownerPrediction) { ghostConfig.DefaultGhostMode = GhostAuthoringComponent.GhostMode.OwnerPredicted; } else { ghostConfig.SupportedGhostModes = predicted ? GhostAuthoringComponent.GhostModeMask.Predicted : GhostAuthoringComponent.GhostModeMask.Interpolated; } Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); testWorld.SpawnOnServer(ghostGameObject); var serverEnt = testWorld.TryGetSingletonEntity <GhostPredictedOnly>(testWorld.ServerWorld); Assert.AreNotEqual(Entity.Null, serverEnt); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostPredictedOnly { Value = 1 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostInterpolatedOnly { Value = 1 }); testWorld.ServerWorld.EntityManager.SetComponentData(serverEnt, new GhostOwnerComponent { NetworkId = predicted ? 1 : 2 }); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Check the clients network id var serverCon = testWorld.TryGetSingletonEntity <NetworkIdComponent>(testWorld.ServerWorld); Assert.AreNotEqual(Entity.Null, serverCon); Assert.AreEqual(1, testWorld.ServerWorld.EntityManager.GetComponentData <NetworkIdComponent>(serverCon).Value); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime); } // Check that the client world has the right thing and value var clientEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, clientEnt); Assert.AreEqual(predicted, testWorld.ClientWorlds[0].EntityManager.HasComponent <PredictedGhostComponent>(clientEnt)); Assert.AreEqual(predicted ? 0 : 1, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostInterpolatedOnly>(clientEnt).Value); Assert.AreEqual(predicted ? 1 : 0, testWorld.ClientWorlds[0].EntityManager.GetComponentData <GhostPredictedOnly>(clientEnt).Value); } }
public void SerializationVariant_AreAppliedToBothRootAndChildEntities() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject("Root"); var childGhost = new GameObject("Child"); childGhost.transform.parent = ghostGameObject.transform; var authoring = ghostGameObject.AddComponent <GhostAuthoringComponent>(); authoring.DefaultGhostMode = GhostAuthoringComponent.GhostMode.Interpolated; authoring.SupportedGhostModes = GhostAuthoringComponent.GhostModeMask.All; //Setup a variant for both root and child entity and check that the runtime serializer use this one to serialize data var attrType = typeof(TranslationVariantTest).GetCustomAttribute <GhostComponentVariationAttribute>(); var hash = GhostComponentVariationAttribute.ComputeVariantHash(typeof(TranslationVariantTest), attrType); authoring.ComponentOverrides = new List <GhostAuthoringComponent.ComponentOverride> { new GhostAuthoringComponent.ComponentOverride { fullTypeName = typeof(Transforms.Translation).FullName, gameObject = ghostGameObject, PrefabType = (int)GhostPrefabType.All, OwnerPredictedSendType = (int)GhostSendType.All, ComponentVariant = hash }, new GhostAuthoringComponent.ComponentOverride { fullTypeName = typeof(Transforms.Translation).FullName, gameObject = childGhost, PrefabType = (int)GhostPrefabType.All, OwnerPredictedSendType = (int)GhostSendType.All, ComponentVariant = hash } }; Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject), "Cannot create ghost collection"); testWorld.CreateWorlds(true, 1); //Register serializers and setup all the system for (int i = 0; i < 16; ++i) { testWorld.Tick(1.0f / 60.0f); } //In order to get the collection setup I need to enter in game testWorld.Connect(1.0f / 60f, 16); testWorld.GoInGame(); testWorld.SpawnOnServer(ghostGameObject); for (int i = 0; i < 16; ++i) { testWorld.Tick(1.0f / 60.0f); } var typeIndex = TypeManager.GetTypeIndex <Transforms.Translation>(); //Then check the expected results var collection = testWorld.TryGetSingletonEntity <GhostCollection>(testWorld.ServerWorld); var ghostSerializerCollection = testWorld.ServerWorld.EntityManager.GetBuffer <GhostComponentSerializer.State>(collection); //Check that the variant has been registered bool variantIsPresent = false; foreach (var t in ghostSerializerCollection) { variantIsPresent |= t.VariantHash == hash; } Assert.IsTrue(variantIsPresent); var componentIndex = testWorld.ServerWorld.EntityManager.GetBuffer <GhostCollectionComponentIndex>(collection); var ghostPrefabCollection = testWorld.ServerWorld.EntityManager.GetBuffer <GhostCollectionPrefabSerializer>(collection); //And verify that the component associated with the ghost for the transform point to this index for (int i = 0; i < ghostPrefabCollection[0].NumComponents; ++i) { var idx = componentIndex[ghostPrefabCollection[0].FirstComponent + i]; if (ghostSerializerCollection[idx.SerializerIndex].ComponentType.TypeIndex == typeIndex) { Assert.IsTrue(ghostSerializerCollection[idx.SerializerIndex].VariantHash == hash); } } } }
public void ServerGhostCountOnlyIncludesRelevantSet() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true); var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new LateJoinCompletionConverter(); Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); float frameTime = 1.0f / 60.0f; // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); for (int i = 0; i < 8; ++i) { testWorld.SpawnOnServer(ghostGameObject); } // Go in-game testWorld.GoInGame(); testWorld.Tick(frameTime); // Setup relevancy var ghostSendSystem = testWorld.ServerWorld.GetExistingSystem <GhostSendSystem>(); ghostSendSystem.GhostRelevancyMode = GhostRelevancyMode.SetIsRelevant; var serverConnectionEnt = testWorld.TryGetSingletonEntity <NetworkIdComponent>(testWorld.ServerWorld); var serverConnectionId = testWorld.ServerWorld.EntityManager.GetComponentData <NetworkIdComponent>(serverConnectionEnt).Value; var query = testWorld.ServerWorld.EntityManager.CreateEntityQuery(ComponentType.ReadWrite <GhostComponent>()); var ghosts = query.ToComponentDataArray <GhostComponent>(Allocator.Temp); Assert.AreEqual(ghosts.Length, 8); for (int i = 0; i < 6; ++i) { ghostSendSystem.GhostRelevancySet.TryAdd(new RelevantGhostForConnection { Ghost = ghosts[i].ghostId, Connection = serverConnectionId }, 1); } // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } var ghostReceiveSystem = testWorld.ClientWorlds[0].GetExistingSystem <GhostReceiveSystem>(); // Validate that the ghost was deleted on the cliet Assert.AreEqual(6, ghostReceiveSystem.GhostCountOnServer); Assert.AreEqual(6, ghostReceiveSystem.GhostCountOnClient); // Spawn a few more and verify taht the count is updated for (int i = 0; i < 8; ++i) { testWorld.SpawnOnServer(ghostGameObject); } for (int i = 0; i < 4; ++i) { testWorld.Tick(frameTime); } Assert.AreEqual(6, ghostReceiveSystem.GhostCountOnServer); Assert.AreEqual(6, ghostReceiveSystem.GhostCountOnClient); } }
public void PartialPredictionTicksAreRolledBack() { using (var testWorld = new NetCodeTestWorld()) { testWorld.Bootstrap(true, typeof(PredictionTestPredictionSystem)); PredictionTestPredictionSystem.s_IsEnabled = true; var ghostGameObject = new GameObject(); ghostGameObject.AddComponent <TestNetCodeAuthoring>().Converter = new PredictionTestConverter(); var ghostConfig = ghostGameObject.AddComponent <GhostAuthoringComponent>(); ghostConfig.DefaultGhostMode = GhostAuthoringComponent.GhostMode.Predicted; Assert.IsTrue(testWorld.CreateGhostCollection(ghostGameObject)); testWorld.CreateWorlds(true, 1); var serverEnt = testWorld.SpawnOnServer(ghostGameObject); Assert.AreNotEqual(Entity.Null, serverEnt); // Connect and make sure the connection could be established Assert.IsTrue(testWorld.Connect(frameTime, 4)); // Go in-game testWorld.GoInGame(); // Let the game run for a bit so the ghosts are spawned on the client for (int i = 0; i < 16; ++i) { testWorld.Tick(frameTime); } var clientEnt = testWorld.TryGetSingletonEntity <GhostOwnerComponent>(testWorld.ClientWorlds[0]); Assert.AreNotEqual(Entity.Null, clientEnt); var prevServer = testWorld.ServerWorld.EntityManager.GetComponentData <Translation>(serverEnt).Value; var prevClient = testWorld.ClientWorlds[0].EntityManager.GetComponentData <Translation>(clientEnt).Value; for (int i = 0; i < 64; ++i) { testWorld.Tick(frameTime / 4); var curServer = testWorld.ServerWorld.EntityManager.GetComponentData <Translation>(serverEnt).Value; var curClient = testWorld.ClientWorlds[0].EntityManager.GetComponentData <Translation>(clientEnt).Value; // Server does not do fractional ticks so it will not advance the position every frame Assert.GreaterOrEqual(curServer.x, prevServer.x); // Client does fractional ticks and position should be always increasing Assert.Greater(curClient.x, prevClient.x); prevServer = curServer; prevClient = curClient; } // Stop updating, let it run for a while and check that they ended on the same value PredictionTestPredictionSystem.s_IsEnabled = false; for (int i = 0; i < 16; ++i) { testWorld.Tick(frameTime); } prevServer = testWorld.ServerWorld.EntityManager.GetComponentData <Translation>(serverEnt).Value; prevClient = testWorld.ClientWorlds[0].EntityManager.GetComponentData <Translation>(clientEnt).Value; Assert.IsTrue(math.distance(prevServer, prevClient) < 0.01); } }