Skip to content

Commit

Permalink
feat: adding Name and GameObjectActive to Spawn settings
Browse files Browse the repository at this point in the history
sending name and gameobject active are now options

BREAKING CHANGE: SpawnMessage now has SpawnValues struct to store values in. SpawnMessage also has new ToString message
  • Loading branch information
James-Frowen committed May 1, 2023
1 parent b01d78e commit fd12390
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 89 deletions.
95 changes: 43 additions & 52 deletions Assets/Mirage/Runtime/ClientObjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,8 @@ public void DestroyAllClientObjects()

private void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage msg)
{
if (msg.prefabHash.HasValue)
identity.PrefabHash = msg.prefabHash.Value;
if (msg.PrefabHash.HasValue)
identity.PrefabHash = msg.PrefabHash.Value;

if (!identity.gameObject.activeSelf)
{
Expand All @@ -540,14 +540,14 @@ private void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage msg)

identity.SetClientValues(this, msg);

if (msg.isLocalPlayer)
if (msg.IsLocalPlayer)
InternalAddCharacter(identity);

// deserialize components if any payload
// (Count is 0 if there were no components)
if (msg.payload.Count > 0)
if (msg.Payload.Count > 0)
{
using (var payloadReader = NetworkReaderPool.GetReader(msg.payload, Client.World))
using (var payloadReader = NetworkReaderPool.GetReader(msg.Payload, Client.World))
{
identity.OnDeserializeAll(payloadReader, true);
}
Expand All @@ -561,24 +561,24 @@ private void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage msg)

internal void OnSpawn(SpawnMessage msg)
{
if (msg.prefabHash == null && msg.sceneId == null)
throw new SpawnObjectException($"Empty prefabHash and sceneId for netId: {msg.netId}");
if (msg.PrefabHash == null && msg.SceneId == null)
throw new SpawnObjectException($"Empty prefabHash and sceneId for netId: {msg.NetId}");

if (logger.LogEnabled()) logger.Log($"Client spawn handler instantiating netId={msg.netId} prefabHash={msg.prefabHash:X} sceneId={msg.sceneId:X} pos={msg.position}");
if (logger.LogEnabled()) logger.Log($"[ClientObjectManager] Spawn: {msg}");

// was the object already spawned?
var existing = Client.World.TryGetIdentity(msg.netId, out var identity);
var existing = Client.World.TryGetIdentity(msg.NetId, out var identity);

if (!existing)
{
//is the object on the prefab or scene object lists?
if (msg.sceneId.HasValue)
if (msg.SceneId.HasValue)
{
identity = SpawnSceneObject(msg);
}
else if (msg.prefabHash.HasValue)
else if (msg.PrefabHash.HasValue)
{
var handler = GetSpawnHandler(msg.prefabHash.Value);
var handler = GetSpawnHandler(msg.PrefabHash.Value);
if (handler.IsAsyncSpawn())
{
OnSpawnAsync(handler.HandlerAsync, msg).Forget();
Expand All @@ -604,7 +604,7 @@ private void AfterSpawn(SpawnMessage msg, bool alreadyExisted, NetworkIdentity s

// add after applying payload, but only if it is new object
if (!alreadyExisted)
Client.World.AddIdentity(msg.netId, spawnedIdentity);
Client.World.AddIdentity(msg.NetId, spawnedIdentity);
}

private async UniTaskVoid OnSpawnAsync(SpawnHandlerAsyncDelegate spawnHandler, SpawnMessage msg)
Expand All @@ -615,11 +615,11 @@ private async UniTaskVoid OnSpawnAsync(SpawnHandlerAsyncDelegate spawnHandler, S
// todo can this be optimized
using (var writer = NetworkWriterPool.GetWriter())
{
writer.Write(msg.payload);
writer.Write(msg.Payload);
// use read and write so that payload will look the same as original
using (var reader = NetworkReaderPool.GetReader(writer.ToArraySegment(), null))
{
msg.payload = reader.Read<ArraySegment<byte>>();
msg.Payload = reader.Read<ArraySegment<byte>>();

var identity = await spawnHandler.Invoke(msg);
AfterSpawn(msg, false, identity);
Expand All @@ -639,11 +639,11 @@ private NetworkIdentity SpawnPrefab(SpawnMessage msg, SpawnHandler handler)
var spawnHandler = handler.Handler;
if (spawnHandler != null)
{
if (logger.LogEnabled()) logger.Log($"Client spawn with custom handler: [netId:{msg.netId} prefabHash:{msg.prefabHash:X} pos:{msg.position} rotation: {msg.rotation}]");
if (logger.LogEnabled()) logger.Log($"[ClientObjectManager] Custom handler for netid:{msg.NetId}");

var obj = spawnHandler.Invoke(msg);
if (obj == null)
throw new SpawnObjectException($"Spawn handler for prefabHash={msg.prefabHash:X} returned null");
throw new SpawnObjectException($"Spawn handler for prefabHash={msg.PrefabHash:X} returned null");
return obj;
}

Expand All @@ -652,25 +652,27 @@ private NetworkIdentity SpawnPrefab(SpawnMessage msg, SpawnHandler handler)

var prefab = handler.Prefab;

if (logger.LogEnabled()) logger.Log($"Client spawn from prefab: [netId:{msg.netId} prefabHash:{msg.prefabHash:X} pos:{msg.position} rotation: {msg.rotation}]");
if (logger.LogEnabled()) logger.Log($"[ClientObjectManager] Instantiate Prefab for netid:{msg.NetId}, hash:{msg.PrefabHash.Value}, prefab:{prefab.name}");

// we need to set position and rotation here incase that their values are used from awake/onenable
var pos = msg.position ?? prefab.transform.position;
var rot = msg.rotation ?? prefab.transform.rotation;
var pos = msg.SpawnValues.Position ?? prefab.transform.position;
var rot = msg.SpawnValues.Rotation ?? prefab.transform.rotation;
return Instantiate(prefab, pos, rot);
}

internal NetworkIdentity SpawnSceneObject(SpawnMessage msg)
{
var spawned = SpawnSceneObject(msg.sceneId.Value);
if (spawned != null)
var sceneId = msg.SceneId.Value;

if (spawnableObjects.TryGetValue(sceneId, out var foundSceneObject))
{
if (logger.LogEnabled()) logger.Log($"Client spawn from scene object [netId:{msg.netId}] [sceneId:{msg.sceneId:X}] obj:{spawned}");
return spawned;
spawnableObjects.Remove(sceneId);
if (logger.LogEnabled()) logger.Log($"[ClientObjectManager] Found scene object for netid:{msg.NetId}, sceneId:{msg.SceneId.Value:X}, obj:{foundSceneObject}");
return foundSceneObject;
}

// failed to spawn
var errorMsg = $"Failed to spawn scene object sceneId={msg.sceneId:X}";
var errorMsg = $"Failed to spawn scene object sceneId={msg.SceneId:X}";
// dump the whole spawnable objects dict for easier debugging
if (logger.LogEnabled())
{
Expand All @@ -686,27 +688,16 @@ internal NetworkIdentity SpawnSceneObject(SpawnMessage msg)
throw new SpawnObjectException(errorMsg);
}

private NetworkIdentity SpawnSceneObject(ulong sceneId)
{
if (spawnableObjects.TryGetValue(sceneId, out var identity))
{
spawnableObjects.Remove(sceneId);
return identity;
}
logger.LogWarning($"Could not find scene object with sceneId:{sceneId:X}");
return null;
}

internal void OnRemoveAuthority(RemoveAuthorityMessage msg)
{
if (logger.LogEnabled()) logger.Log($"Client remove auth handler");

// was the object already spawned?
var existing = Client.World.TryGetIdentity(msg.netId, out var identity);
var existing = Client.World.TryGetIdentity(msg.NetId, out var identity);

if (!existing)
{
logger.LogWarning($"Could not find object with id {msg.netId}");
logger.LogWarning($"Could not find object with id {msg.NetId}");
return;
}

Expand All @@ -729,19 +720,19 @@ internal void OnRemoveCharacter(RemoveCharacterMessage msg)
}

player.Identity = null;
identity.HasAuthority = msg.keepAuthority;
identity.HasAuthority = msg.KeepAuthority;

identity.NotifyAuthority();
}

internal void OnObjectHide(ObjectHideMessage msg)
{
DestroyObject(msg.netId);
DestroyObject(msg.NetId);
}

internal void OnObjectDestroy(ObjectDestroyMessage msg)
{
DestroyObject(msg.netId);
DestroyObject(msg.NetId);
}

private void DestroyObject(uint netId)
Expand All @@ -760,9 +751,9 @@ private void DestroyObject(uint netId)

internal void OnHostClientSpawn(SpawnMessage msg)
{
if (Client.World.TryGetIdentity(msg.netId, out var localObject))
if (Client.World.TryGetIdentity(msg.NetId, out var localObject))
{
if (msg.isLocalPlayer)
if (msg.IsLocalPlayer)
InternalAddCharacter(localObject);

localObject.SetClientValues(this, msg);
Expand All @@ -775,24 +766,24 @@ internal void OnHostClientSpawn(SpawnMessage msg)

internal void OnRpcMessage(RpcMessage msg)
{
if (logger.LogEnabled()) logger.Log($"ClientScene.OnRPCMessage index:{msg.functionIndex} netId:{msg.netId}");
if (logger.LogEnabled()) logger.Log($"ClientScene.OnRPCMessage index:{msg.FunctionIndex} netId:{msg.NetId}");

if (!Client.World.TryGetIdentity(msg.netId, out var identity))
if (!Client.World.TryGetIdentity(msg.NetId, out var identity))
{
if (logger.WarnEnabled()) logger.LogWarning($"Spawned object not found when handling ClientRpc message [netId={msg.netId}]");
if (logger.WarnEnabled()) logger.LogWarning($"Spawned object not found when handling ClientRpc message [netId={msg.NetId}]");
return;
}

var behaviour = identity.NetworkBehaviours[msg.componentIndex];
var behaviour = identity.NetworkBehaviours[msg.ComponentIndex];

var remoteCall = behaviour.RemoteCallCollection.Get(msg.functionIndex);
var remoteCall = behaviour.RemoteCallCollection.Get(msg.FunctionIndex);

if (remoteCall.InvokeType != RpcInvokeType.ClientRpc)
{
throw new MethodInvocationException($"Invalid RPC call with index {msg.functionIndex}");
throw new MethodInvocationException($"Invalid RPC call with index {msg.FunctionIndex}");
}

using (var reader = NetworkReaderPool.GetReader(msg.payload, Client.World))
using (var reader = NetworkReaderPool.GetReader(msg.Payload, Client.World))
{
remoteCall.Invoke(reader, behaviour, null, 0);
}
Expand All @@ -812,10 +803,10 @@ private void CheckForLocalPlayer(NetworkIdentity identity)
private void OnServerRpcReply(INetworkPlayer player, ServerRpcReply reply)
{
// find the callback that was waiting for this and invoke it.
if (_callbacks.TryGetValue(reply.replyId, out var action))
if (_callbacks.TryGetValue(reply.ReplyId, out var action))
{
_callbacks.Remove(_replyId);
using (var reader = NetworkReaderPool.GetReader(reply.payload, Client.World))
using (var reader = NetworkReaderPool.GetReader(reply.Payload, Client.World))
{
action.Invoke(reader);
}
Expand Down
88 changes: 77 additions & 11 deletions Assets/Mirage/Runtime/Messages.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

namespace Mirage
{

#region Public System Messages

/// <summary>
Expand Down Expand Up @@ -103,25 +103,91 @@ public struct SpawnMessage
/// <para>If sceneId != 0 then it is used instead of prefabHash</para>
/// </summary>
public int? PrefabHash;

/// <summary>
/// Local position
/// </summary>
public Vector3? position;
/// <summary>
/// Local rotation
/// </summary>
public Quaternion? rotation;
/// <summary>
/// Local scale
/// Spawn values to set after spawning object, values based on <see cref="NetworkIdentity.TransformSpawnSettings"/>
/// </summary>
public Vector3? scale;
public SpawnValues SpawnValues;

/// <summary>
/// The serialized component data
/// <remark>ArraySegment to avoid unnecessary allocations</remark>
/// </summary>
public ArraySegment<byte> Payload;

public override string ToString()
{
string spawnIDStr;
if (SceneId.HasValue)
spawnIDStr = $"SceneId:{SceneId.Value}";
else if (PrefabHash.HasValue)
spawnIDStr = $"PrefabHash:{PrefabHash.Value}";
else
spawnIDStr = $"SpawnId:Error";

string authStr;
if (IsLocalPlayer)
authStr = "LocalPlayer";
else if (IsOwner)
authStr = "Owner";
else
authStr = "Remote";


return $"SpawnMessage[NetId:{NetId},{spawnIDStr},Authority:{authStr},{SpawnValues},Payload:{Payload.Count}bytes]";

}
}

public struct SpawnValues
{
public Vector3? Position;
public Quaternion? Rotation;
public Vector3? Scale;
public string Name;
public bool? SelfActive;

[ThreadStatic]
private static StringBuilder builder;

public override string ToString()
{
if (builder == null)
builder = new StringBuilder();
else
builder.Clear();

builder.Append("SpawnValues(");
var first = true;

if (Position.HasValue)
Append(ref first, $"Position={Position.Value}");

if (Rotation.HasValue)
Append(ref first, $"Rotation={Rotation.Value}");

if (Scale.HasValue)
Append(ref first, $"Scale={Scale.Value}");

if (!string.IsNullOrEmpty(Name))
Append(ref first, $"Name={Name}");

if (SelfActive.HasValue)
Append(ref first, $"SelfActive={SelfActive.Value}");

builder.Append(")");
return builder.ToString();
}

private static void Append(ref bool first, string value)
{
if (!first) builder.Append(", ");
first = false;
builder.Append(value);
}
}


[NetworkMessage]
public struct RemoveAuthorityMessage
{
Expand Down

0 comments on commit fd12390

Please sign in to comment.