/// <summary>
 /// This sends a network message with a message ID on the connection. This message is sent on channel zero, which by default is the reliable channel.
 /// </summary>
 /// <typeparam name="T">The message type to unregister.</typeparam>
 /// <param name="msg">The message to send.</param>
 /// <param name="channelId">The transport layer channel to send on.</param>
 /// <returns></returns>
 public bool Send <T>(T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
 {
     using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
     {
         // pack message and send allocation free
         MessagePacker.Pack(msg, writer);
         NetworkDiagnostics.OnSend(msg, channelId, writer.Position, 1);
         return(Send(writer.ToArraySegment(), channelId));
     }
 }
        public static byte[] Pack <T>(T message) where T : IMessageBase
        {
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                Pack(message, writer);
                byte[] data = writer.ToArray();

                return(data);
            }
        }
Beispiel #3
0
 /// <summary>
 /// This sends a network message to the connection. You can await it to check for errors
 /// </summary>
 /// <typeparam name="T">The message type</typeparam>
 /// <param name="msg">The message to send.</param>
 /// <param name="channelId">The transport layer channel to send on.</param>
 /// <returns></returns>
 public virtual UniTask SendAsync <T>(T msg, int channelId = Channel.Reliable)
 {
     using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
     {
         // pack message and send allocation free
         MessagePacker.Pack(msg, writer);
         NetworkDiagnostics.OnSend(msg, channelId, writer.Length, 1);
         return(SendAsync(writer.ToArraySegment(), channelId));
     }
 }
        // send a batch. internal so we can test it.
        internal void SendBatch(int channelId, Batch batch)
        {
            // get max batch size for this channel
            int max = Transport.activeTransport.GetMaxBatchSize(channelId);

            // we need a writer to merge queued messages into a batch
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                // for each queued message
                while (batch.messages.Count > 0)
                {
                    // get it
                    PooledNetworkWriter message = batch.messages.Dequeue();
                    ArraySegment <byte> segment = message.ToArraySegment();

                    // IF adding to writer would end up >= MTU then we should
                    // flush first. the goal is to always flush < MTU packets.
                    //
                    // IMPORTANT: if writer is empty and segment is > MTU
                    //            (which can happen for large max sized message)
                    //            then we would send an empty previous writer.
                    //            => don't do that.
                    //            => only send if not empty.
                    if (writer.Position > 0 &&
                        writer.Position + segment.Count >= max)
                    {
                        // flush & reset writer
                        Transport.activeTransport.ServerSend(connectionId, writer.ToArraySegment(), channelId);
                        writer.Position = 0;
                    }

                    // now add to writer in any case
                    // -> WriteBytes instead of WriteSegment because the latter
                    //    would add a size header. we want to write directly.
                    //
                    // NOTE: it's very possible that we add > MTU to writer if
                    //       message size is > MTU.
                    //       which is fine. next iteration will just flush it.
                    writer.WriteBytes(segment.Array, segment.Offset, segment.Count);

                    // return queued message to pool
                    NetworkWriterPool.Recycle(message);
                }

                // done iterating queued messages.
                // batch might still contain the last message.
                // send it.
                if (writer.Position > 0)
                {
                    Transport.activeTransport.ServerSend(connectionId, writer.ToArraySegment(), channelId);
                    writer.Position = 0;
                }
            }
        }
        internal static byte[] PackWithAlloc <T>(T message) where T : IMessageBase
        {
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            Pack(message, writer);
            byte[] data = writer.ToArray();

            NetworkWriterPool.Recycle(writer);

            return(data);
        }
