Skip to content

Commit

Permalink
feat: NetworkIdentity lifecycle events (#88)
Browse files Browse the repository at this point in the history
NetworkIdentity now has events related to lifecycle.  This means you no longer override lifecycle events in NetworkBehaviors,  but you can call AddListener or hook to the events in the inspector.

BREAKING CHANGE: NetworkBehavior no longer has virtuals for lifecycle events
  • Loading branch information
uweeby committed Mar 22, 2020
1 parent dbb31d8 commit b97a930
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 181 deletions.
4 changes: 3 additions & 1 deletion Components/NetworkSceneChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ void Awake()
{
currentScene = gameObject.scene;
if (LogFilter.Debug) Debug.Log($"NetworkSceneChecker.Awake currentScene: {currentScene}");

netIdentity.OnStartServer.AddListener(OnStartServer);
}

public override void OnStartServer()
public void OnStartServer()
{
if (!sceneCheckerObjects.ContainsKey(currentScene))
sceneCheckerObjects.Add(currentScene, new HashSet<NetworkIdentity>());
Expand Down
40 changes: 0 additions & 40 deletions Runtime/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -796,46 +796,6 @@ internal void DeSerializeObjectsDelta(NetworkReader reader)
}
}

/// <summary>
/// This is invoked on clients when the server has caused this object to be destroyed.
/// <para>This can be used as a hook to invoke effects or do client specific cleanup.</para>
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
///<summary>Called on clients when the server destroys the GameObject.</summary>
public virtual void OnNetworkDestroy() { }

/// <summary>
/// This is invoked for NetworkBehaviour objects when they become active on the server.
/// <para>This could be triggered by NetworkServer.Listen() for objects in the scene, or by NetworkServer.Spawn() for objects that are dynamically created.</para>
/// <para>This will be called for objects on a "host" as well as for object on a dedicated server.</para>
/// </summary>
public virtual void OnStartServer() { }

/// <summary>
/// Called on every NetworkBehaviour when it is activated on a client.
/// <para>Objects on the host have this function called, as there is a local client on the host. The values of SyncVars on object are guaranteed to be initialized correctly with the latest state from the server when this function is called on the client.</para>
/// </summary>
public virtual void OnStartClient() { }

/// <summary>
/// Called when the local player object has been set up.
/// <para>This happens after OnStartClient(), as it is triggered by an ownership message from the server. This is an appropriate place to activate components or functionality that should only be active for the local player, such as cameras and input.</para>
/// </summary>
public virtual void OnStartLocalPlayer() { }

/// <summary>
/// This is invoked on behaviours that have authority, based on context and <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see>.
/// <para>This is called after <see cref="OnStartServer">OnStartServer</see> and before <see cref="OnStartClient">OnStartClient.</see></para>
/// <para>When <see cref="NetworkIdentity.AssignClientAuthority"/> is called on the server, this will be called on the client that owns the object. When an object is spawned with <see cref="NetworkServer.Spawn">NetworkServer.Spawn</see> with a NetworkConnection parameter included, this will be called on the client that owns the object.</para>
/// </summary>
public virtual void OnStartAuthority() { }

/// <summary>
/// This is invoked on behaviours when authority is removed.
/// <para>When NetworkIdentity.RemoveClientAuthority is called on the server, this will be called on the client that owns the object.</para>
/// </summary>
public virtual void OnStopAuthority() { }

/// <summary>
/// Callback used by the visibility system to (re)construct the set of observers that can see this object.
/// <para>Implementations of this callback should add network connections of players that can see this object to the observers set.</para>
Expand Down
10 changes: 5 additions & 5 deletions Runtime/NetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ void UnSpawn(NetworkIdentity identity)
{
Guid assetId = identity.assetId;

identity.OnNetworkDestroy();
identity.NetworkDestroy();
if (unspawnHandlers.TryGetValue(assetId, out UnSpawnDelegate handler) && handler != null)
{
handler(identity.gameObject);
Expand Down Expand Up @@ -892,7 +892,7 @@ void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage msg)
if (isSpawnFinished)
{
identity.NotifyAuthority();
identity.OnStartClient();
identity.StartClient();
CheckForLocalPlayer(identity);
}
}
Expand Down Expand Up @@ -992,7 +992,7 @@ internal void OnObjectSpawnFinished(ObjectSpawnFinishedMessage _)
foreach (NetworkIdentity identity in Spawned.Values.OrderBy(uv => uv.netId))
{
identity.NotifyAuthority();
identity.OnStartClient();
identity.StartClient();
CheckForLocalPlayer(identity);
}
isSpawnFinished = true;
Expand Down Expand Up @@ -1049,7 +1049,7 @@ internal void OnHostClientSpawn(SpawnMessage msg)

