This document is about: QUANTUM 1
SWITCH TO

Desync Check Code

Niki Walker's Desync Check Code:

C#

// NWalker - FGOL - Desync dump code:

public void InsideYourRPCEventHandler(...)
{
    case PhotonEventDesync:
    {
        if (! m_desyncMessageBytes.ContainsKey(senderId))
        {
            m_desyncMessageBytes[senderId] = new List<byte>(10000);
        }

        var byteContent = (byte[])content;

        if (byteContent.Length != 0)
        {
            m_desyncMessageBytes[senderId].AddRange(byteContent);
        }
        else
        {
            string dump = Encoding.UTF8.GetString(m_desyncMessageBytes[senderId].ToArray());
            Debug.LogError("Desync from sender: " + sender.NickName + "\n" + dump);
            
#if UNITY_EDITOR // Log it to desktop when in the editor.
            var time = DateTime.UtcNow;
            string desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            string path = desktop + "\\Desync_" + time.Hour + "_" + time.Minute + "_" + time.Second + "__" + sender.ID + "_" + sender.NickName + ".txt";
            File.WriteAllText(path, dump);
            Debug.Log("Desync info outputted to file: " + path);
#endif
            data.DesyncText = null;
        }
    }
    break;

}

// Inside your QuantumCallbacks implementation:
/// <inheritdoc />
public override void OnChecksumError(DeterministicTickChecksumError error, Frame[] frames)
{
    base.OnChecksumError(error, frames);

    // Display to player.
    // ... code here that draws a massive desync error message on screen (for devices without consoles)...

    // Log.
    string desyncMessage = PhotonNetwork.playerName + " DESYNC: " + error.Tick + ": " + error.Checksums.Aggregate("\n", (c, n) => c + "[" + n.Player + ", " + n.Checksum + "]") + "\n\n";
        
    // Send it to all users (including self).
    Action<byte[]> raiseEvent = message =>
    {
        PhotonNetwork.RaiseEvent(HssBootstrapper.PhotonEventDesync, message, true, new RaiseEventOptions
        {
            CachingOption = EventCaching.DoNotCache,
            Encrypt = true,
            ForwardToWebhook = false,
            Receivers = ReceiverGroup.All
        });
    };

    // NW: Due to the char limit when sending strings, only send the one frame, but send the full frame dump:
    for (int index = 0; index < frames.Length; index++)
    {
        var frame = frames[index];

        if (frame.Number == error.Tick || frame.Number == error.Tick - 1)
        {
            string frameLog = "F[" + frame.DumpFrame() + "]\n\n";

            desyncMessage += frameLog;
        }
    }

    var bytes = Encoding.UTF8.GetBytes(desyncMessage);
    foreach (var chunk in bytes.Slices(1024, false))
    {
        raiseEvent(chunk);
    }

    // Zero byte denotes completed.
    raiseEvent(new byte[] { });

    Debug.LogError(desyncMessage);
}


public static class Util
{
    public static T[] CopySlice<T>(this T[] source, int index, int length, bool padToLength)
    {
        int n = length;
        T[] slice = null;

        if (source.Length < index + length)
        {
            n = source.Length - index;
            if (padToLength)
            {
                slice = new T[length];
            }
        }

        if (slice == null) slice = new T[n];
        Array.Copy(source, index, slice, 0, n);
        return slice;
    }

    /// <summary>
    ///        Slices the array into chunks of size <see cref="chunkSize"/>.
    /// </summary>
    public static IEnumerable<T[]> Slices<T>(this T[] source, int chunkSize, bool padToLength)
    {
        for (int i = 0; i < source.Length; i += chunkSize)
        {
            yield return source.CopySlice(i, chunkSize, padToLength);
        }
    }
}
Back to top