ProjectZ/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs
2024-02-19 21:00:36 +03:00

604 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Mirror.Tests.ClientSceneTests
{
public class PayloadTestBehaviour : NetworkBehaviour
{
public int value;
public Vector3 direction;
public event Action OnDeserializeCalled;
public event Action OnSerializeCalled;
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
base.OnSerialize(writer, initialState);
writer.WriteInt(value);
writer.WriteVector3(direction);
OnSerializeCalled?.Invoke();
return true;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
base.OnDeserialize(reader, initialState);
value = reader.ReadInt();
direction = reader.ReadVector3();
OnDeserializeCalled?.Invoke();
}
}
public class BehaviourWithEvents : NetworkBehaviour
{
public event Action OnStartAuthorityCalled;
public event Action OnStartClientCalled;
public event Action OnStartLocalPlayerCalled;
public override void OnStartAuthority()
{
OnStartAuthorityCalled?.Invoke();
}
public override void OnStartClient()
{
OnStartClientCalled?.Invoke();
}
public override void OnStartLocalPlayer()
{
OnStartLocalPlayerCalled?.Invoke();
}
}
public class ClientSceneTests_OnSpawn : ClientSceneTestsBase
{
Dictionary<uint, NetworkIdentity> spawned => NetworkClient.spawned;
[TearDown]
public override void TearDown()
{
spawned.Clear();
base.TearDown();
}
[Test]
public void FindOrSpawnObject_FindExistingObject()
{
CreateNetworked(out _, out NetworkIdentity existing);
const uint netId = 1000;
existing.netId = netId;
spawned.Add(netId, existing);
SpawnMessage msg = new SpawnMessage
{
netId = netId
};
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity found);
Assert.IsTrue(success);
Assert.That(found, Is.EqualTo(existing));
}
[Test]
public void FindOrSpawnObject_ErrorWhenNoExistingAndAssetIdAndSceneIdAreBothEmpty()
{
const uint netId = 1001;
SpawnMessage msg = new SpawnMessage
{
assetId = new Guid(),
sceneId = 0,
netId = netId
};
LogAssert.Expect(LogType.Error, $"OnSpawn message with netId '{netId}' has no AssetId or sceneId");
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsFalse(success);
Assert.IsNull(networkIdentity);
}
[Test]
public void FindOrSpawnObject_SpawnsFromPrefabDictionary()
{
const uint netId = 1002;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
NetworkClient.prefabs.Add(validPrefabGuid, validPrefab);
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsTrue(success);
Assert.IsNotNull(networkIdentity);
Assert.That(networkIdentity.name, Is.EqualTo($"{validPrefab.name}(Clone)"));
// cleanup
GameObject.DestroyImmediate(networkIdentity.gameObject);
}
[Test]
public void FindOrSpawnObject_ErrorWhenPrefabInNullInDictionary()
{
const uint netId = 1002;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
// could happen if the prefab is destroyed or unloaded
NetworkClient.prefabs.Add(validPrefabGuid, null);
LogAssert.Expect(LogType.Error, $"Failed to spawn server object, did you forget to add it to the NetworkManager? assetId={msg.assetId} netId={msg.netId}");
LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}");
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsFalse(success);
Assert.IsNull(networkIdentity);
}
[Test]
public void FindOrSpawnObject_SpawnsFromPrefabIfBothPrefabAndHandlerExists()
{
const uint netId = 1003;
int handlerCalled = 0;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
NetworkClient.prefabs.Add(validPrefabGuid, validPrefab);
NetworkClient.spawnHandlers.Add(validPrefabGuid, x =>
{
handlerCalled++;
CreateNetworked(out GameObject go, out NetworkIdentity _);
return go;
});
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsTrue(success);
Assert.IsNotNull(networkIdentity);
Assert.That(networkIdentity.name, Is.EqualTo($"{validPrefab.name}(Clone)"));
Assert.That(handlerCalled, Is.EqualTo(0), "Handler should not have been called");
}
[Test]
public void FindOrSpawnObject_SpawnHandlerCalledFromDictionary()
{
const uint netId = 1003;
int handlerCalled = 0;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
GameObject createdInhandler = null;
NetworkClient.spawnHandlers.Add(validPrefabGuid, x =>
{
handlerCalled++;
Assert.That(x, Is.EqualTo(msg));
CreateNetworked(out createdInhandler, out NetworkIdentity _);
return createdInhandler;
});
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsTrue(success);
Assert.IsNotNull(networkIdentity);
Assert.That(handlerCalled, Is.EqualTo(1));
Assert.That(networkIdentity.gameObject, Is.EqualTo(createdInhandler), "Object returned should be the same object created by the spawn handler");
}
[Test]
public void FindOrSpawnObject_ErrorWhenSpawnHanlderReturnsNull()
{
const uint netId = 1003;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
NetworkClient.spawnHandlers.Add(validPrefabGuid, (x) => null);
LogAssert.Expect(LogType.Error, $"Spawn Handler returned null, Handler assetId '{msg.assetId}'");
LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}");
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsFalse(success);
Assert.IsNull(networkIdentity);
}
[Test]
public void FindOrSpawnObject_ErrorWhenSpawnHanlderReturnsWithoutNetworkIdentity()
{
const uint netId = 1003;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
assetId = validPrefabGuid
};
NetworkClient.spawnHandlers.Add(validPrefabGuid, (x) =>
{
CreateGameObject(out GameObject go);
return go;
});
LogAssert.Expect(LogType.Error, $"Object Spawned by handler did not have a NetworkIdentity, Handler assetId '{validPrefabGuid}'");
LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}");
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsFalse(success);
Assert.IsNull(networkIdentity);
}
NetworkIdentity CreateSceneObject(ulong sceneId)
{
CreateNetworked(out _, out NetworkIdentity identity);
// set sceneId to zero as it is set in onvalidate (does not set id at runtime)
identity.sceneId = sceneId;
NetworkClient.spawnableObjects.Add(sceneId, identity);
return identity;
}
[Test]
public void FindOrSpawnObject_UsesSceneIdToSpawnFromSpawnableObjectsDictionary()
{
const uint netId = 1003;
const int sceneId = 100020;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
sceneId = sceneId
};
NetworkIdentity sceneObject = CreateSceneObject(sceneId);
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsTrue(success);
Assert.IsNotNull(networkIdentity);
Assert.That(networkIdentity, Is.EqualTo(sceneObject));
}
[Test]
public void FindOrSpawnObject_SpawnsUsingSceneIdInsteadOfAssetId()
{
const uint netId = 1003;
const int sceneId = 100020;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
sceneId = sceneId,
assetId = validPrefabGuid
};
NetworkClient.prefabs.Add(validPrefabGuid, validPrefab);
NetworkIdentity sceneObject = CreateSceneObject(sceneId);
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsTrue(success);
Assert.IsNotNull(networkIdentity);
Assert.That(networkIdentity, Is.EqualTo(sceneObject));
}
[Test]
public void FindOrSpawnObject_ErrorWhenSceneIdIsNotInSpawnableObjectsDictionary()
{
const uint netId = 1004;
const int sceneId = 100021;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
sceneId = sceneId,
};
LogAssert.Expect(LogType.Error, $"Spawn scene object not found for {msg.sceneId:X}. Make sure that client and server use exactly the same project. This only happens if the hierarchy gets out of sync.");
LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}");
bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity);
Assert.IsFalse(success);
Assert.IsNull(networkIdentity);
}
[Test]
public void ApplyPayload_AppliesTransform()
{
const uint netId = 1000;
CreateNetworked(out GameObject _, out NetworkIdentity identity);
Vector3 position = new Vector3(10, 0, 20);
Quaternion rotation = Quaternion.Euler(0, 45, 0);
Vector3 scale = new Vector3(1.5f, 1.5f, 1.5f);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = false,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = position,
rotation = rotation,
scale = scale,
payload = default,
};
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(identity.transform.position, Is.EqualTo(position));
// use angle because of floating point numbers
// only need to check if rotations are approximately equal
Assert.That(Quaternion.Angle(identity.transform.rotation, rotation), Is.LessThan(0.0001f));
Assert.That(identity.transform.localScale, Is.EqualTo(scale));
}
[Test]
public void ApplyPayload_AppliesLocalValuesToTransform()
{
const uint netId = 1000;
CreateGameObject(out GameObject parent);
parent.transform.position = new Vector3(100, 20, 0);
parent.transform.rotation = Quaternion.LookRotation(Vector3.left);
parent.transform.localScale = Vector3.one * 2;
CreateNetworked(out GameObject go, out NetworkIdentity identity);
go.transform.parent = parent.transform;
Vector3 position = new Vector3(10, 0, 20);
Quaternion rotation = Quaternion.Euler(0, 45, 0);
Vector3 scale = new Vector3(1.5f, 1.5f, 1.5f);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = false,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = position,
rotation = rotation,
scale = scale,
payload = default,
};
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(identity.transform.localPosition, Is.EqualTo(position));
// use angle because of floating point numbers
// only need to check if rotations are approximately equal
Assert.That(Quaternion.Angle(identity.transform.localRotation, rotation), Is.LessThan(0.0001f));
Assert.That(identity.transform.localScale, Is.EqualTo(scale));
}
[Test]
[TestCase(true)]
[TestCase(false)]
public void ApplyPayload_AppliesAuthority(bool isOwner)
{
const uint netId = 1000;
CreateNetworked(out _, out NetworkIdentity identity);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = isOwner,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default
};
// set to opposite to make sure it is changed
identity.hasAuthority = !isOwner;
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(identity.hasAuthority, Is.EqualTo(isOwner));
}
[Test]
[TestCase(true)]
[TestCase(false)]
public void ApplyPayload_EnablesObject(bool startActive)
{
const uint netId = 1000;
CreateNetworked(out GameObject go, out NetworkIdentity identity);
go.SetActive(startActive);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = false,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default,
};
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.IsTrue(identity.gameObject.activeSelf);
}
[Test]
public void ApplyPayload_SetsAssetId()
{
const uint netId = 1000;
CreateNetworked(out _, out NetworkIdentity identity);
Guid guid = Guid.NewGuid();
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = false,
sceneId = 0,
assetId = guid,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default
};
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.IsTrue(identity.gameObject.activeSelf);
Assert.That(identity.assetId, Is.EqualTo(guid));
}
[Test]
public void ApplyPayload_DoesNotSetAssetIdToEmpty()
{
const uint netId = 1000;
CreateNetworked(out _, out NetworkIdentity identity);
Guid guid = Guid.NewGuid();
identity.assetId = guid;
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = false,
isOwner = false,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default
};
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(identity.assetId, Is.EqualTo(guid), "AssetId should not have changed");
}
[Test]
public void ApplyPayload_LocalPlayerAddsIdentityToConnection()
{
Debug.Assert(NetworkClient.localPlayer == null, "LocalPlayer should be null before this test");
const uint netId = 1000;
CreateNetworked(out _, out NetworkIdentity identity);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = true,
isOwner = true,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default,
};
NetworkClient.connection = new FakeNetworkConnection();
NetworkClient.ready = true;
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(NetworkClient.localPlayer, Is.EqualTo(identity));
Assert.That(NetworkClient.connection.identity, Is.EqualTo(identity));
}
[Test]
public void ApplyPayload_LocalPlayerWarningWhenNoReadyConnection()
{
Debug.Assert(NetworkClient.localPlayer == null, "LocalPlayer should be null before this test");
const uint netId = 1000;
CreateNetworked(out _, out NetworkIdentity identity);
SpawnMessage msg = new SpawnMessage
{
netId = netId,
isLocalPlayer = true,
isOwner = true,
sceneId = 0,
assetId = Guid.Empty,
// use local values for VR support
position = Vector3.zero,
rotation = Quaternion.identity,
scale = Vector3.one,
payload = default,
};
LogAssert.Expect(LogType.Warning, "No ready connection found for setting player controller during InternalAddPlayer");
NetworkClient.ApplySpawnPayload(identity, msg);
Assert.That(NetworkClient.localPlayer, Is.EqualTo(identity));
}
[Flags]
public enum SpawnFinishedState
{
isSpawnFinished = 1,
hasAuthority = 2,
isLocalPlayer = 4
}
[Test]
public void OnSpawn_GiveNoExtraErrorsWhenPrefabIsntSpawned()
{
const int netId = 20033;
Debug.Assert(spawned.Count == 0, "There should be no spawned objects before test");
SpawnMessage msg = new SpawnMessage
{
netId = netId,
};
// Check for log that FindOrSpawnObject gives, and make sure there are no other error logs
LogAssert.Expect(LogType.Error, $"OnSpawn message with netId '{netId}' has no AssetId or sceneId");
NetworkClient.OnSpawn(msg);
Assert.That(spawned, Is.Empty);
}
}
}