Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added NetworkIdentity Spawn Methods & Tests #3201

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
185 changes: 168 additions & 17 deletions Assets/Mirror/Core/NetworkServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace Mirror
/// <summary>NetworkServer handles remote connections and has a local connection for a local client.</summary>
public static partial class NetworkServer
{
static bool initialized;
public static int maxConnections;
static bool initialized;
public static int maxConnections;

/// <summary>Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.</summary>
// overwritten by NetworkManager (if any)
Expand All @@ -28,7 +28,7 @@ public static partial class NetworkServer
// -> time is interpolated globally on NetworkClient / NetworkConnection
// -> value is interpolated per-component, i.e. NetworkTransform.
// however, both need to be on the same send interval.
public static int sendRate => tickRate;
public static int sendRate => tickRate;
public static float sendInterval => sendRate < int.MaxValue ? 1f / sendRate : 0; // for 30 Hz, that's 33ms
static double lastSendTime;

Expand Down Expand Up @@ -70,17 +70,17 @@ public static partial class NetworkServer
// Connected/Disconnected messages over the network causing undefined
// behaviour.
// => public so that custom NetworkManagers can hook into it
public static Action<NetworkConnectionToClient> OnConnectedEvent;
public static Action<NetworkConnectionToClient> OnDisconnectedEvent;
public static Action<NetworkConnectionToClient> OnConnectedEvent;
public static Action<NetworkConnectionToClient> OnDisconnectedEvent;
public static Action<NetworkConnectionToClient, TransportError, string> OnErrorEvent;

// keep track of actual achieved tick rate.
// might become lower under heavy load.
// very useful for profiling etc.
// measured over 1s each, same as frame rate. no EMA here.
public static int actualTickRate;
static double actualTickRateStart; // start time when counting
static int actualTickRateCounter; // current counter since start
static double actualTickRateStart; // start time when counting
static int actualTickRateCounter; // current counter since start

// profiling
// includes transport update time, because transport calls handlers etc.
Expand Down Expand Up @@ -116,26 +116,26 @@ static void Initialize()

// profiling
earlyUpdateDuration = new TimeSample(sendRate);
lateUpdateDuration = new TimeSample(sendRate);
fullUpdateDuration = new TimeSample(sendRate);
lateUpdateDuration = new TimeSample(sendRate);
fullUpdateDuration = new TimeSample(sendRate);
}

static void AddTransportHandlers()
{
// += so that other systems can also hook into it (i.e. statistics)
Transport.active.OnServerConnected += OnTransportConnected;
Transport.active.OnServerConnected += OnTransportConnected;
Transport.active.OnServerDataReceived += OnTransportData;
Transport.active.OnServerDisconnected += OnTransportDisconnected;
Transport.active.OnServerError += OnTransportError;
Transport.active.OnServerError += OnTransportError;
}

static void RemoveTransportHandlers()
{
// -= so that other systems can also hook into it (i.e. statistics)
Transport.active.OnServerConnected -= OnTransportConnected;
Transport.active.OnServerConnected -= OnTransportConnected;
Transport.active.OnServerDataReceived -= OnTransportData;
Transport.active.OnServerDisconnected -= OnTransportDisconnected;
Transport.active.OnServerError -= OnTransportError;
Transport.active.OnServerError -= OnTransportError;
}

// calls OnStartClient for all SERVER objects in host mode once.
Expand Down Expand Up @@ -1218,32 +1218,171 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
RebuildObservers(identity, true);
}

