// serialize all components (or only dirty ones if not initial state) // -> returns TRUE if any date other than dirtyMask was written! internal bool OnSerializeAllSafely(NetworkBehaviour[] components, NetworkWriter writer, bool initialState) { if (components.Length > 64) { if (LogFilter.logError) { Debug.LogError("Only 64 NetworkBehaviour components are allowed for NetworkIdentity: " + name + " because of the dirtyComponentMask"); } return(false); } // loop through all components only once and then write dirty+payload into the writer afterwards ulong dirtyComponentsMask = 0L; NetworkWriter payload = new NetworkWriter(); for (int i = 0; i < components.Length; ++i) { // is this component dirty? // -> always serialize if initialState so all components are included in spawn packet // -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet NetworkBehaviour comp = m_NetworkBehaviours[i]; if (initialState || comp.IsDirty()) { // set bit #i to 1 in dirty mask dirtyComponentsMask |= (ulong)(1L << i); // serialize the data if (LogFilter.logDebug) { Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState); } OnSerializeSafely(comp, payload, initialState); // Clear dirty bits only if we are synchronizing data and not sending a spawn message. // This preserves the behavior in HLAPI if (!initialState) { comp.ClearAllDirtyBits(); } } } // did we write anything? then write dirty, payload and return true if (dirtyComponentsMask != 0L) { byte[] payloadBytes = payload.ToArray(); writer.WritePackedUInt64(dirtyComponentsMask); // WritePacked64 so we don't write full 8 bytes if we don't have to writer.Write(payloadBytes, 0, payloadBytes.Length); return(true); } // didn't write anything, return false return(false); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState) { // extract data length and data safely, untouched by user code // -> returns empty array if length is 0, so .Length is always the proper length byte[] bytes = reader.ReadBytesAndSize(); if (LogFilter.logDebug) { Debug.Log("OnDeserializeSafely extracted: " + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length); } // call OnDeserialize with a temporary reader, so that the // original one can't be messed with. we also wrap it in a // try-catch block so there's no way to mess up another // component's deserialization try { comp.OnDeserialize(new NetworkReader(bytes), initialState); } catch (Exception e) { // show a detailed error and let the user know what went wrong Debug.LogError("OnDeserialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length + ". Possible Reasons:\n * Do " + comp.GetType() + "'s OnSerialize and OnDeserialize calls write the same amount of data(" + bytes.Length + " bytes)? \n * Was there an exception in " + comp.GetType() + "'s OnSerialize/OnDeserialize code?\n * Are the server and client the exact same project?\n * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" + e.ToString()); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState) { // read header as 4 bytes int contentSize = reader.ReadInt32(); // read content byte[] bytes = reader.ReadBytes(contentSize); if (LogFilter.Debug) { Debug.Log("OnDeserializeSafely extracted: " + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length); } // call OnDeserialize with a temporary reader, so that the // original one can't be messed with. we also wrap it in a // try-catch block so there's no way to mess up another // component's deserialization try { NetworkReader componentReader = new NetworkReader(bytes); comp.OnDeserialize(componentReader, initialState); if (componentReader.Position != componentReader.Length) { Debug.LogWarning("OnDeserialize didn't read the full " + bytes.Length + " bytes for object:" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + ". Make sure that OnSerialize and OnDeserialize write/read the same amount of data in all cases."); } } catch (Exception e) { // show a detailed error and let the user know what went wrong Debug.LogError("OnDeserialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length + ". Possible Reasons:\n * Do " + comp.GetType() + "'s OnSerialize and OnDeserialize calls write the same amount of data(" + bytes.Length + " bytes)? \n * Was there an exception in " + comp.GetType() + "'s OnSerialize/OnDeserialize code?\n * Are the server and client the exact same project?\n * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" + e.ToString()); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// // vis2k: readstring bug prevention: https://issuetracker.unity3d.com/issues/unet-networkwriter-dot-write-causing-readstring-slash-readbytes-out-of-range-errors-in-clients // -> OnSerialize writes length,componentData,length,componentData,... // -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers // -> it will be impossible to read too many or too few bytes in OnDeserialize // -> we can properly track down errors internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState) { // serialize into a temporary writer NetworkWriter temp = new NetworkWriter(); bool result = false; try { result = comp.OnSerialize(temp, initialState); } catch (Exception e) { // show a detailed error and let the user know what went wrong Debug.LogError("OnSerialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + "\n\n" + e.ToString()); } byte[] bytes = temp.ToArray(); if (LogFilter.logDebug) { Debug.Log("OnSerializeSafely written for object=" + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length); } // original HLAPI had a warning in UNetUpdate() in case of large state updates. let's move it here, might // be useful for debugging. if (bytes.Length > Transport.MaxPacketSize) { if (LogFilter.logWarn) { Debug.LogWarning("Large state update of " + bytes.Length + " bytes for netId:" + netId + " from script:" + comp); } } // serialize length,data into the real writer, untouched by user code writer.WriteBytesAndSize(bytes); return(result); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// // vis2k: readstring bug prevention: https://issuetracker.unity3d.com/issues/unet-networkwriter-dot-write-causing-readstring-slash-readbytes-out-of-range-errors-in-clients // -> OnSerialize writes length,componentData,length,componentData,... // -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers // -> it will be impossible to read too many or too few bytes in OnDeserialize // -> we can properly track down errors internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState) { // write placeholder length bytes // (jumping back later is WAY faster than allocating a temporary // writer for the payload, then writing payload.size, payload) int headerPosition = writer.Position; writer.Write((int)0); int contentPosition = writer.Position; // write payload bool result = false; try { result = comp.OnSerialize(writer, initialState); } catch (Exception e) { // show a detailed error and let the user know what went wrong Debug.LogError("OnSerialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + "\n\n" + e.ToString()); } int endPosition = writer.Position; // fill in length now writer.Position = headerPosition; writer.Write(endPosition - contentPosition); writer.Position = endPosition; if (LogFilter.Debug) { Debug.Log("OnSerializeSafely written for object=" + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + "header@" + headerPosition + " content@" + contentPosition + " end@" + endPosition + " contentSize=" + (endPosition - contentPosition)); } return(result); }