localObject.hasAuthority = msg.isOwner;
localObject.NotifyAuthority();
localObject.OnStartClient();
localObject.StartClient();
localObject.OnSetHostVisibility(true);
CheckForLocalPlayer(localObject);
}
Expand Down Expand Up @@ -1102,7 +1102,7 @@ void CheckForLocalPlayer(NetworkIdentity identity)
{
// Set isLocalPlayer to true on this NetworkIdentity and trigger OnStartLocalPlayer in all scripts on the same GO
identity.connectionToServer = connection;
identity.OnStartLocalPlayer();
identity.StartLocalPlayer();

if (LogFilter.Debug) Debug.Log("ClientScene.OnOwnerMessage - player=" + identity.name);
}
Expand Down
160 changes: 54 additions & 106 deletions Runtime/NetworkIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Security.Cryptography;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
#if UNITY_2018_3_OR_NEWER
Expand Down Expand Up @@ -186,6 +187,45 @@ internal set
// keep track of all sceneIds to detect scene duplicates
static readonly Dictionary<ulong, NetworkIdentity> sceneIds = new Dictionary<ulong, NetworkIdentity>();

/// <summary>
/// This is invoked for NetworkBehaviour objects when they become active on the server.
/// <para>This could be triggered by NetworkServer.Listen() for objects in the scene, or by NetworkServer.Spawn() for objects that are dynamically created.</para>
/// <para>This will be called for objects on a "host" as well as for object on a dedicated server.</para>
/// </summary>
public UnityEvent OnStartServer = new UnityEvent();

/// <summary>
/// Called on every NetworkBehaviour when it is activated on a client.
/// <para>Objects on the host have this function called, as there is a local client on the host. The values of SyncVars on object are guaranteed to be initialized correctly with the latest state from the server when this function is called on the client.</para>
/// </summary>
public UnityEvent OnStartClient = new UnityEvent();

/// <summary>
/// Called when the local player object has been set up.
/// <para>This happens after OnStartClient(), as it is triggered by an ownership message from the server. This is an appropriate place to activate components or functionality that should only be active for the local player, such as cameras and input.</para>
/// </summary>
public UnityEvent OnStartLocalPlayer = new UnityEvent();

/// <summary>
/// This is invoked on behaviours that have authority, based on context and <see cref="NetworkIdentity.hasAuthority">NetworkIdentity.hasAuthority</see>.
/// <para>This is called after <see cref="OnStartServer">OnStartServer</see> and before <see cref="OnStartClient">OnStartClient.</see></para>
/// <para>When <see cref="NetworkIdentity.AssignClientAuthority"/> is called on the server, this will be called on the client that owns the object. When an object is spawned with <see cref="NetworkServer.Spawn">NetworkServer.Spawn</see> with a NetworkConnection parameter included, this will be called on the client that owns the object.</para>
/// </summary>
public UnityEvent OnStartAuthority = new UnityEvent();

/// <summary>
/// This is invoked on behaviours when authority is removed.
/// <para>When NetworkIdentity.RemoveClientAuthority is called on the server, this will be called on the client that owns the object.</para>
/// </summary>
public UnityEvent OnStopAuthority = new UnityEvent();

/// <summary>
/// This is invoked on clients when the server has caused this object to be destroyed.
/// <para>This can be used as a hook to invoke effects or do client specific cleanup.</para>
/// </summary>
///<summary>Called on clients when the server destroys the GameObject.</summary>
public UnityEvent OnNetworkDestroy = new UnityEvent();

/// <summary>
/// Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id
/// </summary>
Expand Down Expand Up @@ -501,7 +541,7 @@ void OnDestroy()
}
}

internal void OnStartServer()
internal void StartServer()
{
// If the instance/net ID is invalid here then this is an object instantiated from a prefab and the server should assign a valid ID
if (netId != 0)
Expand All @@ -521,124 +561,47 @@ internal void OnStartServer()
// because we already set m_isServer=true above)
server.spawned[netId] = this;

foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnStartServer should be caught, so that one
// component's exception doesn't stop all other components from
// being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
comp.OnStartServer();
}
catch (Exception e)
{
Debug.LogError("Exception in OnStartServer:" + e.Message + " " + e.StackTrace);
}
}
OnStartServer.Invoke();
}

bool clientStarted;
internal void OnStartClient()
internal void StartClient()
{
if (clientStarted)
return;
clientStarted = true;

if (LogFilter.Debug) Debug.Log("OnStartClient " + gameObject + " netId:" + netId);
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnStartClient should be caught, so that one
// component's exception doesn't stop all other components from
// being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
// user implemented startup
comp.OnStartClient();
}
catch (Exception e)
{
Debug.LogError("Exception in OnStartClient:" + e.Message + " " + e.StackTrace);
}
}
OnStartClient.Invoke();
}