static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
{
// ensure network identity is not null
if (identity == null)
{
Debug.LogError($"SpawnObject has no NetworkIdentity. Please add a NetworkIdentity to the SpawnObject");
return;
}

GameObject obj = identity.gameObject;

// verify if we can spawn this
if (Utils.IsPrefab(obj))
{
Debug.LogError($"GameObject {obj.name} is a prefab, it can't be spawned. Instantiate it first.");
return;
}

if (identity.SpawnedFromInstantiate)
{
// Using Instantiate on SceneObject is not allowed, so stop spawning here
// NetworkIdentity.Awake already logs error, no need to log a second error here
return;
}

// Spawn should only be called once per netId
if (spawned.ContainsKey(identity.netId))
{
Debug.LogWarning($"{identity} with netId={identity.netId} was already spawned.");
return;
}

identity.connectionToClient = (NetworkConnectionToClient)ownerConnection;

// special case to make sure hasAuthority is set
// on start server in host mode
if (ownerConnection is LocalConnectionToClient)
identity.isOwned = true;

// only call OnStartServer if not spawned yet.
// check used to be in NetworkIdentity. may not be necessary anymore.
if (!identity.isServer && identity.netId == 0)
{
// configure NetworkIdentity
identity.isLocalPlayer = NetworkClient.localPlayer == identity;
identity.isClient = NetworkClient.active;
identity.isServer = true;
identity.netId = NetworkIdentity.GetNextNetworkId();

// add to spawned (after assigning netId)
spawned[identity.netId] = identity;

// callback after all fields were set
identity.OnStartServer();
}

// Debug.Log($"SpawnObject instance ID {identity.netId} asset ID {identity.assetId}");

if (aoi)
{
// This calls user code which might throw exceptions
// We don't want this to leave us in bad state
try
{
aoi.OnSpawned(identity);
}
catch (Exception e)
{
Debug.LogException(e);
}
}

RebuildObservers(identity, true);
}

/// <summary>Spawn the given game object on all clients which are ready.</summary>
// This will cause a new object to be instantiated from the registered
// prefab, or from a custom spawn function.
public static void Spawn(GameObject obj, NetworkConnection ownerConnection = null)
{
SpawnObject(obj, ownerConnection);
NetworkIdentity identity = obj.GetComponent<NetworkIdentity>();

if(identity == null)
{
Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
return;
}

SpawnObject(identity, ownerConnection);
}

/// <summary>Spawn the given game object on all clients which are ready.</summary>
// This will cause a new object to be instantiated from the registered
// prefab, or from a custom spawn function.
public static void Spawn(NetworkIdentity identity, NetworkConnection ownerConnection = null)
{
SpawnObject(identity, ownerConnection);
}

/// <summary>Spawns an object and also assigns Client Authority to the specified client.</summary>
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, GameObject ownerPlayer)
{
NetworkIdentity identity = ownerPlayer.GetComponent<NetworkIdentity>();
NetworkIdentity identity = obj.GetComponent<NetworkIdentity>();
NetworkIdentity ownerIdentity = ownerPlayer.GetComponent<NetworkIdentity>();

if (identity == null)
{
Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
return;
}

if (ownerIdentity == null)
{
Debug.LogError("Player object has no NetworkIdentity");
return;
}

if (identity.connectionToClient == null)
if (ownerIdentity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}

Spawn(obj, identity.connectionToClient);
Spawn(identity, ownerIdentity.connectionToClient);
}

/// <summary>Spawns an object and also assigns Client Authority to the specified client.</summary>
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
{
NetworkIdentity ownerIdentity = ownerPlayer.GetComponent<NetworkIdentity>();

if (ownerIdentity == null)
{
Debug.LogError("Player object has no NetworkIdentity");
return;
}

if (ownerIdentity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}

Spawn(spawnIdentity, ownerIdentity.connectionToClient);
}

/// <summary>Spawns an object and also assigns Client Authority to the specified client.</summary>
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(NetworkIdentity spawnIdentity, NetworkIdentity ownerIdentity)
{
if (ownerIdentity == null)
{
Debug.LogError("Player object has no NetworkIdentity");
return;
}

if (ownerIdentity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}

Spawn(spawnIdentity, ownerIdentity.connectionToClient);
}

/// <summary>Spawns an object and also assigns Client Authority to the specified client.</summary>
Expand All @@ -1257,6 +1396,18 @@ public static void Spawn(GameObject obj, uint assetId, NetworkConnection ownerCo
SpawnObject(obj, ownerConnection);
}

/// <summary>Spawns an object and also assigns Client Authority to the specified client.</summary>
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(NetworkIdentity identity, uint assetId, NetworkConnection ownerConnection = null)
{
if(identity != null)
{
identity.assetId = assetId;
}

SpawnObject(identity, ownerConnection);
}

