Compare commits

...

2 Commits

Author SHA1 Message Date
65b153f97d Ready for PacMan?? 2026-01-13 21:28:53 +01:00
da0e6699e7 Working well now I think? 2026-01-12 21:08:20 +01:00
5 changed files with 1008 additions and 693 deletions

View File

@@ -290,12 +290,12 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2841571} m_GameObject: {fileID: 2841571}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 4.266148, y: 14.909718, z: -30.038902}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 297676220} m_Father: {fileID: 712073434}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &6734103 --- !u!1001 &6734103
PrefabInstance: PrefabInstance:
@@ -9699,11 +9699,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 1897221841}
- {fileID: 2841572}
- {fileID: 963448677} - {fileID: 963448677}
- {fileID: 1334822256}
- {fileID: 1683006206}
m_Father: {fileID: 1886023632} m_Father: {fileID: 1886023632}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &300095180 --- !u!1001 &300095180
@@ -22129,6 +22125,10 @@ Transform:
- {fileID: 462995872} - {fileID: 462995872}
- {fileID: 339867315} - {fileID: 339867315}
- {fileID: 99960227} - {fileID: 99960227}
- {fileID: 1334822256}
- {fileID: 1683006206}
- {fileID: 2841572}
- {fileID: 1381489908}
m_Father: {fileID: 1886023632} m_Father: {fileID: 1886023632}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!4 &712932630 stripped --- !u!4 &712932630 stripped
@@ -25080,6 +25080,7 @@ Transform:
- {fileID: 196434888} - {fileID: 196434888}
- {fileID: 1933482823} - {fileID: 1933482823}
- {fileID: 1959372908} - {fileID: 1959372908}
- {fileID: 1897221841}
m_Father: {fileID: 1886023632} m_Father: {fileID: 1886023632}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &808835976 --- !u!1001 &808835976
@@ -25357,6 +25358,9 @@ MonoBehaviour:
- {fileID: 1910438138} - {fileID: 1910438138}
- {fileID: 1407423087} - {fileID: 1407423087}
- {fileID: 1541737191} - {fileID: 1541737191}
delay: 1
maxEventSendTries: 3
pingDelay: 0.3
DebugImageToIndicateOwner: {fileID: 0} DebugImageToIndicateOwner: {fileID: 0}
--- !u!1 &817541382 stripped --- !u!1 &817541382 stripped
GameObject: GameObject:
@@ -29223,37 +29227,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
m_PrefabInstance: {fileID: 230093841} m_PrefabInstance: {fileID: 230093841}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1 &979503862
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 979503863}
m_Layer: 0
m_Name: Smoothing when correcting for turns
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &979503863
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 979503862}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1002127288}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &982882420 --- !u!1001 &982882420
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -30452,40 +30425,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
m_PrefabInstance: {fileID: 1789751918} m_PrefabInstance: {fileID: 1789751918}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1 &1002127287
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1002127288}
m_Layer: 0
m_Name: Optional network optimising
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1002127288
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1002127287}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1321513926}
- {fileID: 979503863}
- {fileID: 1983089941}
m_Father: {fileID: 1886023632}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1002272347 stripped --- !u!1 &1002272347 stripped
GameObject: GameObject:
m_CorrespondingSourceObject: {fileID: 4099390335584803315, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 4099390335584803315, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
@@ -37839,6 +37778,9 @@ MonoBehaviour:
- {fileID: 1156754006} - {fileID: 1156754006}
- {fileID: 527776391} - {fileID: 527776391}
- {fileID: 357748371} - {fileID: 357748371}
delay: 1
maxEventSendTries: 3
pingDelay: 0.3
DebugImageToIndicateOwner: {fileID: 1745114900} DebugImageToIndicateOwner: {fileID: 1745114900}
--- !u!1 &1203813879 --- !u!1 &1203813879
GameObject: GameObject:
@@ -41526,37 +41468,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
m_PrefabInstance: {fileID: 1008616612} m_PrefabInstance: {fileID: 1008616612}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1 &1321513925
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1321513926}
m_Layer: 0
m_Name: Ghosts and pacman only try to turn at intersections
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1321513926
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1321513925}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1002127288}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1321862915 --- !u!1001 &1321862915
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -41782,12 +41693,12 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1334822255} m_GameObject: {fileID: 1334822255}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 4.266148, y: 14.909718, z: -30.038902}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 297676220} m_Father: {fileID: 712073434}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1335132404 --- !u!1001 &1335132404
PrefabInstance: PrefabInstance:
@@ -43042,6 +42953,37 @@ Transform:
m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
m_PrefabInstance: {fileID: 321492582} m_PrefabInstance: {fileID: 321492582}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1 &1381489907
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1381489908}
m_Layer: 0
m_Name: FullSync with forced sync of id and time
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1381489908
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1381489907}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 4.266148, y: 14.909718, z: -30.038902}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 712073434}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1381602940 --- !u!1001 &1381602940
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -51821,12 +51763,12 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1683006205} m_GameObject: {fileID: 1683006205}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 4.266148, y: 14.909718, z: -30.038902}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 297676220} m_Father: {fileID: 712073434}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!4 &1683500364 stripped --- !u!4 &1683500364 stripped
Transform: Transform:
@@ -58950,7 +58892,6 @@ Transform:
- {fileID: 1961479820} - {fileID: 1961479820}
- {fileID: 2129311453} - {fileID: 2129311453}
- {fileID: 2142346958} - {fileID: 2142346958}
- {fileID: 1002127288}
- {fileID: 806746267} - {fileID: 806746267}
- {fileID: 1438618203} - {fileID: 1438618203}
m_Father: {fileID: 0} m_Father: {fileID: 0}
@@ -59065,7 +59006,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 297676220} m_Father: {fileID: 806746267}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1898242099 stripped --- !u!1 &1898242099 stripped
GameObject: GameObject:
@@ -61610,37 +61551,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3} m_CorrespondingSourceObject: {fileID: 1315692994360949719, guid: 00a825a5aeafee94789192f61cbb3a5a, type: 3}
m_PrefabInstance: {fileID: 1728375025} m_PrefabInstance: {fileID: 1728375025}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1 &1983089940
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1983089941}
m_Layer: 0
m_Name: Low-lag distance modes
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1983089941
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1983089940}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1002127288}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1988625401 --- !u!1001 &1988625401
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,20 @@
using Cysharp.Threading.Tasks.Triggers; using System;
using JetBrains.Annotations;
using System;
using System.Drawing;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using TMPro; using TMPro;
using UdonSharp; using UdonSharp;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.UdonNetworkCalling; using VRC.SDK3.UdonNetworkCalling;
using VRC.SDKBase; using VRC.SDKBase;
using VRC.Udon.ClientBindings.Interfaces;
using VRC.Udon.Common; using VRC.Udon.Common;
namespace Marro.PacManUdon namespace Marro.PacManUdon
{ {
public enum NetworkEventType public enum NetworkEventType
{ {
FullSync = 0, FullSyncForced = 0,
PacManTurn = 1, FullSync = 1,
PacManTurn = 2,
} }
public class NetworkManager : UdonSharpBehaviour public class NetworkManager : UdonSharpBehaviour
@@ -45,9 +40,28 @@ namespace Marro.PacManUdon
// [7]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state. // [7]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state.
// [+]: Event-specific data. // [+]: Event-specific data.
#region Settings
/// <summary>
/// Objects which are controlled by this <see cref="NetworkManager"/>.
/// </summary>
[SerializeField] private SyncedObject[] syncedObjects;
/// <summary>
/// The delay at which the receiving side replays events.
/// </summary>
[SerializeField] private float delay = 1f;
/// <summary>
/// The maximum amount of times a message is sent.
/// </summary>
[SerializeField] private int maxEventSendTries = 3;
/// <summary>
/// How long to wait since last message to send next ping.
/// </summary>
[SerializeField] private float pingDelay = 0.3f;
#endregion
#region Constants #region Constants
/// <summary> /// <summary>
/// The maximum size of the buffer in bytes. /// The maximum amount of events in the buffer.
/// </summary> /// </summary>
private const int BufferMaxTotalEvents = 255; private const int BufferMaxTotalEvents = 255;
@@ -71,19 +85,9 @@ namespace Marro.PacManUdon
/// The total length of the header of an event, in bytes. /// The total length of the header of an event, in bytes.
/// </summary> /// </summary>
private const ushort HeaderLength = 8; private const ushort HeaderLength = 8;
/// <summary>
/// The delay at which the receiving side replays events.
/// </summary>
private const float Delay = 1f;
#endregion #endregion
#region Private attributes #region Private attributes
/// <summary>
/// Objects which are controlled by this <see cref="NetworkManager"/>.
/// </summary>
[SerializeField] private SyncedObject[] syncedObjects;
/// <summary> /// <summary>
/// Offset from system time to network time, including delay. /// Offset from system time to network time, including delay.
/// </summary> /// </summary>
@@ -98,33 +102,53 @@ namespace Marro.PacManUdon
/// </summary> /// </summary>
private float nextEventTime; private float nextEventTime;
/// <summary>
/// The timestamp of the most recent event created or received.
/// </summary>
private float lastEventTimestamp;
/// <summary>
/// The message id of the most recent event created or received.
/// </summary>
private byte lastEventId;
/// <summary> /// <summary>
/// Amounot of retries in a row without a successful sync. /// Amounot of retries in a row without a successful sync.
/// </summary> /// </summary>
private int retriesWithoutSuccess; private int retriesWithoutSuccess;
/// <summary> /// <summary>
/// True if there is a full sync in the queue and we are not currently synced. /// For receiver: True if there's a full sync in the queue and we are not synced, otherwise false.
/// For transmitter: True if there's a full sync in the queue which has not yet been transmitted, otherwise false.
/// </summary> /// </summary>
private bool fullSyncInQueue; private bool hasFullSyncReady;
/// <summary>
/// True if serialization has been requestsed
/// </summary>
private bool serializationRequested;
/// <summary> /// <summary>
/// Main buffer of data to be transmitted or processed /// Queue of events to be transmitted or processed.
/// </summary> /// </summary>
private byte[][] buffer; private byte[][] eventsQueue;
/// <summary> /// <summary>
/// Index of <see cref="buffer"/>. /// Index of <see cref="eventsQueue"/>.
/// </summary> /// </summary>
private int bufferIndex; private int eventsQueueIndex;
/// <summary>
/// The value of <see cref="eventsQueueIndex"/> at the last transmission.
/// </summary>
private int eventsQueueIndexAtLastTransmission;
/// <summary>
/// Counts of new events at recent transmissions.
/// </summary>
private int[] eventTransmissionHistory;
/// <summary>
/// Index of <see cref="eventTransmissionHistoryIndex"/>.
/// </summary>
private int eventTransmissionHistoryIndex;
/// <summary>
/// Time of last event transmission.
/// </summary>
private float lastEventTransmissionTime;
/// <summary>
/// The message id of the most recent event created or received.
/// </summary>
private byte lastEventId;
/// <summary> /// <summary>
/// Data which is currently available on the network. /// Data which is currently available on the network.
@@ -184,13 +208,11 @@ namespace Marro.PacManUdon
SetOwner(Networking.IsOwner(gameObject)); SetOwner(Networking.IsOwner(gameObject));
buffer = new byte[BufferMaxTotalEvents][]; ClearBuffer();
bufferIndex = 0;
Synced = IsOwner; // Owner is always synced Synced = IsOwner; // Owner is always synced
retriesWithoutSuccess = 0; retriesWithoutSuccess = 0;
lastEventTimestamp = 0; hasFullSyncReady = false;
lastEventId = 0;
fullSyncInQueue = false;
offsetTime = Time.fixedTime; offsetTime = Time.fixedTime;
internalTime = 0; internalTime = 0;
@@ -207,10 +229,16 @@ namespace Marro.PacManUdon
// Fetch the current time // Fetch the current time
UpdateInternalTime(); UpdateInternalTime();
// If able and needed, process received events if (Ready)
if (Ready && !IsOwner)
{ {
ProgressEventTime(); if (IsOwner)
{
ProgressPingTime(); // See if we need to send a ping
}
else
{
ProgressEventTime(); // See if there's events that need to be replayed
}
} }
// Forwards simulated time at the FixedUpdate pace // Forwards simulated time at the FixedUpdate pace
@@ -259,7 +287,7 @@ namespace Marro.PacManUdon
} }
else else
{ {
SendEvent(NetworkEventType.FullSync); SendEvent(NetworkEventType.FullSyncForced);
} }
} }
@@ -291,7 +319,7 @@ namespace Marro.PacManUdon
var timestamp = SyncedTime; var timestamp = SyncedTime;
var eventId = GetNextEventId(lastEventId); var eventId = GetNextEventId(lastEventId);
InitializeEvent(eventType, timestamp, eventId, BufferMaxTotalEvents, out byte[][] data, out var index); InitializeEvent(eventType, timestamp, eventId, BufferMaxTotalEvents, out byte[][] data, out var index);
foreach (var obj in syncedObjects) foreach (var obj in syncedObjects)
@@ -299,8 +327,6 @@ namespace Marro.PacManUdon
obj.AppendSyncedData(data, ref index, eventType); obj.AppendSyncedData(data, ref index, eventType);
} }
var oldIndex = this.bufferIndex;
var result = Flatten(data, 0, index); var result = Flatten(data, 0, index);
// Validate and fill in event size // Validate and fill in event size
@@ -316,13 +342,17 @@ namespace Marro.PacManUdon
var eventSizeBytes = BitConverter.GetBytes((ushort)eventSize); var eventSizeBytes = BitConverter.GetBytes((ushort)eventSize);
Array.Copy(eventSizeBytes, 0, result, HeaderEventSizeIndex, eventSizeBytes.Length); Array.Copy(eventSizeBytes, 0, result, HeaderEventSizeIndex, eventSizeBytes.Length);
if (IsFullSync(eventType))
{
hasFullSyncReady = true;
}
QueueEventInBuffer(result); QueueEventInBuffer(result);
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Prepared event with {eventSize} bytes and timestamp {timestamp} for serialization, index went from {oldIndex} to {this.bufferIndex}"); Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Prepared event with {eventSize} bytes and timestamp {timestamp} for serialization, index is now {this.eventsQueueIndex}");
RequestSerialization(); RequestSerializationForEvents();
lastEventTimestamp = timestamp;
lastEventId = eventId; lastEventId = eventId;
retriesWithoutSuccess = 0; // We had success! retriesWithoutSuccess = 0; // We had success!
@@ -346,6 +376,12 @@ namespace Marro.PacManUdon
index = 1; index = 1;
} }
private void RequestSerializationForEvents()
{
RequestSerialization();
serializationRequested = true;
}
[NetworkCallable] [NetworkCallable]
public void RequestEventReceived(NetworkEventType eventType) public void RequestEventReceived(NetworkEventType eventType)
{ {
@@ -354,8 +390,76 @@ namespace Marro.PacManUdon
return; return;
} }
if (IsFullSync(eventType) && hasFullSyncReady)
{
return; // Don't send another full sync if we're already preparing to send one
}
if (eventType == NetworkEventType.FullSyncForced)
{
SendEvent(NetworkEventType.FullSync); // Remote is not allowed to request a forced full sync
}
SendEvent(eventType); SendEvent(eventType);
} }
private void ProgressPingTime()
{
if (eventsQueueIndex > 0
&& internalTime - lastEventTransmissionTime >= pingDelay)
{
RequestSerializationForEvents();
}
}
public override void OnPreSerialization()
{
if (!Ready || !IsOwner || !serializationRequested || eventsQueue == null || eventsQueueIndex == 0)
{
return;
}
networkedData = Flatten(eventsQueue, 0, eventsQueueIndex);
eventTransmissionHistory[eventTransmissionHistoryIndex] = eventsQueueIndex - eventsQueueIndexAtLastTransmission;
eventTransmissionHistoryIndex += 1;
if (eventTransmissionHistoryIndex >= eventTransmissionHistory.Length)
{
eventTransmissionHistoryIndex = 0;
}
serializationRequested = false;
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Serializing {eventsQueueIndex} event(s), eventTransmissionHistory is now {ArrayToString(eventTransmissionHistory)} with index {eventTransmissionHistoryIndex}");
}
public override void OnPostSerialization(SerializationResult result)
{
if (!Ready || !IsOwner || networkedData.Length == 0)
{
return;
}
if (!result.success)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Serialization failed! Tried to send {result.byteCount} bytes.");
return;
}
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Serialized with {networkedData.Length} bytes.\nBytes sent:\n{ArrayToString(networkedData)}");
// Remove data from the buffer that no longer needs to be (re)transmitted
DequeueEventsFromBuffer(eventTransmissionHistory[eventTransmissionHistoryIndex]);
eventTransmissionHistory[eventTransmissionHistoryIndex] = 0; // Prevent trying to remove the same events again
eventsQueueIndexAtLastTransmission = eventsQueueIndex;
networkedData = new byte[0];
// If there was a full sync in the queue, it has now been transmitted at least once
hasFullSyncReady = false;
lastEventTransmissionTime = internalTime;
}
#endregion #endregion
#region Receiver #region Receiver
@@ -382,20 +486,13 @@ namespace Marro.PacManUdon
return; // Nothing to store return; // Nothing to store
} }
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received {networkedData.Length} bytes!\nBytes received:\n{BytesToString(networkedData)}"); Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received {networkedData.Length} bytes!\nBytes received:\n{ArrayToString(networkedData)}");
var length = networkedData.Length; var length = networkedData.Length;
int index = 0; int index = 0;
int eventSize = 0; // Store event size here so we can increment the index no matter how we increment the loop int eventSize = 0; // Store event size here so we can increment the index no matter how we increment the loop
while (true) while ((index += eventSize) < length)
{ {
index += eventSize;
if (index >= length)
{
break;
}
if (length - index < HeaderLength) if (length - index < HeaderLength)
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(StoreIncomingData)}: Remaining data in networkedData is not long enough to form a complete event! remaining: {length - index}."); Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(StoreIncomingData)}: Remaining data in networkedData is not long enough to form a complete event! remaining: {length - index}.");
@@ -421,38 +518,41 @@ namespace Marro.PacManUdon
var @event = GetArrayPart(networkedData, index, eventSize); var @event = GetArrayPart(networkedData, index, eventSize);
var eventType = GetEventTypeFromHeader(@event);
var timestamp = GetTimestampFromHeader(@event); var timestamp = GetTimestampFromHeader(@event);
var eventId = GetEventIdFromHeader(@event); var eventId = GetEventIdFromHeader(@event);
var eventType = GetEventTypeFromHeader(@event);
if (Synced || fullSyncInQueue) if (eventType != NetworkEventType.FullSyncForced && (Synced || hasFullSyncReady))
{ {
if (timestamp == lastEventTimestamp // Check if event id is sequential
&& eventId == lastEventId) if (eventId != GetNextEventId(lastEventId))
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Duplicate message of type {eventType}, timestamp: {timestamp}, messageId: {eventId}."); if (index + eventSize >= length // If this is the last event of the batch
&& eventId != lastEventId) // Unless the eventId is the same, then this is probably just a duplicate
{
// The last event of the batch has to be synced up with our current event id count, else we've missed an event
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) EventIds were not sequential! Did we miss an event? Timestamp: {timestamp}, eventId: {eventId}, lastEventId: {lastEventId}.");
HandleError(false);
return;
}
// We've likely already processed this event, ignore it
continue; continue;
} }
if (eventId != GetNextEventId(lastEventId))
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) EventIds were not sequential! Did we miss a serialization? Timestamp: {timestamp}, eventId: {eventId}, lastEventId: {lastEventId}.");
HandleError(false);
return;
}
QueueEventInBuffer(@event); QueueEventInBuffer(@event);
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)} Queued event with id {eventId}");
} }
else else
{ {
// If we're not yet synced, we only care about full sync events. // If we're not yet synced, we only care about full sync events.
if (eventType == NetworkEventType.FullSync) if (IsFullSync(eventType))
{ {
QueueFullSyncInBuffer(@event); // Immediately process full sync QueueFullSyncForReplay(@event); // Immediately process full sync
} }
} }
lastEventTimestamp = timestamp;
lastEventId = eventId; lastEventId = eventId;
} }
@@ -462,19 +562,16 @@ namespace Marro.PacManUdon
} }
} }
private void QueueFullSyncInBuffer(byte[] @event) private void QueueFullSyncForReplay(byte[] @event)
{ {
// Intentionally not doing a buffer size check here, since this is not appended to the buffer
// (and there is no good way to continue if this event is too large)
// Clear buffer and put the full sync into it // Clear buffer and put the full sync into it
ClearBuffer(); ClearBuffer();
QueueEventInBuffer(@event); QueueEventInBuffer(@event);
// Set this event to play after the default delay // Set this event to play after the default delay
nextEventTime = internalTime + Delay; nextEventTime = internalTime + delay;
fullSyncInQueue = true; hasFullSyncReady = true;
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Queued full sync in buffer, should execute at {nextEventTime}."); Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Queued full sync in buffer, should execute at {nextEventTime}.");
} }
@@ -483,22 +580,30 @@ namespace Marro.PacManUdon
{ {
IsEventUpdate = true; IsEventUpdate = true;
while (bufferIndex != 0 && nextEventTime <= internalTime) while (eventsQueueIndex > 0 && nextEventTime <= internalTime)
{ {
PerformEvent(buffer[0]); var success = PerformEvent(eventsQueue[0]);
if (!success)
{
return;
}
DequeueEventsFromBuffer(1); DequeueEventsFromBuffer(1);
UpdateNextEventTime(); UpdateNextEventTime();
} }
} }
private void PerformEvent(byte[] @event) private bool PerformEvent(byte[] @event)
{ {
var timestamp = GetTimestampFromHeader(@event); var timestamp = GetTimestampFromHeader(@event);
var eventType = GetEventTypeFromHeader(@event); var eventType = GetEventTypeFromHeader(@event);
if (eventType == NetworkEventType.FullSync) var isFullSync = IsFullSync(eventType);
if (!Synced || eventType == NetworkEventType.FullSyncForced)
{ {
SyncToTimestamp(timestamp, GetEventIdFromHeader(@event)); SyncToTimestamp(timestamp);
} }
var index = (int)HeaderLength; // Skip header var index = (int)HeaderLength; // Skip header
@@ -518,7 +623,7 @@ namespace Marro.PacManUdon
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Malformed data reported by {obj.name} during event type {eventType}!"); Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Malformed data reported by {obj.name} during event type {eventType}!");
HandleError(true); HandleError(true);
return; return false;
} }
} }
@@ -527,49 +632,69 @@ namespace Marro.PacManUdon
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Amount of data read does not match event size! Expected {eventSize}, read {index}."); Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Amount of data read does not match event size! Expected {eventSize}, read {index}.");
HandleError(true); HandleError(true);
return; return false;
} }
if (!Synced && eventType == NetworkEventType.FullSync) if (!Synced && isFullSync)
{ {
fullSyncInQueue = false; hasFullSyncReady = false;
Synced = true; Synced = true;
} }
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Processed incoming event! Total {index} bytes."); Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Processed incoming event! Total {index} bytes.");
retriesWithoutSuccess = 0; // We had success! retriesWithoutSuccess = 0; // We had success!
return true;
}
public override void OnDeserialization()
{
if (!Ready || IsOwner)
{
return;
}
StoreIncomingData();
} }
#endregion #endregion
#region Buffer #region Buffer
private void ClearBuffer() private void ClearBuffer()
{ {
buffer = new byte[BufferMaxTotalEvents][]; eventsQueue = new byte[BufferMaxTotalEvents][];
bufferIndex = 0; eventsQueueIndex = 0;
eventTransmissionHistory = new int[maxEventSendTries];
eventTransmissionHistoryIndex = 0;
eventsQueueIndexAtLastTransmission = 0;
lastEventId = 0;
} }
private void DequeueEventsFromBuffer(int eventCount) private void DequeueEventsFromBuffer(int eventCount)
{ {
var oldBuffer = buffer; if (eventCount > eventsQueueIndex) // Never remove more events than are in the queue
bufferIndex -= eventCount; {
buffer = new byte[BufferMaxTotalEvents][]; eventCount = eventsQueueIndex;
Array.Copy(oldBuffer, eventCount, buffer, 0, bufferIndex); }
var oldBuffer = eventsQueue;
eventsQueueIndex -= eventCount;
eventsQueue = new byte[BufferMaxTotalEvents][];
Array.Copy(oldBuffer, eventCount, eventsQueue, 0, eventsQueueIndex);
} }
private bool QueueEventInBuffer(byte[] @event) private bool QueueEventInBuffer(byte[] @event)
{ {
if (bufferIndex >= BufferMaxTotalEvents) if (eventsQueueIndex >= BufferMaxTotalEvents)
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer not large enough to store event! Maximum event count: {BufferMaxTotalEvents}."); Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer not large enough to store event! Maximum event count: {BufferMaxTotalEvents}.");
HandleError(true); HandleError(true);
return false; return false;
} }
buffer[bufferIndex++] = @event; eventsQueue[eventsQueueIndex++] = @event;
return true; return true;
} }
#endregion #endregion
#region Time #region Time
@@ -586,7 +711,7 @@ namespace Marro.PacManUdon
SyncedTime = newTime; SyncedTime = newTime;
} }
private void SyncToTimestamp(float timestamp, byte eventId) private void SyncToTimestamp(float timestamp)
{ {
var oldOffset = offsetTime; var oldOffset = offsetTime;
offsetTime = Time.fixedTime - timestamp; offsetTime = Time.fixedTime - timestamp;
@@ -596,30 +721,33 @@ namespace Marro.PacManUdon
SyncedTime -= delta; SyncedTime -= delta;
nextEventTime -= delta; nextEventTime -= delta;
lastEventTimestamp = timestamp;
lastEventId = eventId;
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Synced to timestamp {timestamp}, current time is {Time.fixedTime}, offsetTime is now {offsetTime}, internalTime is now {internalTime}, SyncedTime is now {SyncedTime}, nextEventTime is now {nextEventTime}"); Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Synced to timestamp {timestamp}, current time is {Time.fixedTime}, offsetTime is now {offsetTime}, internalTime is now {internalTime}, SyncedTime is now {SyncedTime}, nextEventTime is now {nextEventTime}");
} }
private void UpdateNextEventTime() private void UpdateNextEventTime()
{ {
if (bufferIndex == 0) if (eventsQueueIndex == 0)
{ {
return; return;
} }
var nextEventTime = GetTimestampFromHeader(buffer[0]); var nextEventTime = GetTimestampFromHeader(eventsQueue[0]);
if (nextEventTime >= this.nextEventTime)
{ if (nextEventTime < this.nextEventTime)
this.nextEventTime = nextEventTime;
}
else
{ {
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) New event is earlier than previous event!"); Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) New event is earlier than previous event!");
HandleError(true); HandleError(true);
return; return;
} }
if (nextEventTime < SyncedTime)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) New event timestamp is earlier than our current synced time by {SyncedTime - nextEventTime} seconds! nextEventTime: {nextEventTime} SyncedTime: {SyncedTime}, internalTime: {internalTime}");
HandleError(true);
return;
}
this.nextEventTime = nextEventTime;
} }
#endregion #endregion
@@ -636,6 +764,9 @@ namespace Marro.PacManUdon
return currentEventId; return currentEventId;
} }
private static bool IsFullSync(NetworkEventType eventType)
=> eventType == NetworkEventType.FullSync || eventType == NetworkEventType.FullSyncForced;
private static ushort GetEventSizeFromHeader(byte[] @event, int eventIndex = 0) private static ushort GetEventSizeFromHeader(byte[] @event, int eventIndex = 0)
=> BitConverter.ToUInt16(@event, eventIndex + HeaderEventSizeIndex); => BitConverter.ToUInt16(@event, eventIndex + HeaderEventSizeIndex);
@@ -657,79 +788,36 @@ namespace Marro.PacManUdon
return; return;
} }
SetOwner(newOwner == Networking.LocalPlayer); bool newOwnerIsLocalPlayer = newOwner == Networking.LocalPlayer;
} SetOwner(newOwnerIsLocalPlayer);
private int indexAtLastSerialization = 0; if (newOwnerIsLocalPlayer)
public override void OnPreSerialization()
{
if (!Ready)
{ {
return; SendEvent(NetworkEventType.FullSyncForced);
} }
if (IsOwner)
{
if (buffer == null || bufferIndex == 0)
{
return;
}
networkedData = Flatten(buffer, 0, bufferIndex);
indexAtLastSerialization = bufferIndex;
}
else
{
networkedData = new byte[0]; // Prevent exception loop in VRChat SDK
}
}
public override void OnPostSerialization(SerializationResult result)
{
if (!Ready)
{
return;
}
if (!result.success)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Serialization failed! Tried to send {result.byteCount} bytes.");
return;
}
if (!IsOwner || networkedData.Length == 0)
{
return;
}
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Serialized with {networkedData.Length} bytes!\nBytes sent:\n{BytesToString(networkedData)}");
// Remove all transferred data from the buffer, leaving data that came in after serialization
DequeueEventsFromBuffer(indexAtLastSerialization);
networkedData = new byte[0];
}
public override void OnDeserialization()
{
if (!Ready || IsOwner)
{
return;
}
StoreIncomingData();
} }
#endregion #endregion
#region Utils #region Utils
public string BytesToString(byte[] bytes) public string ArrayToString(byte[] bytes)
{ {
var sb = new StringBuilder("new byte[] { "); var sb = new StringBuilder("[ ");
foreach (var b in bytes) foreach (var b in bytes)
{ {
sb.Append(b + ", "); sb.Append(b + " ");
} }
sb.Append("}"); sb.Append("]");
return sb.ToString();
}
public string ArrayToString(int[] bytes)
{
var sb = new StringBuilder("[ ");
foreach (var b in bytes)
{
sb.Append(b + " ");
}
sb.Append("]");
return sb.ToString(); return sb.ToString();
} }
@@ -774,7 +862,7 @@ namespace Marro.PacManUdon
#region Debug #region Debug
public void SimulateSyncToTimestamp(float timestamp) public void SimulateSyncToTimestamp(float timestamp)
{ {
SyncToTimestamp(timestamp, 0); SyncToTimestamp(timestamp);
} }
public void WriteDebugOutput(TMP_InputField debugOutput) public void WriteDebugOutput(TMP_InputField debugOutput)
@@ -783,12 +871,15 @@ namespace Marro.PacManUdon
$"IsOwner: {IsOwner}\n" + $"IsOwner: {IsOwner}\n" +
$"Ready: {Ready}\n" + $"Ready: {Ready}\n" +
$"Synced: {Synced}\n" + $"Synced: {Synced}\n" +
$"fullSyncInQueue: {fullSyncInQueue}\n" + $"hasFullSyncReady: {hasFullSyncReady}\n" +
$"lastEventId: {lastEventId}" +
$"Time.fixedTime: {Time.fixedTime}\n" + $"Time.fixedTime: {Time.fixedTime}\n" +
$"offsetTime: {offsetTime}\n" + $"offsetTime: {offsetTime}\n" +
$"internalTime: {internalTime}\n" + $"internalTime: {internalTime}\n" +
$"SyncedTime: {SyncedTime}\n" + $"SyncedTime: {SyncedTime}\n" +
$"Dt: {Dt}\n" + $"Dt: {Dt}\n" +
$"BufferIndex: {eventsQueueIndex}\n" +
$"BufferIndexHistory: {ArrayToString(eventTransmissionHistory)}\n" +
$"\n"; $"\n";
} }