static NetworkIdentity previousLocalPlayer = null;
internal void OnStartLocalPlayer()
internal void StartLocalPlayer()
{
if (previousLocalPlayer == this)
return;
previousLocalPlayer = this;

foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnStartLocalPlayer should be caught, so that
// one component's exception doesn't stop all other components
// from being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
comp.OnStartLocalPlayer();
}
catch (Exception e)
{
Debug.LogError("Exception in OnStartLocalPlayer:" + e.Message + " " + e.StackTrace);
}
}
OnStartLocalPlayer.Invoke();
}

bool hadAuthority;
internal void NotifyAuthority()
{
if (!hadAuthority && hasAuthority)
OnStartAuthority();
StartAuthority();
if (hadAuthority && !hasAuthority)
OnStopAuthority();
StopAuthority();
hadAuthority = hasAuthority;
}

internal void OnStartAuthority()
internal void StartAuthority()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnStartAuthority should be caught, so that one
// component's exception doesn't stop all other components from
// being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
comp.OnStartAuthority();
}
catch (Exception e)
{
Debug.LogError("Exception in OnStartAuthority:" + e.Message + " " + e.StackTrace);
}
}
OnStartAuthority.Invoke();
}

internal void OnStopAuthority()
internal void StopAuthority()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnStopAuthority should be caught, so that one
// component's exception doesn't stop all other components from
// being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
comp.OnStopAuthority();
}
catch (Exception e)
{
Debug.LogError("Exception in OnStopAuthority:" + e.Message + " " + e.StackTrace);
}
}
OnStopAuthority.Invoke();
}

internal void OnSetHostVisibility(bool visible)
Expand Down Expand Up @@ -673,24 +636,9 @@ internal bool OnCheckObserver(NetworkConnection conn)
return true;
}

internal void OnNetworkDestroy()
internal void NetworkDestroy()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
// an exception in OnNetworkDestroy should be caught, so that
// one component's exception doesn't stop all other components
// from being initialized
// => this is what Unity does for Start() etc. too.
// one exception doesn't stop all the other Start() calls!
try
{
comp.OnNetworkDestroy();
}
catch (Exception e)
{
Debug.LogError("Exception in OnNetworkDestroy:" + e.Message + " " + e.StackTrace);
}
}
OnNetworkDestroy.Invoke();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
6 changes: 3 additions & 3 deletions Runtime/NetworkServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ internal void ActivateHostScene()
{
if (LogFilter.Debug) Debug.Log("ActivateHostScene " + identity.netId + " " + identity);

identity.OnStartClient();
identity.StartClient();
}
}
}
Expand Down Expand Up @@ -892,7 +892,7 @@ internal void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
if (ownerConnection is ULocalConnectionToClient)
identity.hasAuthority = true;

identity.OnStartServer();
identity.StartServer();

if (LogFilter.Debug) Debug.Log("SpawnObject instance ID " + identity.netId + " asset ID " + identity.assetId);

Expand Down Expand Up @@ -1058,7 +1058,7 @@ void DestroyObject(NetworkIdentity identity, bool destroyServerObject)
identity.ClearObservers();
if (LocalClientActive)
{
identity.OnNetworkDestroy();
identity.OnNetworkDestroy.Invoke();
}

// when unspawning, dont destroy the server's object
Expand Down
9 changes: 6 additions & 3 deletions Samples~/AdditiveScenes/Scripts/PlayerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ public class PlayerController : NetworkBehaviour
{
public CharacterController characterController;

void Awake()
{
netIdentity.OnStartLocalPlayer.AddListener(OnStartLocalPlayer);
}

void OnValidate()
{
if (characterController == null)
characterController = GetComponent<CharacterController>();
}

public override void OnStartLocalPlayer()
public void OnStartLocalPlayer()
{
base.OnStartLocalPlayer();

Camera.main.orthographic = false;
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = new Vector3(0f, 3f, -8f);
Expand Down
8 changes: 6 additions & 2 deletions Samples~/AdditiveScenes/Scripts/RandomColor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ namespace Mirror.Examples.Additive
{
public class RandomColor : NetworkBehaviour
{
public override void OnStartServer()
void Awake()
{
netIdentity.OnStartServer.AddListener(OnStartServer);
}

public void OnStartServer()
{
base.OnStartServer();
color = Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f);
}

Expand Down

0 comments on commit b97a930

Please sign in to comment.