From 9f188375cd1324c0583892fc87f49269622987e6 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Mon, 8 Aug 2022 18:30:00 +0200
Subject: [PATCH 1/8] Added Identity Spawn Methods
---
Assets/Mirror/Runtime/NetworkServer.cs | 113 +++++++++++++++++++++++++
1 file changed, 113 insertions(+)
diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs
index 44e273a63c..4be0e315e0 100644
--- a/Assets/Mirror/Runtime/NetworkServer.cs
+++ b/Assets/Mirror/Runtime/NetworkServer.cs
@@ -1094,6 +1094,64 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
RebuildObservers(identity, true);
}
+ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
+ {
+ 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 (!active)
+ {
+ Debug.LogError($"SpawnObject for {obj}, NetworkServer is not active. Cannot spawn objects without an active server.");
+ return;
+ }
+
+ if (identity == null)
+ {
+ Debug.LogError($"SpawnObject {obj} has no NetworkIdentity. Please add a NetworkIdentity to {obj}");
+ 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;
+ }
+
+ identity.connectionToClient = (NetworkConnectionToClient)ownerConnection;
+
+ // special case to make sure hasAuthority is set
+ // on start server in host mode
+ if (ownerConnection is LocalConnectionToClient)
+ identity.hasAuthority = true;
+
+ 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);
+ }
+
/// Spawn the given game object on all clients which are ready.
// This will cause a new object to be instantiated from the registered
// prefab, or from a custom spawn function.
@@ -1102,6 +1160,14 @@ public static void Spawn(GameObject obj, NetworkConnection ownerConnection = nul
SpawnObject(obj, ownerConnection);
}
+ /// Spawn the given game object on all clients which are ready.
+ // 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);
+ }
+
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, GameObject ownerPlayer)
@@ -1122,6 +1188,45 @@ public static void Spawn(GameObject obj, GameObject ownerPlayer)
Spawn(obj, identity.connectionToClient);
}
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
+ public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
+ {
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+ 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);
+ }
+
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // 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);
+ }
+
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
@@ -1133,6 +1238,14 @@ public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerCo
SpawnObject(obj, ownerConnection);
}
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
+ public static void Spawn(NetworkIdentity identity, Guid assetId, NetworkConnection ownerConnection = null)
+ {
+ identity.assetId = assetId;
+ SpawnObject(identity, ownerConnection);
+ }
+
internal static bool ValidateSceneObject(NetworkIdentity identity)
{
if (identity.gameObject.hideFlags == HideFlags.NotEditable ||
From 6750d9416173c0846968db8d8d524d255b74d0a9 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Mon, 8 Aug 2022 19:03:40 +0200
Subject: [PATCH 2/8] Added New Tests & Fixed a Test
Added possibly obsolete tests for Spawn using Network Identity
Fixed Shutdown_DisabledAllSpawnedPrefabs() using wrong identity
---
.../Tests/Editor/NetworkIdentityTests.cs | 45 +++++++++++++++++++
.../Tests/Runtime/NetworkServerRuntimeTest.cs | 14 +++++-
2 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
index 1863fa36a5..64ac1b33e3 100644
--- a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
+++ b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
@@ -272,6 +272,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()
@@ -299,6 +317,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()
{
diff --git a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
index 03559af624..c5f3140004 100644
--- a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
+++ b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
@@ -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();
@@ -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);
}
@@ -84,6 +88,14 @@ NetworkIdentity SpawnPrefab(GameObject prefab)
return identity1;
}
+ NetworkIdentity SpawnNetworkPrefab(GameObject prefab)
+ {
+ NetworkIdentity clone1 = GameObject.Instantiate(prefab).GetComponent();
+ NetworkServer.Spawn(clone1);
+ Assert.IsTrue(NetworkServer.spawned.ContainsValue(clone1));
+ return clone1;
+ }
+
[Test]
public void Shutdown_DisablesAllSpawnedPrefabs()
{
@@ -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);
}
From 0e7cb11f37fa475370b87ec2803350a296763531 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Thu, 11 Aug 2022 20:41:25 +0200
Subject: [PATCH 3/8] Adjusted Spawn methods to use NetworkIdentity
---
Assets/Mirror/Runtime/NetworkServer.cs | 58 +++++++++++++++++++-------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs
index 4be0e315e0..935ffd5bdb 100644
--- a/Assets/Mirror/Runtime/NetworkServer.cs
+++ b/Assets/Mirror/Runtime/NetworkServer.cs
@@ -1096,6 +1096,13 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
{
+ // ensure spawn object has a network identity
+ 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
@@ -1111,12 +1118,6 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
return;
}
- if (identity == null)
- {
- Debug.LogError($"SpawnObject {obj} has no NetworkIdentity. Please add a NetworkIdentity to {obj}");
- return;
- }
-
if (identity.SpawnedFromInstantiate)
{
// Using Instantiate on SceneObject is not allowed, so stop spawning here
@@ -1157,7 +1158,15 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
// prefab, or from a custom spawn function.
public static void Spawn(GameObject obj, NetworkConnection ownerConnection = null)
{
- SpawnObject(obj, ownerConnection);
+ NetworkIdentity identity = obj.GetComponent();
+
+ if (identity == null)
+ {
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
+ return;
+ }
+
+ SpawnObject(identity, ownerConnection);
}
/// Spawn the given game object on all clients which are ready.
@@ -1172,20 +1181,28 @@ public static void Spawn(NetworkIdentity identity, NetworkConnection ownerConnec
// 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 identity = obj.GetComponent();
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
if (identity == null)
{
- Debug.LogError("Player object has no NetworkIdentity");
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
return;
}
- if (identity.connectionToClient == null)
+ if (ownerIdentity == null)
+ {
+ Debug.LogError("Player object is not a player.");
+ return;
+ }
+
+ if (ownerIdentity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}
- Spawn(obj, identity.connectionToClient);
+ Spawn(identity, ownerIdentity.connectionToClient);
}
/// Spawns an object and also assigns Client Authority to the specified client.
@@ -1193,6 +1210,7 @@ public static void Spawn(GameObject obj, GameObject ownerPlayer)
public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
{
NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
if (ownerIdentity == null)
{
Debug.LogError("Player object has no NetworkIdentity");
@@ -1231,18 +1249,28 @@ public static void Spawn(NetworkIdentity spawnIdentity, NetworkIdentity ownerIde
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
{
- if (GetNetworkIdentity(obj, out NetworkIdentity identity))
+ NetworkIdentity identity = obj.GetComponent();
+
+ if (identity == null)
{
- identity.assetId = assetId;
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
+ return;
}
- SpawnObject(obj, ownerConnection);
+
+ identity.assetId = assetId;
+
+ SpawnObject(identity, ownerConnection);
}
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(NetworkIdentity identity, Guid assetId, NetworkConnection ownerConnection = null)
{
- identity.assetId = assetId;
+ if(identity != null)
+ {
+ identity.assetId = assetId;
+ }
+
SpawnObject(identity, ownerConnection);
}
From 84e7ac61c9b27462e3fd3d473b7cbb2f8b459068 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Mon, 8 Aug 2022 18:30:00 +0200
Subject: [PATCH 4/8] Added Identity Spawn Methods
---
Assets/Mirror/Core/NetworkServer.cs | 445 ++++++++++++----------------
1 file changed, 188 insertions(+), 257 deletions(-)
diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs
index 063b573361..4be0e315e0 100644
--- a/Assets/Mirror/Core/NetworkServer.cs
+++ b/Assets/Mirror/Core/NetworkServer.cs
@@ -7,30 +7,10 @@
namespace Mirror
{
/// NetworkServer handles remote connections and has a local connection for a local client.
- public static partial class NetworkServer
+ public static class NetworkServer
{
- static bool initialized;
- public static int maxConnections;
-
- /// 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.
- // overwritten by NetworkManager (if any)
- public static int tickRate = 30;
-
- // tick rate is in Hz.
- // convert to interval in seconds for convenience where needed.
- //
- // send interval is 1 / sendRate.
- // but for tests we need a way to set it to exactly 0.
- // 1 / int.max would not be exactly 0, so handel that manually.
- public static float tickInterval => tickRate < int.MaxValue ? 1f / tickRate : 0; // for 30 Hz, that's 33ms
-
- // time & value snapshot interpolation are separate.
- // -> 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 float sendInterval => sendRate < int.MaxValue ? 1f / sendRate : 0; // for 30 Hz, that's 33ms
- static double lastSendTime;
+ static bool initialized;
+ public static int maxConnections;
/// Connection to host mode client (if any)
public static NetworkConnectionToClient localConnection { get; private set; }
@@ -70,27 +50,10 @@ 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 OnConnectedEvent;
- public static Action OnDisconnectedEvent;
+ public static Action OnConnectedEvent;
+ public static Action OnDisconnectedEvent;
public static Action 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
-
- // profiling
- // includes transport update time, because transport calls handlers etc.
- // averaged over 1s by passing 'tickRate' to constructor.
- public static TimeSample earlyUpdateDuration;
- public static TimeSample lateUpdateDuration;
-
- // capture full Unity update time from before Early- to after LateUpdate
- public static TimeSample fullUpdateDuration;
-
// initialization / shutdown ///////////////////////////////////////////
static void Initialize()
{
@@ -109,33 +72,28 @@ static void Initialize()
// reset NetworkTime
NetworkTime.ResetStatics();
- Debug.Assert(Transport.active != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.active' first");
+ Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.activeTransport' first");
AddTransportHandlers();
initialized = true;
-
- // profiling
- earlyUpdateDuration = 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.OnServerDataReceived += OnTransportData;
- Transport.active.OnServerDisconnected += OnTransportDisconnected;
- Transport.active.OnServerError += OnTransportError;
+ Transport.activeTransport.OnServerConnected += OnTransportConnected;
+ Transport.activeTransport.OnServerDataReceived += OnTransportData;
+ Transport.activeTransport.OnServerDisconnected += OnTransportDisconnected;
+ Transport.activeTransport.OnServerError += OnTransportError;
}
static void RemoveTransportHandlers()
{
// -= so that other systems can also hook into it (i.e. statistics)
- Transport.active.OnServerConnected -= OnTransportConnected;
- Transport.active.OnServerDataReceived -= OnTransportData;
- Transport.active.OnServerDisconnected -= OnTransportDisconnected;
- Transport.active.OnServerError -= OnTransportError;
+ Transport.activeTransport.OnServerConnected -= OnTransportConnected;
+ Transport.activeTransport.OnServerDataReceived -= OnTransportData;
+ Transport.activeTransport.OnServerDisconnected -= OnTransportDisconnected;
+ Transport.activeTransport.OnServerError -= OnTransportError;
}
// calls OnStartClient for all SERVER objects in host mode once.
@@ -147,7 +105,7 @@ public static void ActivateHostScene()
if (!identity.isClient)
{
// Debug.Log($"ActivateHostScene {identity.netId} {identity}");
- NetworkClient.CheckForStartClient(identity);
+ identity.OnStartClient();
}
}
}
@@ -157,8 +115,6 @@ internal static void RegisterMessageHandlers()
RegisterHandler(OnClientReadyMessage);
RegisterHandler(OnCommandMessage);
RegisterHandler(NetworkTime.OnServerPing, false);
- RegisterHandler(OnEntityStateMessage, true);
- RegisterHandler(OnTimeSnapshotMessage, true);
}
/// Starts server and listens to incoming connections with max connections limit.
@@ -170,7 +126,7 @@ public static void Listen(int maxConns)
// only start server if we want to listen
if (!dontListen)
{
- Transport.active.ServerStart();
+ Transport.activeTransport.ServerStart();
//Debug.Log("Server started listening");
}
@@ -227,7 +183,7 @@ public static void Shutdown()
// someone might enabled dontListen at runtime.
// but we still need to stop the server.
// fixes https://github.com/vis2k/Mirror/issues/2536
- Transport.active.ServerStop();
+ Transport.activeTransport.ServerStop();
// transport handlers are hooked into when initializing.
// so only remove them when shutting down.
@@ -240,7 +196,6 @@ public static void Shutdown()
dontListen = false;
active = false;
isLoadingScene = false;
- lastSendTime = 0;
localConnection = null;
@@ -340,7 +295,7 @@ public static void SendToAll(T message, int channelId = Channels.Reliable, bo
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message only once
- NetworkMessages.Pack(message, writer);
+ MessagePacking.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
// filter and then send to all internet connections at once
@@ -380,13 +335,13 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI
where T : struct, NetworkMessage
{
// Debug.Log($"Server.SendToObservers {typeof(T)}");
- if (identity == null || identity.observers.Count == 0)
+ if (identity == null || identity.observers == null || identity.observers.Count == 0)
return;
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message into byte[] once
- NetworkMessages.Pack(message, writer);
+ MessagePacking.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
foreach (NetworkConnectionToClient conn in identity.observers.Values)
@@ -399,22 +354,22 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI
}
/// Send a message to only clients which are ready with option to include the owner of the object identity
- // TODO obsolete this later. it's not used anymore
+ // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
public static void SendToReadyObservers(NetworkIdentity identity, T message, bool includeOwner = true, int channelId = Channels.Reliable)
where T : struct, NetworkMessage
{
// Debug.Log($"Server.SendToReady {typeof(T)}");
- if (identity == null || identity.observers.Count == 0)
+ if (identity == null || identity.observers == null || identity.observers.Count == 0)
return;
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message only once
- NetworkMessages.Pack(message, writer);
+ MessagePacking.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
int count = 0;
- foreach (NetworkConnectionToClient conn in identity.observers.Values)
+ foreach (NetworkConnection conn in identity.observers.Values)
{
bool isOwner = conn == identity.connectionToClient;
if ((!isOwner || includeOwner) && conn.isReady)
@@ -429,7 +384,7 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message,
}
/// Send a message to only clients which are ready including the owner of the NetworkIdentity
- // TODO obsolete this later. it's not used anymore
+ // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
public static void SendToReadyObservers(NetworkIdentity identity, T message, int channelId)
where T : struct, NetworkMessage
{
@@ -448,14 +403,14 @@ static void OnTransportConnected(int connectionId)
if (connectionId == 0)
{
Debug.LogError($"Server.HandleConnect: invalid connectionId: {connectionId} . Needs to be != 0, because 0 is reserved for local player.");
- Transport.active.ServerDisconnect(connectionId);
+ Transport.activeTransport.ServerDisconnect(connectionId);
return;
}
// connectionId not in use yet?
if (connections.ContainsKey(connectionId))
{
- Transport.active.ServerDisconnect(connectionId);
+ Transport.activeTransport.ServerDisconnect(connectionId);
// Debug.Log($"Server connectionId {connectionId} already in use...kicked client");
return;
}
@@ -474,7 +429,7 @@ static void OnTransportConnected(int connectionId)
else
{
// kick
- Transport.active.ServerDisconnect(connectionId);
+ Transport.activeTransport.ServerDisconnect(connectionId);
// Debug.Log($"Server full, kicked client {connectionId}");
}
}
@@ -490,7 +445,7 @@ internal static void OnConnected(NetworkConnectionToClient conn)
static bool UnpackAndInvoke(NetworkConnectionToClient connection, NetworkReader reader, int channelId)
{
- if (NetworkMessages.UnpackId(reader, out ushort msgType))
+ if (MessagePacking.Unpack(reader, out ushort msgType))
{
// try to invoke the handler for that message
if (handlers.TryGetValue(msgType, out NetworkMessageDelegate handler))
@@ -552,7 +507,7 @@ internal static void OnTransportData(int connectionId, ArraySegment data,
connection.unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimestamp))
{
// enough to read at least header size?
- if (reader.Remaining >= NetworkMessages.IdSize)
+ if (reader.Remaining >= MessagePacking.HeaderSize)
{
// make remoteTimeStamp available to the user
connection.remoteTimeStamp = remoteTimestamp;
@@ -653,12 +608,12 @@ static void OnTransportError(int connectionId, TransportError error, string reas
public static void RegisterHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = NetworkMessages.GetId();
+ ushort msgType = MessagePacking.GetId();
if (handlers.ContainsKey(msgType))
{
Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
}
- handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
+ handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
}
/// Register a handler for message type T. Most should require authentication.
@@ -666,20 +621,20 @@ public static void RegisterHandler(Action handl
public static void RegisterHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = NetworkMessages.GetId();
+ ushort msgType = MessagePacking.GetId();
if (handlers.ContainsKey(msgType))
{
Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
}
- handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
+ handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
}
/// Replace a handler for message type T. Most should require authentication.
public static void ReplaceHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = NetworkMessages.GetId();
- handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
+ ushort msgType = MessagePacking.GetId();
+ handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
}
/// Replace a handler for message type T. Most should require authentication.
@@ -693,7 +648,7 @@ public static void ReplaceHandler(Action handler, bool requireAuthenticati
public static void UnregisterHandler()
where T : struct, NetworkMessage
{
- ushort msgType = NetworkMessages.GetId();
+ ushort msgType = MessagePacking.GetId();
handlers.Remove(msgType);
}
@@ -784,7 +739,7 @@ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameOb
// special case, we are in host mode, set hasAuthority to true so that all overrides see it
if (conn is LocalConnectionToClient)
{
- identity.isOwned = true;
+ identity.hasAuthority = true;
NetworkClient.InternalAddPlayer(identity);
}
@@ -804,7 +759,7 @@ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameOb
// for that object. This function is used for "adding" a player, not for
// "replacing" the player on a connection. If there is already a player
// on this playerControllerId for this connection, this will fail.
- public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId)
+ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId)
{
if (GetNetworkIdentity(player, out NetworkIdentity identity))
{
@@ -844,7 +799,7 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga
// special case, we are in host mode, set hasAuthority to true so that all overrides see it
if (conn is LocalConnectionToClient)
{
- identity.isOwned = true;
+ identity.hasAuthority = true;
NetworkClient.InternalAddPlayer(identity);
}
@@ -877,7 +832,7 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga
/// Replaces connection's player object. The old object is not destroyed.
// This does NOT change the ready state of the connection, so it can
// safely be used while changing scenes.
- public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, bool keepAuthority = false)
+ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId, bool keepAuthority = false)
{
if (GetNetworkIdentity(player, out NetworkIdentity identity))
{
@@ -1007,70 +962,13 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg,
// Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}");
using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload))
- identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn);
- }
-
- // client to server broadcast //////////////////////////////////////////
- // for client's owned ClientToServer components.
- static void OnEntityStateMessage(NetworkConnectionToClient connection, EntityStateMessage message)
- {
- // need to validate permissions carefully.
- // an attacker may attempt to modify a not-owned or not-ClientToServer component.
-
- // valid netId?
- if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null)
- {
- // owned by the connection?
- if (identity.connectionToClient == connection)
- {
- using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload))
- {
- // DeserializeServer checks permissions internally.
- // failure to deserialize disconnects to prevent exploits.
- if (!identity.DeserializeServer(reader))
- {
- Debug.LogWarning($"Server failed to deserialize client state for {identity.name} with netId={identity.netId}, Disconnecting.");
- connection.Disconnect();
- }
- }
- }
- // an attacker may attempt to modify another connection's entity
- else
- {
- Debug.LogWarning($"Connection {connection.connectionId} attempted to modify {identity} which is not owned by the connection. Disconnecting the connection.");
- connection.Disconnect();
- }
- }
- // no warning. don't spam server logs.
- // else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
- }
-
- // client sends TimeSnapshotMessage every sendInterval.
- // batching already includes the remoteTimestamp.
- // we simply insert it on-message here.
- // => only for reliable channel. unreliable would always arrive earlier.
- static void OnTimeSnapshotMessage(NetworkConnectionToClient connection, TimeSnapshotMessage _)
- {
- // insert another snapshot for snapshot interpolation.
- // before calling OnDeserialize so components can use
- // NetworkTime.time and NetworkTime.timeStamp.
-
- // TODO validation?
- // maybe we shouldn't allow timeline to deviate more than a certain %.
- // for now, this is only used for client authority movement.
-
-#if !UNITY_2020_3_OR_NEWER
- // Unity 2019 doesn't have Time.timeAsDouble yet
- connection.OnTimeSnapshot(new TimeSnapshot(connection.remoteTimeStamp, NetworkTime.localTime));
-#else
- connection.OnTimeSnapshot(new TimeSnapshot(connection.remoteTimeStamp, Time.timeAsDouble));
-#endif
+ identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient);
}
// spawning ////////////////////////////////////////////////////////////
static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter)
{
- // Only call SerializeAll if there are NetworkBehaviours
+ // Only call OnSerializeAllSafely if there are NetworkBehaviours
if (identity.NetworkBehaviours.Length == 0)
{
return default;
@@ -1078,7 +976,7 @@ static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentit
// serialize all components with initialState = true
// (can be null if has none)
- identity.SerializeServer(true, ownerWriter, observersWriter);
+ identity.OnSerializeAllSafely(true, ownerWriter, observersWriter);
// convert to ArraySegment to avoid reader allocations
// if nothing was written, .ToArraySegment returns an empty segment.
@@ -1168,37 +1066,73 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
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;
+ identity.hasAuthority = true;
+
+ 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);
+ }
+
+ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
+ {
+ 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;
+ }
- // only call OnStartServer if not spawned yet.
- // check used to be in NetworkIdentity. may not be necessary anymore.
- if (!identity.isServer && identity.netId == 0)
+ if (!active)
{
- // configure NetworkIdentity
- identity.isLocalPlayer = NetworkClient.localPlayer == identity;
- identity.isClient = NetworkClient.active;
- identity.isServer = true;
- identity.netId = NetworkIdentity.GetNextNetworkId();
+ Debug.LogError($"SpawnObject for {obj}, NetworkServer is not active. Cannot spawn objects without an active server.");
+ return;
+ }
- // add to spawned (after assigning netId)
- spawned[identity.netId] = identity;
+ if (identity == null)
+ {
+ Debug.LogError($"SpawnObject {obj} has no NetworkIdentity. Please add a NetworkIdentity to {obj}");
+ return;
+ }
- // callback after all fields were set
- identity.OnStartServer();
+ 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;
}
+ identity.connectionToClient = (NetworkConnectionToClient)ownerConnection;
+
+ // special case to make sure hasAuthority is set
+ // on start server in host mode
+ if (ownerConnection is LocalConnectionToClient)
+ identity.hasAuthority = true;
+
+ identity.OnStartServer();
+
// Debug.Log($"SpawnObject instance ID {identity.netId} asset ID {identity.assetId}");
if (aoi)
@@ -1226,6 +1160,14 @@ public static void Spawn(GameObject obj, NetworkConnection ownerConnection = nul
SpawnObject(obj, ownerConnection);
}
+ /// Spawn the given game object on all clients which are ready.
+ // 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);
+ }
+
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, GameObject ownerPlayer)
@@ -1248,7 +1190,46 @@ public static void Spawn(GameObject obj, GameObject ownerPlayer)
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
- public static void Spawn(GameObject obj, uint assetId, NetworkConnection ownerConnection = null)
+ public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
+ {
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+ 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);
+ }
+
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // 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);
+ }
+
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
+ public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
{
if (GetNetworkIdentity(obj, out NetworkIdentity identity))
{
@@ -1257,6 +1238,29 @@ public static void Spawn(GameObject obj, uint assetId, NetworkConnection ownerCo
SpawnObject(obj, ownerConnection);
}
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
+ public static void Spawn(NetworkIdentity identity, Guid assetId, NetworkConnection ownerConnection = null)
+ {
+ identity.assetId = assetId;
+ SpawnObject(identity, ownerConnection);
+ }
+
+ internal static bool ValidateSceneObject(NetworkIdentity identity)
+ {
+ if (identity.gameObject.hideFlags == HideFlags.NotEditable ||
+ identity.gameObject.hideFlags == HideFlags.HideAndDontSave)
+ return false;
+
+#if UNITY_EDITOR
+ if (UnityEditor.EditorUtility.IsPersistent(identity.gameObject))
+ return false;
+#endif
+
+ // If not a scene object
+ return identity.sceneId != 0;
+ }
+
/// Spawns NetworkIdentities in the scene on the server.
// NetworkIdentity objects in a scene are disabled by default. Calling
// SpawnObjects() causes these scene objects to be enabled and spawned.
@@ -1272,7 +1276,7 @@ public static bool SpawnObjects()
// first pass: activate all scene objects
foreach (NetworkIdentity identity in identities)
{
- if (Utils.IsSceneObject(identity))
+ if (ValidateSceneObject(identity))
{
// Debug.Log($"SpawnObjects sceneId:{identity.sceneId:X} name:{identity.gameObject.name}");
identity.gameObject.SetActive(true);
@@ -1292,7 +1296,7 @@ public static bool SpawnObjects()
// second pass: spawn all scene objects
foreach (NetworkIdentity identity in identities)
{
- if (Utils.IsSceneObject(identity))
+ if (ValidateSceneObject(identity))
// pass connection so that authority is not lost when server loads a scene
// https://github.com/vis2k/Mirror/pull/2987
Spawn(identity.gameObject, identity.connectionToClient);
@@ -1441,10 +1445,7 @@ static void DestroyObject(NetworkIdentity identity, DestroyMode mode)
identity.connectionToClient?.RemoveOwnedObject(identity);
// send object destroy message to all observers, clear observers
- SendToObservers(identity, new ObjectDestroyMessage
- {
- netId = identity.netId
- });
+ SendToObservers(identity, new ObjectDestroyMessage{netId = identity.netId});
identity.ClearObservers();
// in host mode, call OnStopClient/OnStopLocalPlayer manually
@@ -1457,11 +1458,10 @@ static void DestroyObject(NetworkIdentity identity, DestroyMode mode)
// The object may have been spawned with host client ownership,
// e.g. a pet so we need to clear hasAuthority and call
// NotifyAuthority which invokes OnStopAuthority if hasAuthority.
- identity.isOwned = false;
+ identity.hasAuthority = false;
identity.NotifyAuthority();
// remove from NetworkClient dictionary
- NetworkClient.connection.owned.Remove(identity);
NetworkClient.spawned.Remove(identity.netId);
}
@@ -1666,6 +1666,10 @@ static void RebuildObserversCustom(NetworkIdentity identity, bool initialize)
// both worlds without any worrying now!
public static void RebuildObservers(NetworkIdentity identity, bool initialize)
{
+ // observers are null until OnStartServer creates them
+ if (identity.observers == null)
+ return;
+
// if there is no interest management system,
// or if 'force shown' then add all connections
if (aoi == null || identity.visible == Visibility.ForceShown)
@@ -1681,11 +1685,11 @@ public static void RebuildObservers(NetworkIdentity identity, bool initialize)
// broadcasting ////////////////////////////////////////////////////////
// helper function to get the right serialization for a connection
- static NetworkWriter SerializeForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
+ static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
{
// get serialization for this entity (cached)
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
- NetworkIdentitySerialization serialization = identity.GetServerSerializationAtTick(Time.frameCount);
+ NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(Time.frameCount);
// is this entity owned by this connection?
bool owned = identity.connectionToClient == connection;
@@ -1724,7 +1728,7 @@ static void BroadcastToConnection(NetworkConnectionToClient connection)
{
// get serialization for this entity viewed by this connection
// (if anything was serialized this time)
- NetworkWriter serialization = SerializeForConnection(identity, connection);
+ NetworkWriter serialization = GetEntitySerializationForConnection(identity, connection);
if (serialization != null)
{
EntityStateMessage message = new EntityStateMessage
@@ -1770,17 +1774,6 @@ static void Broadcast()
// pull in UpdateVarsMessage for each entity it observes
if (connection.isReady)
{
- // send time for snapshot interpolation every sendInterval.
- // BroadcastToConnection() may not send if nothing is new.
- //
- // sent over unreliable.
- // NetworkTime / Transform both use unreliable.
- //
- // make sure Broadcast() is only called every sendInterval,
- // even if targetFrameRate isn't set in host mode (!)
- // (done via AccurateInterval)
- connection.Send(new TimeSnapshotMessage(), Channels.Unreliable);
-
// broadcast world state to this connection
BroadcastToConnection(connection);
}
@@ -1819,83 +1812,21 @@ static void Broadcast()
// (we add this to the UnityEngine in NetworkLoop)
internal static void NetworkEarlyUpdate()
{
- // measure update time for profiling.
- if (active)
- {
- earlyUpdateDuration.Begin();
- fullUpdateDuration.Begin();
- }
-
// process all incoming messages first before updating the world
- if (Transport.active != null)
- Transport.active.ServerEarlyUpdate();
-
- // step each connection's local time interpolation in early update.
- foreach (NetworkConnectionToClient connection in connections.Values)
- connection.UpdateTimeInterpolation();
-
- if (active) earlyUpdateDuration.End();
+ if (Transport.activeTransport != null)
+ Transport.activeTransport.ServerEarlyUpdate();
}
internal static void NetworkLateUpdate()
{
+ // only broadcast world if active
if (active)
- {
- // measure update time for profiling.
- lateUpdateDuration.Begin();
-
- // only broadcast world if active
- // broadcast every sendInterval.
- // AccurateInterval to avoid update frequency inaccuracy issues:
- // https://github.com/vis2k/Mirror/pull/3153
- //
- // for example, host mode server doesn't set .targetFrameRate.
- // Broadcast() would be called every tick.
- // snapshots might be sent way too often, etc.
- //
- // during tests, we always call Broadcast() though.
- //
- // also important for syncInterval=0 components like
- // NetworkTransform, so they can sync on same interval as time
- // snapshots _but_ not every single tick.
- if (!Application.isPlaying ||
-#if !UNITY_2020_3_OR_NEWER
- // Unity 2019 doesn't have Time.timeAsDouble yet
- AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime))
-#else
- AccurateInterval.Elapsed(Time.timeAsDouble, sendInterval, ref lastSendTime))
-#endif
- {
- Broadcast();
- }
- }
+ Broadcast();
// process all outgoing messages after updating the world
// (even if not active. still want to process disconnects etc.)
- if (Transport.active != null)
- Transport.active.ServerLateUpdate();
-
- // measure actual tick rate every second.
- if (active)
- {
- ++actualTickRateCounter;
-
- // NetworkTime.localTime has defines for 2019 / 2020 compatibility
- if (NetworkTime.localTime >= actualTickRateStart + 1)
- {
- // calculate avg by exact elapsed time.
- // assuming 1s wouldn't be accurate, usually a few more ms passed.
- float elapsed = (float)(NetworkTime.localTime - actualTickRateStart);
- actualTickRate = Mathf.RoundToInt(actualTickRateCounter / elapsed);
- actualTickRateStart = NetworkTime.localTime;
- actualTickRateCounter = 0;
- }
-
- // measure total update time. including transport.
- // because in early update, transport update calls handlers.
- lateUpdateDuration.End();
- fullUpdateDuration.End();
- }
+ if (Transport.activeTransport != null)
+ Transport.activeTransport.ServerLateUpdate();
}
}
}
From 4b3dd236594db71b696c45395c191dfd41834948 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Mon, 8 Aug 2022 19:03:40 +0200
Subject: [PATCH 5/8] Added New Tests & Fixed a Test
Added possibly obsolete tests for Spawn using Network Identity
Fixed Shutdown_DisabledAllSpawnedPrefabs() using wrong identity
---
.../Tests/Editor/NetworkIdentityTests.cs | 45 +++++++++++++++++++
.../Tests/Runtime/NetworkServerRuntimeTest.cs | 14 +++++-
2 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
index f1b0b9d49b..400cd5eb26 100644
--- a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
+++ b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs
@@ -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()
@@ -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()
{
diff --git a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
index 03559af624..c5f3140004 100644
--- a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
+++ b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs
@@ -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();
@@ -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);
}
@@ -84,6 +88,14 @@ NetworkIdentity SpawnPrefab(GameObject prefab)
return identity1;
}
+ NetworkIdentity SpawnNetworkPrefab(GameObject prefab)
+ {
+ NetworkIdentity clone1 = GameObject.Instantiate(prefab).GetComponent();
+ NetworkServer.Spawn(clone1);
+ Assert.IsTrue(NetworkServer.spawned.ContainsValue(clone1));
+ return clone1;
+ }
+
[Test]
public void Shutdown_DisablesAllSpawnedPrefabs()
{
@@ -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);
}
From a4cbdb6016b25b9e0278d0ecc0838b150b37b148 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Thu, 11 Aug 2022 20:41:25 +0200
Subject: [PATCH 6/8] Adjusted Spawn methods to use NetworkIdentity
---
Assets/Mirror/Core/NetworkServer.cs | 58 +++++++++++++++++++++--------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs
index 4be0e315e0..935ffd5bdb 100644
--- a/Assets/Mirror/Core/NetworkServer.cs
+++ b/Assets/Mirror/Core/NetworkServer.cs
@@ -1096,6 +1096,13 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
{
+ // ensure spawn object has a network identity
+ 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
@@ -1111,12 +1118,6 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
return;
}
- if (identity == null)
- {
- Debug.LogError($"SpawnObject {obj} has no NetworkIdentity. Please add a NetworkIdentity to {obj}");
- return;
- }
-
if (identity.SpawnedFromInstantiate)
{
// Using Instantiate on SceneObject is not allowed, so stop spawning here
@@ -1157,7 +1158,15 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
// prefab, or from a custom spawn function.
public static void Spawn(GameObject obj, NetworkConnection ownerConnection = null)
{
- SpawnObject(obj, ownerConnection);
+ NetworkIdentity identity = obj.GetComponent();
+
+ if (identity == null)
+ {
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
+ return;
+ }
+
+ SpawnObject(identity, ownerConnection);
}
/// Spawn the given game object on all clients which are ready.
@@ -1172,20 +1181,28 @@ public static void Spawn(NetworkIdentity identity, NetworkConnection ownerConnec
// 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 identity = obj.GetComponent();
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
if (identity == null)
{
- Debug.LogError("Player object has no NetworkIdentity");
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
return;
}
- if (identity.connectionToClient == null)
+ if (ownerIdentity == null)
+ {
+ Debug.LogError("Player object is not a player.");
+ return;
+ }
+
+ if (ownerIdentity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}
- Spawn(obj, identity.connectionToClient);
+ Spawn(identity, ownerIdentity.connectionToClient);
}
/// Spawns an object and also assigns Client Authority to the specified client.
@@ -1193,6 +1210,7 @@ public static void Spawn(GameObject obj, GameObject ownerPlayer)
public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
{
NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
if (ownerIdentity == null)
{
Debug.LogError("Player object has no NetworkIdentity");
@@ -1231,18 +1249,28 @@ public static void Spawn(NetworkIdentity spawnIdentity, NetworkIdentity ownerIde
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
{
- if (GetNetworkIdentity(obj, out NetworkIdentity identity))
+ NetworkIdentity identity = obj.GetComponent();
+
+ if (identity == null)
{
- identity.assetId = assetId;
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
+ return;
}
- SpawnObject(obj, ownerConnection);
+
+ identity.assetId = assetId;
+
+ SpawnObject(identity, ownerConnection);
}
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(NetworkIdentity identity, Guid assetId, NetworkConnection ownerConnection = null)
{
- identity.assetId = assetId;
+ if(identity != null)
+ {
+ identity.assetId = assetId;
+ }
+
SpawnObject(identity, ownerConnection);
}
From cb763408ee0ece877b07bf5cdf913ec6603618d1 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Sat, 10 Dec 2022 21:01:18 +0100
Subject: [PATCH 7/8] Reset NetworkServer.cs
---
Assets/Mirror/Core/NetworkServer.cs | 477 +++++++++++++++-------------
1 file changed, 259 insertions(+), 218 deletions(-)
diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs
index 935ffd5bdb..4021f26efe 100644
--- a/Assets/Mirror/Core/NetworkServer.cs
+++ b/Assets/Mirror/Core/NetworkServer.cs
@@ -7,11 +7,31 @@
namespace Mirror
{
/// NetworkServer handles remote connections and has a local connection for a local client.
- public static class NetworkServer
+ public static partial class NetworkServer
{
static bool initialized;
public static int maxConnections;
+ /// 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.
+ // overwritten by NetworkManager (if any)
+ public static int tickRate = 30;
+
+ // tick rate is in Hz.
+ // convert to interval in seconds for convenience where needed.
+ //
+ // send interval is 1 / sendRate.
+ // but for tests we need a way to set it to exactly 0.
+ // 1 / int.max would not be exactly 0, so handel that manually.
+ public static float tickInterval => tickRate < int.MaxValue ? 1f / tickRate : 0; // for 30 Hz, that's 33ms
+
+ // time & value snapshot interpolation are separate.
+ // -> 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 float sendInterval => sendRate < int.MaxValue ? 1f / sendRate : 0; // for 30 Hz, that's 33ms
+ static double lastSendTime;
+
/// Connection to host mode client (if any)
public static NetworkConnectionToClient localConnection { get; private set; }
@@ -54,6 +74,23 @@ public static class NetworkServer
public static Action OnDisconnectedEvent;
public static Action 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
+
+ // profiling
+ // includes transport update time, because transport calls handlers etc.
+ // averaged over 1s by passing 'tickRate' to constructor.
+ public static TimeSample earlyUpdateDuration;
+ public static TimeSample lateUpdateDuration;
+
+ // capture full Unity update time from before Early- to after LateUpdate
+ public static TimeSample fullUpdateDuration;
+
// initialization / shutdown ///////////////////////////////////////////
static void Initialize()
{
@@ -72,28 +109,33 @@ static void Initialize()
// reset NetworkTime
NetworkTime.ResetStatics();
- Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.activeTransport' first");
+ Debug.Assert(Transport.active != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.active' first");
AddTransportHandlers();
initialized = true;
+
+ // profiling
+ earlyUpdateDuration = 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.activeTransport.OnServerConnected += OnTransportConnected;
- Transport.activeTransport.OnServerDataReceived += OnTransportData;
- Transport.activeTransport.OnServerDisconnected += OnTransportDisconnected;
- Transport.activeTransport.OnServerError += OnTransportError;
+ Transport.active.OnServerConnected += OnTransportConnected;
+ Transport.active.OnServerDataReceived += OnTransportData;
+ Transport.active.OnServerDisconnected += OnTransportDisconnected;
+ Transport.active.OnServerError += OnTransportError;
}
static void RemoveTransportHandlers()
{
// -= so that other systems can also hook into it (i.e. statistics)
- Transport.activeTransport.OnServerConnected -= OnTransportConnected;
- Transport.activeTransport.OnServerDataReceived -= OnTransportData;
- Transport.activeTransport.OnServerDisconnected -= OnTransportDisconnected;
- Transport.activeTransport.OnServerError -= OnTransportError;
+ Transport.active.OnServerConnected -= OnTransportConnected;
+ Transport.active.OnServerDataReceived -= OnTransportData;
+ Transport.active.OnServerDisconnected -= OnTransportDisconnected;
+ Transport.active.OnServerError -= OnTransportError;
}
// calls OnStartClient for all SERVER objects in host mode once.
@@ -105,7 +147,7 @@ public static void ActivateHostScene()
if (!identity.isClient)
{
// Debug.Log($"ActivateHostScene {identity.netId} {identity}");
- identity.OnStartClient();
+ NetworkClient.CheckForStartClient(identity);
}
}
}
@@ -115,6 +157,8 @@ internal static void RegisterMessageHandlers()
RegisterHandler(OnClientReadyMessage);
RegisterHandler(OnCommandMessage);
RegisterHandler(NetworkTime.OnServerPing, false);
+ RegisterHandler(OnEntityStateMessage, true);
+ RegisterHandler(OnTimeSnapshotMessage, true);
}
/// Starts server and listens to incoming connections with max connections limit.
@@ -126,7 +170,7 @@ public static void Listen(int maxConns)
// only start server if we want to listen
if (!dontListen)
{
- Transport.activeTransport.ServerStart();
+ Transport.active.ServerStart();
//Debug.Log("Server started listening");
}
@@ -183,7 +227,7 @@ public static void Shutdown()
// someone might enabled dontListen at runtime.
// but we still need to stop the server.
// fixes https://github.com/vis2k/Mirror/issues/2536
- Transport.activeTransport.ServerStop();
+ Transport.active.ServerStop();
// transport handlers are hooked into when initializing.
// so only remove them when shutting down.
@@ -196,6 +240,7 @@ public static void Shutdown()
dontListen = false;
active = false;
isLoadingScene = false;
+ lastSendTime = 0;
localConnection = null;
@@ -295,7 +340,7 @@ public static void SendToAll(T message, int channelId = Channels.Reliable, bo
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message only once
- MessagePacking.Pack(message, writer);
+ NetworkMessages.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
// filter and then send to all internet connections at once
@@ -335,13 +380,13 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI
where T : struct, NetworkMessage
{
// Debug.Log($"Server.SendToObservers {typeof(T)}");
- if (identity == null || identity.observers == null || identity.observers.Count == 0)
+ if (identity == null || identity.observers.Count == 0)
return;
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message into byte[] once
- MessagePacking.Pack(message, writer);
+ NetworkMessages.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
foreach (NetworkConnectionToClient conn in identity.observers.Values)
@@ -354,22 +399,22 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI
}
/// Send a message to only clients which are ready with option to include the owner of the object identity
- // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
+ // TODO obsolete this later. it's not used anymore
public static void SendToReadyObservers(NetworkIdentity identity, T message, bool includeOwner = true, int channelId = Channels.Reliable)
where T : struct, NetworkMessage
{
// Debug.Log($"Server.SendToReady {typeof(T)}");
- if (identity == null || identity.observers == null || identity.observers.Count == 0)
+ if (identity == null || identity.observers.Count == 0)
return;
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// pack message only once
- MessagePacking.Pack(message, writer);
+ NetworkMessages.Pack(message, writer);
ArraySegment segment = writer.ToArraySegment();
int count = 0;
- foreach (NetworkConnection conn in identity.observers.Values)
+ foreach (NetworkConnectionToClient conn in identity.observers.Values)
{
bool isOwner = conn == identity.connectionToClient;
if ((!isOwner || includeOwner) && conn.isReady)
@@ -384,7 +429,7 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message,
}
/// Send a message to only clients which are ready including the owner of the NetworkIdentity
- // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
+ // TODO obsolete this later. it's not used anymore
public static void SendToReadyObservers(NetworkIdentity identity, T message, int channelId)
where T : struct, NetworkMessage
{
@@ -403,14 +448,14 @@ static void OnTransportConnected(int connectionId)
if (connectionId == 0)
{
Debug.LogError($"Server.HandleConnect: invalid connectionId: {connectionId} . Needs to be != 0, because 0 is reserved for local player.");
- Transport.activeTransport.ServerDisconnect(connectionId);
+ Transport.active.ServerDisconnect(connectionId);
return;
}
// connectionId not in use yet?
if (connections.ContainsKey(connectionId))
{
- Transport.activeTransport.ServerDisconnect(connectionId);
+ Transport.active.ServerDisconnect(connectionId);
// Debug.Log($"Server connectionId {connectionId} already in use...kicked client");
return;
}
@@ -429,7 +474,7 @@ static void OnTransportConnected(int connectionId)
else
{
// kick
- Transport.activeTransport.ServerDisconnect(connectionId);
+ Transport.active.ServerDisconnect(connectionId);
// Debug.Log($"Server full, kicked client {connectionId}");
}
}
@@ -445,7 +490,7 @@ internal static void OnConnected(NetworkConnectionToClient conn)
static bool UnpackAndInvoke(NetworkConnectionToClient connection, NetworkReader reader, int channelId)
{
- if (MessagePacking.Unpack(reader, out ushort msgType))
+ if (NetworkMessages.UnpackId(reader, out ushort msgType))
{
// try to invoke the handler for that message
if (handlers.TryGetValue(msgType, out NetworkMessageDelegate handler))
@@ -507,7 +552,7 @@ internal static void OnTransportData(int connectionId, ArraySegment data,
connection.unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimestamp))
{
// enough to read at least header size?
- if (reader.Remaining >= MessagePacking.HeaderSize)
+ if (reader.Remaining >= NetworkMessages.IdSize)
{
// make remoteTimeStamp available to the user
connection.remoteTimeStamp = remoteTimestamp;
@@ -608,12 +653,12 @@ static void OnTransportError(int connectionId, TransportError error, string reas
public static void RegisterHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = MessagePacking.GetId();
+ ushort msgType = NetworkMessages.GetId();
if (handlers.ContainsKey(msgType))
{
Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
}
- handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
+ handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
}
/// Register a handler for message type T. Most should require authentication.
@@ -621,20 +666,20 @@ public static void RegisterHandler(Action handl
public static void RegisterHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = MessagePacking.GetId();
+ ushort msgType = NetworkMessages.GetId();
if (handlers.ContainsKey(msgType))
{
Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
}
- handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
+ handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
}
/// Replace a handler for message type T. Most should require authentication.
public static void ReplaceHandler(Action handler, bool requireAuthentication = true)
where T : struct, NetworkMessage
{
- ushort msgType = MessagePacking.GetId();
- handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
+ ushort msgType = NetworkMessages.GetId();
+ handlers[msgType] = NetworkMessages.WrapHandler(handler, requireAuthentication);
}
/// Replace a handler for message type T. Most should require authentication.
@@ -648,7 +693,7 @@ public static void ReplaceHandler(Action handler, bool requireAuthenticati
public static void UnregisterHandler()
where T : struct, NetworkMessage
{
- ushort msgType = MessagePacking.GetId();
+ ushort msgType = NetworkMessages.GetId();
handlers.Remove(msgType);
}
@@ -739,7 +784,7 @@ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameOb
// special case, we are in host mode, set hasAuthority to true so that all overrides see it
if (conn is LocalConnectionToClient)
{
- identity.hasAuthority = true;
+ identity.isOwned = true;
NetworkClient.InternalAddPlayer(identity);
}
@@ -759,7 +804,7 @@ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameOb
// for that object. This function is used for "adding" a player, not for
// "replacing" the player on a connection. If there is already a player
// on this playerControllerId for this connection, this will fail.
- public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId)
+ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId)
{
if (GetNetworkIdentity(player, out NetworkIdentity identity))
{
@@ -799,7 +844,7 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga
// special case, we are in host mode, set hasAuthority to true so that all overrides see it
if (conn is LocalConnectionToClient)
{
- identity.hasAuthority = true;
+ identity.isOwned = true;
NetworkClient.InternalAddPlayer(identity);
}
@@ -832,7 +877,7 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga
/// Replaces connection's player object. The old object is not destroyed.
// This does NOT change the ready state of the connection, so it can
// safely be used while changing scenes.
- public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId, bool keepAuthority = false)
+ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, bool keepAuthority = false)
{
if (GetNetworkIdentity(player, out NetworkIdentity identity))
{
@@ -962,13 +1007,70 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg,
// Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}");
using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload))
- identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient);
+ identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn);
+ }
+
+ // client to server broadcast //////////////////////////////////////////
+ // for client's owned ClientToServer components.
+ static void OnEntityStateMessage(NetworkConnectionToClient connection, EntityStateMessage message)
+ {
+ // need to validate permissions carefully.
+ // an attacker may attempt to modify a not-owned or not-ClientToServer component.
+
+ // valid netId?
+ if (spawned.TryGetValue(message.netId, out NetworkIdentity identity) && identity != null)
+ {
+ // owned by the connection?
+ if (identity.connectionToClient == connection)
+ {
+ using (NetworkReaderPooled reader = NetworkReaderPool.Get(message.payload))
+ {
+ // DeserializeServer checks permissions internally.
+ // failure to deserialize disconnects to prevent exploits.
+ if (!identity.DeserializeServer(reader))
+ {
+ Debug.LogWarning($"Server failed to deserialize client state for {identity.name} with netId={identity.netId}, Disconnecting.");
+ connection.Disconnect();
+ }
+ }
+ }
+ // an attacker may attempt to modify another connection's entity
+ else
+ {
+ Debug.LogWarning($"Connection {connection.connectionId} attempted to modify {identity} which is not owned by the connection. Disconnecting the connection.");
+ connection.Disconnect();
+ }
+ }
+ // no warning. don't spam server logs.
+ // else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
+ }
+
+ // client sends TimeSnapshotMessage every sendInterval.
+ // batching already includes the remoteTimestamp.
+ // we simply insert it on-message here.
+ // => only for reliable channel. unreliable would always arrive earlier.
+ static void OnTimeSnapshotMessage(NetworkConnectionToClient connection, TimeSnapshotMessage _)
+ {
+ // insert another snapshot for snapshot interpolation.
+ // before calling OnDeserialize so components can use
+ // NetworkTime.time and NetworkTime.timeStamp.
+
+ // TODO validation?
+ // maybe we shouldn't allow timeline to deviate more than a certain %.
+ // for now, this is only used for client authority movement.
+
+#if !UNITY_2020_3_OR_NEWER
+ // Unity 2019 doesn't have Time.timeAsDouble yet
+ connection.OnTimeSnapshot(new TimeSnapshot(connection.remoteTimeStamp, NetworkTime.localTime));
+#else
+ connection.OnTimeSnapshot(new TimeSnapshot(connection.remoteTimeStamp, Time.timeAsDouble));
+#endif
}
// spawning ////////////////////////////////////////////////////////////
static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter)
{
- // Only call OnSerializeAllSafely if there are NetworkBehaviours
+ // Only call SerializeAll if there are NetworkBehaviours
if (identity.NetworkBehaviours.Length == 0)
{
return default;
@@ -976,7 +1078,7 @@ static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentit
// serialize all components with initialState = true
// (can be null if has none)
- identity.OnSerializeAllSafely(true, ownerWriter, observersWriter);
+ identity.SerializeServer(true, ownerWriter, observersWriter);
// convert to ArraySegment to avoid reader allocations
// if nothing was written, .ToArraySegment returns an empty segment.
@@ -1066,62 +1168,10 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
return;
}
- identity.connectionToClient = (NetworkConnectionToClient)ownerConnection;
-
- // special case to make sure hasAuthority is set
- // on start server in host mode
- if (ownerConnection is LocalConnectionToClient)
- identity.hasAuthority = true;
-
- identity.OnStartServer();
-
- // Debug.Log($"SpawnObject instance ID {identity.netId} asset ID {identity.assetId}");
-
- if (aoi)
+ // Spawn should only be called once per netId
+ if (spawned.ContainsKey(identity.netId))
{
- // 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);
- }
-
- static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnection)
- {
- // ensure spawn object has a network identity
- 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 (!active)
- {
- Debug.LogError($"SpawnObject for {obj}, NetworkServer is not active. Cannot spawn objects without an active server.");
- 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
+ Debug.LogWarning($"{identity} with netId={identity.netId} was already spawned.");
return;
}
@@ -1130,9 +1180,24 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
// special case to make sure hasAuthority is set
// on start server in host mode
if (ownerConnection is LocalConnectionToClient)
- identity.hasAuthority = true;
+ 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();
- identity.OnStartServer();
+ // 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}");
@@ -1158,135 +1223,38 @@ static void SpawnObject(NetworkIdentity identity, NetworkConnection ownerConnect
// prefab, or from a custom spawn function.
public static void Spawn(GameObject obj, NetworkConnection ownerConnection = null)
{
- NetworkIdentity identity = obj.GetComponent();
-
- if (identity == null)
- {
- Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
- return;
- }
-
- SpawnObject(identity, ownerConnection);
- }
-
- /// Spawn the given game object on all clients which are ready.
- // 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);
+ SpawnObject(obj, ownerConnection);
}
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
public static void Spawn(GameObject obj, GameObject ownerPlayer)
{
- NetworkIdentity identity = obj.GetComponent();
- NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
-
+ NetworkIdentity identity = ownerPlayer.GetComponent();
if (identity == null)
- {
- Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
- return;
- }
-
- if (ownerIdentity == null)
- {
- Debug.LogError("Player object is not a player.");
- return;
- }
-
- if (ownerIdentity.connectionToClient == null)
- {
- Debug.LogError("Player object is not a player.");
- return;
- }
-
- Spawn(identity, ownerIdentity.connectionToClient);
- }
-
- /// Spawns an object and also assigns Client Authority to the specified client.
- // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
- public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
- {
- NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
-
- 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);
- }
-
- /// Spawns an object and also assigns Client Authority to the specified client.
- // 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)
+ if (identity.connectionToClient == null)
{
Debug.LogError("Player object is not a player.");
return;
}
- Spawn(spawnIdentity, ownerIdentity.connectionToClient);
+ Spawn(obj, identity.connectionToClient);
}
/// Spawns an object and also assigns Client Authority to the specified client.
// This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
- public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
+ public static void Spawn(GameObject obj, uint assetId, NetworkConnection ownerConnection = null)
{
- NetworkIdentity identity = obj.GetComponent();
-
- if (identity == null)
- {
- Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
- return;
- }
-
- identity.assetId = assetId;
-
- SpawnObject(identity, ownerConnection);
- }
-
- /// Spawns an object and also assigns Client Authority to the specified client.
- // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
- public static void Spawn(NetworkIdentity identity, Guid assetId, NetworkConnection ownerConnection = null)
- {
- if(identity != null)
+ if (GetNetworkIdentity(obj, out NetworkIdentity identity))
{
identity.assetId = assetId;
}
-
- SpawnObject(identity, ownerConnection);
- }
-
- internal static bool ValidateSceneObject(NetworkIdentity identity)
- {
- if (identity.gameObject.hideFlags == HideFlags.NotEditable ||
- identity.gameObject.hideFlags == HideFlags.HideAndDontSave)
- return false;
-
-#if UNITY_EDITOR
- if (UnityEditor.EditorUtility.IsPersistent(identity.gameObject))
- return false;
-#endif
-
- // If not a scene object
- return identity.sceneId != 0;
+ SpawnObject(obj, ownerConnection);
}
/// Spawns NetworkIdentities in the scene on the server.
@@ -1304,7 +1272,7 @@ public static bool SpawnObjects()
// first pass: activate all scene objects
foreach (NetworkIdentity identity in identities)
{
- if (ValidateSceneObject(identity))
+ if (Utils.IsSceneObject(identity))
{
// Debug.Log($"SpawnObjects sceneId:{identity.sceneId:X} name:{identity.gameObject.name}");
identity.gameObject.SetActive(true);
@@ -1324,7 +1292,7 @@ public static bool SpawnObjects()
// second pass: spawn all scene objects
foreach (NetworkIdentity identity in identities)
{
- if (ValidateSceneObject(identity))
+ if (Utils.IsSceneObject(identity))
// pass connection so that authority is not lost when server loads a scene
// https://github.com/vis2k/Mirror/pull/2987
Spawn(identity.gameObject, identity.connectionToClient);
@@ -1473,7 +1441,10 @@ static void DestroyObject(NetworkIdentity identity, DestroyMode mode)
identity.connectionToClient?.RemoveOwnedObject(identity);
// send object destroy message to all observers, clear observers
- SendToObservers(identity, new ObjectDestroyMessage{netId = identity.netId});
+ SendToObservers(identity, new ObjectDestroyMessage
+ {
+ netId = identity.netId
+ });
identity.ClearObservers();
// in host mode, call OnStopClient/OnStopLocalPlayer manually
@@ -1486,10 +1457,11 @@ static void DestroyObject(NetworkIdentity identity, DestroyMode mode)
// The object may have been spawned with host client ownership,
// e.g. a pet so we need to clear hasAuthority and call
// NotifyAuthority which invokes OnStopAuthority if hasAuthority.
- identity.hasAuthority = false;
+ identity.isOwned = false;
identity.NotifyAuthority();
// remove from NetworkClient dictionary
+ NetworkClient.connection.owned.Remove(identity);
NetworkClient.spawned.Remove(identity.netId);
}
@@ -1694,10 +1666,6 @@ static void RebuildObserversCustom(NetworkIdentity identity, bool initialize)
// both worlds without any worrying now!
public static void RebuildObservers(NetworkIdentity identity, bool initialize)
{
- // observers are null until OnStartServer creates them
- if (identity.observers == null)
- return;
-
// if there is no interest management system,
// or if 'force shown' then add all connections
if (aoi == null || identity.visible == Visibility.ForceShown)
@@ -1713,11 +1681,11 @@ public static void RebuildObservers(NetworkIdentity identity, bool initialize)
// broadcasting ////////////////////////////////////////////////////////
// helper function to get the right serialization for a connection
- static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
+ static NetworkWriter SerializeForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
{
// get serialization for this entity (cached)
// IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
- NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(Time.frameCount);
+ NetworkIdentitySerialization serialization = identity.GetServerSerializationAtTick(Time.frameCount);
// is this entity owned by this connection?
bool owned = identity.connectionToClient == connection;
@@ -1756,7 +1724,7 @@ static void BroadcastToConnection(NetworkConnectionToClient connection)
{
// get serialization for this entity viewed by this connection
// (if anything was serialized this time)
- NetworkWriter serialization = GetEntitySerializationForConnection(identity, connection);
+ NetworkWriter serialization = SerializeForConnection(identity, connection);
if (serialization != null)
{
EntityStateMessage message = new EntityStateMessage
@@ -1802,6 +1770,17 @@ static void Broadcast()
// pull in UpdateVarsMessage for each entity it observes
if (connection.isReady)
{
+ // send time for snapshot interpolation every sendInterval.
+ // BroadcastToConnection() may not send if nothing is new.
+ //
+ // sent over unreliable.
+ // NetworkTime / Transform both use unreliable.
+ //
+ // make sure Broadcast() is only called every sendInterval,
+ // even if targetFrameRate isn't set in host mode (!)
+ // (done via AccurateInterval)
+ connection.Send(new TimeSnapshotMessage(), Channels.Unreliable);
+
// broadcast world state to this connection
BroadcastToConnection(connection);
}
@@ -1840,21 +1819,83 @@ static void Broadcast()
// (we add this to the UnityEngine in NetworkLoop)
internal static void NetworkEarlyUpdate()
{
+ // measure update time for profiling.
+ if (active)
+ {
+ earlyUpdateDuration.Begin();
+ fullUpdateDuration.Begin();
+ }
+
// process all incoming messages first before updating the world
- if (Transport.activeTransport != null)
- Transport.activeTransport.ServerEarlyUpdate();
+ if (Transport.active != null)
+ Transport.active.ServerEarlyUpdate();
+
+ // step each connection's local time interpolation in early update.
+ foreach (NetworkConnectionToClient connection in connections.Values)
+ connection.UpdateTimeInterpolation();
+
+ if (active) earlyUpdateDuration.End();
}
internal static void NetworkLateUpdate()
{
- // only broadcast world if active
if (active)
- Broadcast();
+ {
+ // measure update time for profiling.
+ lateUpdateDuration.Begin();
+
+ // only broadcast world if active
+ // broadcast every sendInterval.
+ // AccurateInterval to avoid update frequency inaccuracy issues:
+ // https://github.com/vis2k/Mirror/pull/3153
+ //
+ // for example, host mode server doesn't set .targetFrameRate.
+ // Broadcast() would be called every tick.
+ // snapshots might be sent way too often, etc.
+ //
+ // during tests, we always call Broadcast() though.
+ //
+ // also important for syncInterval=0 components like
+ // NetworkTransform, so they can sync on same interval as time
+ // snapshots _but_ not every single tick.
+ if (!Application.isPlaying ||
+#if !UNITY_2020_3_OR_NEWER
+ // Unity 2019 doesn't have Time.timeAsDouble yet
+ AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime))
+#else
+ AccurateInterval.Elapsed(Time.timeAsDouble, sendInterval, ref lastSendTime))
+#endif
+ {
+ Broadcast();
+ }
+ }
// process all outgoing messages after updating the world
// (even if not active. still want to process disconnects etc.)
- if (Transport.activeTransport != null)
- Transport.activeTransport.ServerLateUpdate();
+ if (Transport.active != null)
+ Transport.active.ServerLateUpdate();
+
+ // measure actual tick rate every second.
+ if (active)
+ {
+ ++actualTickRateCounter;
+
+ // NetworkTime.localTime has defines for 2019 / 2020 compatibility
+ if (NetworkTime.localTime >= actualTickRateStart + 1)
+ {
+ // calculate avg by exact elapsed time.
+ // assuming 1s wouldn't be accurate, usually a few more ms passed.
+ float elapsed = (float)(NetworkTime.localTime - actualTickRateStart);
+ actualTickRate = Mathf.RoundToInt(actualTickRateCounter / elapsed);
+ actualTickRateStart = NetworkTime.localTime;
+ actualTickRateCounter = 0;
+ }
+
+ // measure total update time. including transport.
+ // because in early update, transport update calls handlers.
+ lateUpdateDuration.End();
+ fullUpdateDuration.End();
+ }
}
}
}
From de81c5ef071a8c1da2e086c83c1002abe8615143 Mon Sep 17 00:00:00 2001
From: realQuartzi <34374881+realQuartzi@users.noreply.github.com>
Date: Sat, 10 Dec 2022 21:30:36 +0100
Subject: [PATCH 8/8] Re-added NetworkIdentity Spawn Options
---
Assets/Mirror/Core/NetworkServer.cs | 159 +++++++++++++++++++++++++++-
1 file changed, 155 insertions(+), 4 deletions(-)
diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs
index 4021f26efe..c2d5a58948 100644
--- a/Assets/Mirror/Core/NetworkServer.cs
+++ b/Assets/Mirror/Core/NetworkServer.cs
@@ -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);
+ }
+
/// Spawn the given game object on all clients which are ready.
// 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();
+
+ if(identity == null)
+ {
+ Debug.LogError($"GameObject {obj.name} doesn't have NetworkIdentity.");
+ return;
+ }
+
+ SpawnObject(identity, ownerConnection);
+ }
+
+ /// Spawn the given game object on all clients which are ready.
+ // 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);
}
/// Spawns an object and also assigns Client Authority to the specified client.
// 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 identity = obj.GetComponent();
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
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);
+ }
+
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
+ public static void Spawn(NetworkIdentity spawnIdentity, GameObject ownerPlayer)
+ {
+ NetworkIdentity ownerIdentity = ownerPlayer.GetComponent();
+
+ 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);
+ }
+
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // 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);
}
/// Spawns an object and also assigns Client Authority to the specified client.
@@ -1257,6 +1396,18 @@ public static void Spawn(GameObject obj, uint assetId, NetworkConnection ownerCo
SpawnObject(obj, ownerConnection);
}
+ /// Spawns an object and also assigns Client Authority to the specified client.
+ // 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);
+ }
+
/// Spawns NetworkIdentities in the scene on the server.
// NetworkIdentity objects in a scene are disabled by default. Calling
// SpawnObjects() causes these scene objects to be enabled and spawned.