View File

@@ -132,7 +132,8 @@ public class TestBall : SyncedObject
public override void AppendSyncedData(byte[][] data, ref int index, NetworkEventType eventType) public override void AppendSyncedData(byte[][] data, ref int index, NetworkEventType eventType)
{ {
if (eventType == 0) if (eventType == NetworkEventType.FullSync
|| eventType == NetworkEventType.FullSyncForced)
{ {
Debug.Log($"({nameof(TestBall)}) Sending sync data at progress {GetProgress()} and amountUp {amountUp}."); Debug.Log($"({nameof(TestBall)}) Sending sync data at progress {GetProgress()} and amountUp {amountUp}.");
data[index++] = BitConverter.GetBytes(amountUp); data[index++] = BitConverter.GetBytes(amountUp);
@@ -142,18 +143,19 @@ public class TestBall : SyncedObject
public override bool SetSyncedData(byte[] data, ref int index, NetworkEventType eventType) public override bool SetSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{ {
if (eventType == 0) if (eventType == NetworkEventType.FullSync
|| eventType == NetworkEventType.FullSyncForced)
{ {
amountUp = BitConverter.ToSingle(data, index); amountUp = BitConverter.ToSingle(data, index);
SetProgress(BitConverter.ToSingle(data, index + 4)); SetProgress(BitConverter.ToSingle(data, index + 4));
Debug.Log($"({nameof(TestBall)}) Received sync event, synced to progress {GetProgress()} and amountUp {amountUp}."); //Debug.Log($"({nameof(TestBall)}) Received sync event, synced to progress {GetProgress()} and amountUp {amountUp}.");
index += 8; index += 8;
} }
else else
{ {
Debug.Log($"({nameof(TestBall)}) Received up event, jumped up at {GetProgress()} from {amountUp}."); //Debug.Log($"({nameof(TestBall)}) Received up event, jumped up at {GetProgress()} from {amountUp}.");
Jump(); Jump();
Debug.Log($"({nameof(TestBall)}) Received up event, jumped up at {GetProgress()} to {amountUp}."); //Debug.Log($"({nameof(TestBall)}) Received up event, jumped up at {GetProgress()} to {amountUp}.");
} }
return true; return true;

View File

@@ -49,7 +49,7 @@ public class TestBallManager : UdonSharpBehaviour
testBall.UpButtonPressed(); testBall.UpButtonPressed();
} }
networkManager.SendEvent((NetworkEventType)1); networkManager.SendEvent(NetworkEventType.PacManTurn);
} }
public void SyncButtonPressed() public void SyncButtonPressed()