Beispiel #6
0
 /// <summary>Send a NetworkMessage to this connection over the given channel.</summary>
 public void Send <T>(T msg, int channelId = Channels.Reliable)
     where T : struct, NetworkMessage
 {
     using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
     {
         // pack message and send allocation free
         MessagePacking.Pack(msg, writer);
         NetworkDiagnostics.OnSend(msg, channelId, writer.Position, 1);
         Send(writer.ToArraySegment(), channelId);
     }
 }
        public static byte[] Pack <T>(T message) where T : IMessageBase
        {
            var writer = NetworkWriterPool.GetWriter();

            Pack(message, writer);
            var data = writer.ToArray();

            NetworkWriterPool.Recycle(writer);

            return(data);
        }
        // add a message for batching
        // we allow any sized messages.
        // caller needs to make sure they are within max packet size.
        public void AddMessage(ArraySegment <byte> message)
        {
            // put into a (pooled) writer
            // -> WriteBytes instead of WriteSegment because the latter
            //    would add a size header. we want to write directly.
            // -> will be returned to pool when making the batch!
            // IMPORTANT: NOT adding a size header / msg saves LOTS of bandwidth
            PooledNetworkWriter writer = NetworkWriterPool.GetWriter();

            writer.WriteBytes(message.Array, message.Offset, message.Count);
            messages.Enqueue(writer);
        }
        /// <summary>
        /// This sends a network message with a message ID on the connection. This message is sent on channel zero, which by default is the reliable channel.
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message to send.</param>
        /// <param name="channelId">The transport layer channel to send on.</param>
        /// <returns></returns>
        public virtual bool Send <T>(T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
        {
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            // pack message and send allocation free
            MessagePacker.Pack(msg, writer);
            NetworkDiagnostics.OnSend(msg, channelId, writer.Position, 1);
            bool result = Send(writer.ToArraySegment(), channelId);

            NetworkWriterPool.Recycle(writer);
            return(result);
        }
        // Send stage two: serialized NetworkMessage as ArraySegment<byte>
        internal override void Send(ArraySegment <byte> segment, int channelId = Channels.Reliable)
        {
            // get a writer to copy the message into since the segment is only
            // valid until returning.
            // => pooled writer will be returned to pool when dequeuing.
            // => WriteBytes instead of WriteArraySegment because the latter
            //    includes a 4 bytes header. we just want to write raw.
            //Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}");
            PooledNetworkWriter writer = NetworkWriterPool.GetWriter();

            writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
            connectionToServer.queue.Enqueue(writer);
        }
        void CheckSendRate()
        {
            if (sendMessagesAllowed && syncInterval > 0 && sendTimer < Time.time)
            {
                sendTimer = Time.time + syncInterval;

                NetworkWriter writer = NetworkWriterPool.GetWriter();
                if (WriteParameters(writer))
                {
                    SendAnimationParametersMessage(writer.ToArray());
                }
                NetworkWriterPool.Recycle(writer);
            }
        }
Beispiel #12
0
        /// <summary>
        /// This function invokes the registered handler function for a message.
        /// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message object to process.</param>
        /// <returns></returns>
        public bool InvokeHandler <T>(T msg) where T : IMessageBase
        {
            // get writer from pool
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            // pack and invoke
            int msgType = MessagePacker.GetId <T>();

            MessagePacker.Pack(msg, writer);
            bool result = InvokeHandler(msgType, new NetworkReader(writer.ToArraySegment()));

            // recycle writer and return
            NetworkWriterPool.Recycle(writer);
            return(result);
        }
        void CheckSendRate()
        {
            if (SendMessagesAllowed && syncInterval > 0 && sendTimer < Time.time)
            {
                sendTimer = Time.time + syncInterval;

                using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
                {
                    if (WriteParameters(writer))
                    {
                        SendMotionParametersMessage(writer.ToArray());
                    }
                }
            }
        }
Beispiel #14
0
        /// <summary>
        /// This function invokes the registered handler function for a message.
        /// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message object to process.</param>
        /// <returns>Returns true if the handler was successfully invoked</returns>
        public bool InvokeHandler <T>(T msg, int channelId) where T : NetworkMessage
        {
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                // if it is a value type,  just use typeof(T) to avoid boxing
                // this works because value types cannot be derived
                // if it is a reference type (for example NetworkMessage),
                // ask the message for the real type
                int msgType = MessagePacker.GetId(default(T) != null ? typeof(T) : msg.GetType());

                MessagePacker.Pack(msg, writer);
                ArraySegment <byte> segment = writer.ToArraySegment();
                using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(segment))
                    return(InvokeHandler(msgType, networkReader, channelId));
            }
        }
Beispiel #15
0
        void CheckSendRate()
        {
            float now = Time.time;

            if (SendMessagesAllowed && syncInterval >= 0 && now > nextSendTime)
            {
                nextSendTime = now + syncInterval;

                using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) {
                    if (WriteParameters(writer))
                    {
                        SendAnimationParametersMessage(writer.ToArray());
                    }
                }
            }
        }
        void UpdateClient()
        {
            // send to server if we have local authority (and aren't the server)
            // -> only if connectionToServer has been initialized yet too
            // check only each 'syncInterval'
            if (!IsServer && IsClientWithAuthority && Time.time - lastClientSendTime >= syncInterval)
            {
                if (HasEitherMovedRotatedScaled())
                {
                    // serialize
                    // local position/rotation for VR support
                    using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
                    {
                        SerializeIntoWriter(writer, TargetComponent.transform.localPosition, TargetComponent.transform.localRotation, TargetComponent.transform.localScale);

                        // send to server
                        CmdClientToServerSync(writer.ToArray());
                    }
                }
                lastClientSendTime = Time.time;
            }

            // apply interpolation on client for all players
            // unless this client has authority over the object. could be
            // himself or another object that he was assigned authority over
            if (!IsClientWithAuthority && goal != null)
            {
                // teleport or interpolate
                if (NeedsTeleport())
                {
                    // local position/rotation for VR support
                    ApplyPositionRotationScale(goal.LocalPosition, goal.LocalRotation, goal.LocalScale);

                    // reset data points so we don't keep interpolating
                    start = null;
                    goal  = null;
                }
                else
                {
                    // local position/rotation for VR support
                    ApplyPositionRotationScale(InterpolatePosition(start, goal, TargetComponent.transform.localPosition),
                                               InterpolateRotation(start, goal, TargetComponent.transform.localRotation),
                                               InterpolateScale(start, goal, TargetComponent.transform.localScale));
                }
            }
        }