/// <summary>Spawns NetworkIdentities in the scene on the server.</summary>
// NetworkIdentity objects in a scene are disabled by default. Calling
// SpawnObjects() causes these scene objects to be enabled and spawned.
Expand Down
45 changes: 45 additions & 0 deletions Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,24 @@ public void ServerMode_IsFlags_Test()
Assert.That(component.OnStartServer_isServer, Is.EqualTo(true));
}

// check isClient/isServer/isLocalPlayer in server-only mode
[Test]
public void ServerMode_IsFlags_Identity_Test()
{
CreateNetworked(out GameObject _, out NetworkIdentity identity, out IsClientServerCheckComponent component);

// start the server
NetworkServer.Listen(1000);

// spawn it
NetworkServer.Spawn(identity);

// OnStartServer should have been called. check the flags.
Assert.That(component.OnStartServer_isClient, Is.EqualTo(false));
Assert.That(component.OnStartServer_isLocalPlayer, Is.EqualTo(false));
Assert.That(component.OnStartServer_isServer, Is.EqualTo(true));
}

// check isClient/isServer/isLocalPlayer in host mode
[Test]
public void HostMode_IsFlags_Test()
Expand Down Expand Up @@ -305,6 +323,33 @@ public void HostMode_IsFlags_Test()
NetworkServer.RemoveLocalConnection();
}

// check isClient/isServer/isLocalPlayer in host mode
[Test]
public void HostMode_IsFlags_Identity_Test()
{
CreateNetworked(out GameObject gameObject, out NetworkIdentity identity, out IsClientServerCheckComponent component);

// start the server
NetworkServer.Listen(1000);

// start the client
NetworkClient.ConnectHost();

// set is as local player
NetworkClient.InternalAddPlayer(identity);

// spawn it
NetworkServer.Spawn(identity);

// OnStartServer should have been called. check the flags.
Assert.That(component.OnStartServer_isClient, Is.EqualTo(true));
Assert.That(component.OnStartServer_isLocalPlayer, Is.EqualTo(true));
Assert.That(component.OnStartServer_isServer, Is.EqualTo(true));

// stop the client
NetworkServer.RemoveLocalConnection();
}

[Test]
public void GetSetAssetId()
{
Expand Down
14 changes: 13 additions & 1 deletion Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public IEnumerator Shutdown_DestroysAllSpawnedPrefabs()

NetworkIdentity identity1 = SpawnPrefab(prefab);
NetworkIdentity identity2 = SpawnPrefab(prefab);
NetworkIdentity identity3 = SpawnNetworkPrefab(prefab);
NetworkIdentity identity4 = SpawnNetworkPrefab(prefab);

// shutdown, wait 1 frame for unity to destroy objects
NetworkServer.Shutdown();
Expand All @@ -71,6 +73,8 @@ public IEnumerator Shutdown_DestroysAllSpawnedPrefabs()
// need to use untiy `==` check
Assert.IsTrue(identity1 == null);
Assert.IsTrue(identity2 == null);
Assert.IsTrue(identity3 == null);
Assert.IsTrue(identity4 == null);

Assert.That(NetworkServer.spawned, Is.Empty);
}
Expand All @@ -84,6 +88,14 @@ NetworkIdentity SpawnPrefab(GameObject prefab)
return identity1;
}

NetworkIdentity SpawnNetworkPrefab(GameObject prefab)
{
NetworkIdentity clone1 = GameObject.Instantiate(prefab).GetComponent<NetworkIdentity>();
NetworkServer.Spawn(clone1);
Assert.IsTrue(NetworkServer.spawned.ContainsValue(clone1));
return clone1;
}

[Test]
public void Shutdown_DisablesAllSpawnedPrefabs()
{
Expand All @@ -104,7 +116,7 @@ public void Shutdown_DisablesAllSpawnedPrefabs()
Assert.IsTrue(identity1 != null);
Assert.IsTrue(identity2 != null);
Assert.IsFalse(identity1.gameObject.activeSelf);
Assert.IsFalse(identity1.gameObject.activeSelf);
Assert.IsFalse(identity2.gameObject.activeSelf);

Assert.That(NetworkServer.spawned, Is.Empty);
}
Expand Down