Beispiel #17
0
        internal void SendSpawnMessage(NetworkIdentity identity, NetworkConnection conn)
        {
            if (identity.serverOnly)
            {
                return;
            }

            // for easier debugging
            if (LogFilter.Debug)
            {
                Debug.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId.ToString("X") + " netid=" + identity.NetId);
            }

            // one writer for owner, one for observers
            using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter())
            {
                // serialize all components with initialState = true
                // (can be null if has none)
                (int ownerWritten, int observersWritten) = identity.OnSerializeAllSafely(true, ownerWriter, observersWriter);

                // convert to ArraySegment to avoid reader allocations
                // (need to handle null case too)
                ArraySegment <byte> ownerSegment     = ownerWritten > 0 ? ownerWriter.ToArraySegment() : default;
                ArraySegment <byte> observersSegment = observersWritten > 0 ? observersWriter.ToArraySegment() : default;

                var msg = new SpawnMessage
                {
                    netId         = identity.NetId,
                    isLocalPlayer = conn.Identity == identity,
                    isOwner       = identity.ConnectionToClient == conn,
                    sceneId       = identity.sceneId,
                    assetId       = identity.AssetId,
                    // use local values for VR support
                    position = identity.transform.localPosition,
                    rotation = identity.transform.localRotation,
                    scale    = identity.transform.localScale
                };

                // use owner segment if 'conn' owns this identity, otherwise
                // use observers segment
                msg.payload = msg.isOwner ? ownerSegment : observersSegment;

                conn.Send(msg);
            }
        }
Beispiel #18
0
        public static byte[] PackMessage(int msgType, MessageBase msg)
        {
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                try
                {
                    // write message type
                    writer.WriteInt16((short)msgType);

                    // serialize message into writer
                    msg.Serialize(writer);

                    // return byte[]
                    return(writer.ToArray());
                }
                finally { }
            }
        }
Beispiel #19
0
        /// <summary>
        /// This function invokes the registered handler function for a message.
        /// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message object to process.</param>
        /// <returns></returns>
        public bool InvokeHandler <T>(T msg, int channelId) where T : IMessageBase
        {
            // get writer from pool
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                // if it is a value type,  just use typeof(T) to avoid boxing
                // this works because value types cannot be derived
                // if it is a reference type (for example IMessageBase),
                // ask the message for the real type
                int msgType = MessagePacker.GetId(typeof(T).IsValueType ? typeof(T) : msg.GetType());

                Debug.Log("[NetworkConnection].InvokeHandler -- msg: " + msg.ToString());

                MessagePacker.Pack(msg, writer);
                ArraySegment <byte> segment = writer.ToArraySegment();
                using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(segment))
                    return(InvokeHandler(msgType, networkReader, channelId));
            }
        }
        void FixedUpdate()
        {
            if (!SendMessagesAllowed)
            {
                return;
            }

            CheckSendRate();

            if (m_MotionStateUpdate)
            {
                //Update the motionHash
                using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
                {
                    WriteParameters(writer);
                    SendMotionMessage(m_Motion.currentState.serializationKey, writer.ToArray());
                }
            }
        }
Beispiel #21
0
        /// <summary>
        /// This function invokes the registered handler function for a message.
        /// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message object to process.</param>
        /// <returns></returns>
        public bool InvokeHandler <T>(T msg, int channelId) where T : IMessageBase
        {
            // get writer from pool
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            // if it is a value type,  just use typeof(T) to avoid boxing
            // this works because value types cannot be derived
            // if it is a reference type (for example IMessageBase),
            // ask the message for the real type
            int msgType = MessagePacker.GetId(typeof(T).IsValueType ? typeof(T) : msg.GetType());

            MessagePacker.Pack(msg, writer);
            ArraySegment <byte> segment = writer.ToArraySegment();
            bool result = InvokeHandler(msgType, new NetworkReader(segment), channelId);

            // recycle writer and return
            NetworkWriterPool.Recycle(writer);
            return(result);
        }
        /// <summary>
        /// This function invokes the registered handler function for a message.
        /// <para>Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.</para>
        /// </summary>
        /// <typeparam name="T">The message type to unregister.</typeparam>
        /// <param name="msg">The message object to process.</param>
        /// <returns></returns>
        public bool InvokeHandler <T>(T msg, int channelId) where T : IMessageBase
        {
            // get writer from pool
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            // pack and invoke
            int msgType = MessagePacker.GetId(typeof(T).IsValueType ? typeof(T) : msg.GetType());

            MessagePacker.Pack(msg, writer);
            ArraySegment <byte> segment = writer.ToArraySegment();
            NetworkReader       reader  = NetworkReaderPool.GetReader(segment);
            bool result = InvokeHandler(msgType, reader, channelId);

            NetworkReaderPool.Recycle(reader);

            // recycle writer and return
            NetworkWriterPool.Recycle(writer);
            return(result);
        }
        public static void Send <T>(IEnumerable <NetworkConnection> connections, T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
        {
            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
            {
                // pack message into byte[] once
                MessagePacker.Pack(msg, writer);
                var segment = writer.ToArraySegment();
                int count   = 0;

                foreach (NetworkConnection conn in connections)
                {
                    // send to all connections, but don't wait for them
                    _ = conn.SendAsync(segment);
                    count++;
                }

                NetworkDiagnostics.OnSend(msg, channelId, segment.Count, count);
            }
        }
        internal override void Update()
        {
            base.Update();

            // should we still process a connected event?
            if (connectedEventPending)
            {
                connectedEventPending = false;
                NetworkClient.OnConnectedEvent?.Invoke();
            }

            // process internal messages so they are applied at the correct time
            while (queue.Count > 0)
            {
                // call receive on queued writer's content, return to pool
                PooledNetworkWriter writer  = queue.Dequeue();
                ArraySegment <byte> message = writer.ToArraySegment();

                // OnTransportData assumes a proper batch with timestamp etc.
                // let's make a proper batch and pass it to OnTransportData.
                Batcher batcher = GetBatchForChannelId(Channels.Reliable);
                batcher.AddMessage(message);

                using (PooledNetworkWriter batchWriter = NetworkWriterPool.GetWriter())
                {
                    // make a batch with our local time (double precision)
                    if (batcher.MakeNextBatch(batchWriter, NetworkTime.localTime))
                    {
                        NetworkClient.OnTransportData(batchWriter.ToArraySegment(), Channels.Reliable);
                    }
                }

                NetworkWriterPool.Recycle(writer);
            }

            // should we still process a disconnected event?
            if (disconnectedEventPending)
            {
                disconnectedEventPending = false;
                NetworkClient.OnDisconnectedEvent?.Invoke();
            }
        }
Beispiel #25
0
        public static byte[] PackMessage(int msgType, MessageBase msg)
        {
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            try
            {
                // write message type
                writer.Write((short)msgType);

                // serialize message into writer
                msg.Serialize(writer);

                // return byte[]
                return(writer.ToArray());
            }
            finally
            {
                NetworkWriterPool.Recycle(writer);
            }
        }
Beispiel #26
0
        // pack message before sending
        public static byte[] Pack <T>(T message) where T : IMessageBase
        {
            NetworkWriter writer = NetworkWriterPool.GetWriter();

            try
            {
                // write message type
                int msgType = GetId <T>();
                writer.WriteUInt16((ushort)msgType);

                // serialize message into writer
                message.Serialize(writer);

                // return byte[]
                return(writer.ToArray());
            }
            finally
            {
                NetworkWriterPool.Recycle(writer);
            }
        }
Beispiel #27
0
        internal void SendSpawnMessage(NetworkIdentity identity, INetworkConnection conn)
        {
            if (identity.serverOnly)
            {
                return;
            }

            // for easier debugging
            if (logger.LogEnabled())
            {
                logger.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId.ToString("X") + " netid=" + identity.NetId);
            }

            // one writer for owner, one for observers
            using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter())
            {
                bool isOwner = identity.ConnectionToClient == conn;

                ArraySegment <byte> payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter);

                var msg = new SpawnMessage
                {
                    netId         = identity.NetId,
                    isLocalPlayer = conn.Identity == identity,
                    isOwner       = isOwner,
                    sceneId       = identity.sceneId,
                    assetId       = identity.AssetId,
                    // use local values for VR support
                    position = identity.transform.localPosition,
                    rotation = identity.transform.localRotation,
                    scale    = identity.transform.localScale,

                    payload = payload,
                };


                conn.Send(msg);
            }
        }
        void FixedUpdate()
        {
            if (!SendMessagesAllowed)
            {
                return;
            }

            CheckSendRate();

            for (int i = 0; i < Animator.layerCount; i++)
            {
                if (!CheckAnimStateChanged(out int stateHash, out float normalizedTime, i))
                {
                    continue;
                }

                using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
                {
                    WriteParameters(writer);
                    SendAnimationMessage(stateHash, normalizedTime, i, writer.ToArray());
                }
            }
        }
        void Update()
        {
            // if server then always sync to others.
            if (isServer)
            {
                // just use OnSerialize via SetDirtyBit only sync when position
                // changed. set dirty bits 0 or 1
                SetDirtyBit(HasEitherMovedRotatedScaled() ? 1UL : 0UL);
            }

            // no 'else if' since host mode would be both
            if (isClient)
            {
                // send to server if we have local authority (and aren't the server)
                // -> only if connectionToServer has been initialized yet too
                if (!isServer && isClientWithAuthority)
                {
                    // check only each 'syncInterval'
                    if (Time.time - lastClientSendTime >= syncInterval)
                    {
                        if (HasEitherMovedRotatedScaled())
                        {
                            // serialize
                            // local position/rotation for VR support
                            NetworkWriter writer = NetworkWriterPool.GetWriter();
                            SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);

                            // send to server
                            CmdClientToServerSync(writer.ToArray());
                            NetworkWriterPool.Recycle(writer);
                        }
                        lastClientSendTime = Time.time;
                    }
                }

                // apply interpolation on client for all players
                // unless this client has authority over the object. could be
                // himself or another object that he was assigned authority over
                if (!isClientWithAuthority)
                {
                    // received one yet? (initialized?)
                    if (goal != null)
                    {
                        // teleport or interpolate
                        if (NeedsTeleport())
                        {
                            // local position/rotation for VR support
                            ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);

                            // reset data points so we don't keep interpolating
                            start = null;
                            goal  = null;
                        }
                        else
                        {
                            // local position/rotation for VR support
                            ApplyPositionRotationScale(InterpolatePosition(start, goal, targetComponent.transform.localPosition),
                                                       InterpolateRotation(start, goal, targetComponent.transform.localRotation),
                                                       InterpolateScale(start, goal, targetComponent.transform.localScale));
                        }
                    }
                }
            }
        }
Beispiel #30
0
        internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnection conn)
        {
            if (identity.serverOnly)
            {
                return;
            }

            if (LogFilter.Debug)
            {
                Debug.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId.ToString("X") + " netid=" + identity.netId);                  // for easier debugging
            }
            NetworkWriter writer = NetworkWriterPool.GetWriter();



            // convert to ArraySegment to avoid reader allocations
            // (need to handle null case too)
            ArraySegment <byte> segment = default;

            // serialize all components with initialState = true
            // (can be null if has none)
            if (identity.OnSerializeAllSafely(true, writer))
            {
                segment = writer.ToArraySegment();
            }

            // 'identity' is a prefab that should be spawned
            if (identity.sceneId == 0)
            {
                SpawnPrefabMessage msg = new SpawnPrefabMessage
                {
                    netId   = identity.netId,
                    owner   = conn?.playerController == identity,
                    assetId = identity.assetId,
                    // use local values for VR support
                    position = identity.transform.localPosition,
                    rotation = identity.transform.localRotation,
                    scale    = identity.transform.localScale,
                    payload  = segment
                };

                // conn is != null when spawning it for a client
                if (conn != null)
                {
                    conn.Send(msg);
                }
                // conn is == null when spawning it for the local player
                else
                {
                    SendToReady(identity, msg);
                }
            }
            // 'identity' is a scene object that should be spawned again
            else
            {
                SpawnSceneObjectMessage msg = new SpawnSceneObjectMessage
                {
                    netId   = identity.netId,
                    owner   = conn?.playerController == identity,
                    sceneId = identity.sceneId,
                    // use local values for VR support
                    position = identity.transform.localPosition,
                    rotation = identity.transform.localRotation,
                    scale    = identity.transform.localScale,
                    payload  = segment
                };

                // conn is != null when spawning it for a client
                if (conn != null)
                {
                    conn.Send(msg);
                }
                // conn is == null when spawning it for the local player
                else
                {
                    SendToReady(identity, msg);
                }
            }

            NetworkWriterPool.Recycle(writer);
        }