Working on networkmanager
This commit is contained in:
@@ -37,38 +37,146 @@ namespace Marro.PacManUdon
|
||||
// [6]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state.
|
||||
// [7+]: Event-specific data
|
||||
|
||||
#region Constants
|
||||
/// <summary>
|
||||
/// The maximum size of the buffer in bytes.
|
||||
/// </summary>
|
||||
private const int BufferMaxSizeBytes = 10000;
|
||||
/// <summary>
|
||||
/// How many bytes to increase the buffer size by if the current one is not enough.
|
||||
/// </summary>
|
||||
private const int BufferIncrementSizeBytes = 1000;
|
||||
|
||||
[SerializeField] private SyncedObject[] syncedObjects;
|
||||
|
||||
[SerializeField] private Animator DebugImageToIndicateOwner;
|
||||
|
||||
private bool isOwner;
|
||||
private bool isSynced;
|
||||
|
||||
private long startTimeTicks = DateTime.UtcNow.Ticks; // Initialize to prevent errors
|
||||
private long nextEventTimeTicks;
|
||||
|
||||
private int retriesWithoutSuccess;
|
||||
|
||||
// Main buffer of events
|
||||
private byte[] buffer;
|
||||
private int index;
|
||||
|
||||
/// <summary>
|
||||
/// The index in an event where the event size is stored.
|
||||
/// </summary>
|
||||
private const ushort HeaderEventSizeIndex = 0;
|
||||
/// <summary>
|
||||
/// The index in an event where the timestamp is stored.
|
||||
/// </summary>
|
||||
private const ushort HeaderTimestampIndex = 2;
|
||||
/// <summary>
|
||||
/// The index in an event where the event type is stored.
|
||||
/// </summary>
|
||||
private const ushort HeaderEventTypeIndex = 6;
|
||||
/// <summary>
|
||||
/// The total length of the header of an event, in bytes.
|
||||
/// </summary>
|
||||
private const ushort HeaderLength = 7;
|
||||
|
||||
private const int Delay = 250;
|
||||
/// <summary>
|
||||
/// The multiplier from Unity time to a timestamp.
|
||||
/// </summary>
|
||||
private const int TimestampMultiplier = 1000;
|
||||
/// <summary>
|
||||
/// The zero value of a timestamp. Anything below this value is negative.
|
||||
/// </summary>
|
||||
private const uint TimestampZeroValue = 1000;
|
||||
/// <summary>
|
||||
/// The delay at which the receiving side replays events.
|
||||
/// </summary>
|
||||
private const float Delay = 0.250f;
|
||||
#endregion
|
||||
|
||||
#region Private attributes
|
||||
/// <summary>
|
||||
/// Objects which are controlled by this <see cref="NetworkManager"/>.
|
||||
/// </summary>
|
||||
[SerializeField] private SyncedObject[] syncedObjects;
|
||||
|
||||
/// <summary>
|
||||
/// An animator which visualizes whether the current perspective is the owner.
|
||||
/// </summary>
|
||||
[SerializeField] private Animator DebugImageToIndicateOwner;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current perspective is the transmitting side.
|
||||
/// </summary>
|
||||
private bool isOwner;
|
||||
/// <summary>
|
||||
/// Whether the current perspective is synced with the owner. (Always true if current perspective is owner.)
|
||||
/// </summary>
|
||||
private bool isSynced;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Offset from system time to network time, including delay.
|
||||
/// </summary>
|
||||
private float offsetTime;
|
||||
/// <summary>
|
||||
/// Time since last full sync, captured when this FixedUpdate started, with network delay applied.
|
||||
/// </summary>
|
||||
private float internalTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time at which next received event occured.
|
||||
/// </summary>
|
||||
private float nextEventTime;
|
||||
|
||||
/// <summary>
|
||||
/// Amounot of retries in a row without a successful sync.
|
||||
/// </summary>
|
||||
private int retriesWithoutSuccess;
|
||||
|
||||
/// <summary>
|
||||
/// Main buffer of data to be transmitted or processed
|
||||
/// </summary>
|
||||
private byte[] buffer;
|
||||
/// <summary>
|
||||
/// Index of <see cref="buffer"/>.
|
||||
/// </summary>
|
||||
private int bufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Data which is currently available on the network.
|
||||
/// </summary>
|
||||
[UdonSynced] private byte[] networkedData = new byte[0];
|
||||
#endregion
|
||||
|
||||
public long CurrentTimeTicks { get; private set; }
|
||||
#region Public fields
|
||||
/// <summary>
|
||||
/// Whether this <see cref="NetworkManager"/> is ready to transmit or receive data.
|
||||
/// If false, networking is disabled and this <see cref="NetworkManager"/> acts as a pass-through.
|
||||
/// </summary>
|
||||
public bool Ready { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The time since last full sync which is currently being simulated.
|
||||
/// </summary>
|
||||
public float SyncedTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time since the last simulation, in seconds.
|
||||
/// </summary>
|
||||
public float Dt { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the current simulation to prepare for applying a network event?
|
||||
/// True = Yes, This update is preparing for a network update.
|
||||
/// False = No, this update is after the network update or there was no
|
||||
/// </summary>
|
||||
public bool IsEventUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the local user owner?
|
||||
/// </summary>
|
||||
public bool IsOwner => isOwner;
|
||||
#endregion
|
||||
|
||||
#region General
|
||||
public void Awake()
|
||||
{
|
||||
offsetTime = Time.fixedTime;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (Ready)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Already initialized, rejecting repeat call to {nameof(Initialize)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: NetworkManager only supports little endian! Network sync will not be possible.");
|
||||
@@ -77,328 +185,49 @@ namespace Marro.PacManUdon
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = new byte[BufferIncrementSizeBytes];
|
||||
index = 0;
|
||||
startTimeTicks = DateTime.UtcNow.Ticks;
|
||||
isSynced = false;
|
||||
retriesWithoutSuccess = 0;
|
||||
SetOwner(Networking.IsOwner(gameObject));
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Initialized, startTimeTicks: {startTimeTicks}");
|
||||
buffer = new byte[BufferIncrementSizeBytes];
|
||||
bufferIndex = 0;
|
||||
isSynced = isOwner; // Owner is always synced
|
||||
retriesWithoutSuccess = 0;
|
||||
|
||||
offsetTime = Time.fixedTime;
|
||||
internalTime = 0;
|
||||
SyncedTime = 0;
|
||||
Dt = Time.fixedDeltaTime;
|
||||
|
||||
Ready = true;
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Initialized, time offset: {offsetTime}");
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void FixedUpdate()
|
||||
{
|
||||
UpdateInternalTime();
|
||||
|
||||
if (!isOwner)
|
||||
{
|
||||
ProgressReplayTime();
|
||||
ProgressEventTime();
|
||||
}
|
||||
|
||||
UpdateTime(DateTime.UtcNow.Ticks);
|
||||
PerformFixedSyncedUpdate();
|
||||
}
|
||||
|
||||
public void SendEvent(NetworkEventType eventType)
|
||||
public void UpdateInternalTime()
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Attempted {nameof(SendEvent)} while not the owner!");
|
||||
return;
|
||||
}
|
||||
internalTime = Time.fixedTime - offsetTime;
|
||||
}
|
||||
|
||||
var eventTime = GetTimestamp(CurrentTimeTicks);
|
||||
|
||||
InitializeEvent(eventType, eventTime, BufferMaxSizeBytes, out byte[][] data, out var index);
|
||||
private void PerformFixedSyncedUpdate()
|
||||
{
|
||||
IsEventUpdate = false;
|
||||
ProgressSyncedTime(internalTime);
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
obj.AppendSyncedData(data, ref index, eventType);
|
||||
obj.SyncedUpdate();
|
||||
}
|
||||
|
||||
// Get event size, skipping over the event size which is not yet included
|
||||
ushort eventSize = 0;
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
eventSize += (ushort)data[i].Length;
|
||||
}
|
||||
|
||||
if (!EnsureSpaceToStoreEvent(eventSize))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data[0] = BitConverter.GetBytes(eventSize);
|
||||
|
||||
var oldIndex = this.index;
|
||||
|
||||
FlattenAndCopy(data, index, buffer, ref this.index);
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Prepared event with {eventSize} bytes and timestamp {eventTime} for serialization, index went from {oldIndex} to {this.index}");
|
||||
|
||||
RequestSerialization();
|
||||
|
||||
retriesWithoutSuccess = 0; // We had success!
|
||||
}
|
||||
|
||||
public void RequestEvent(NetworkEventType eventType)
|
||||
{
|
||||
if (isOwner)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Attempted {nameof(RequestEvent)} while we are the owner!");
|
||||
return;
|
||||
}
|
||||
|
||||
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "RequestEventReceived", eventType);
|
||||
}
|
||||
|
||||
[NetworkCallable]
|
||||
public void RequestEventReceived(NetworkEventType eventType)
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Attempted {nameof(RequestEventReceived)} while we are not the owner!");
|
||||
return;
|
||||
}
|
||||
|
||||
SendEvent(eventType);
|
||||
}
|
||||
|
||||
private void ProcessIncomingData()
|
||||
{
|
||||
if (networkedData.Length == 0)
|
||||
{
|
||||
return; // Nothing to process
|
||||
}
|
||||
|
||||
var length = networkedData.Length;
|
||||
int index = 0;
|
||||
while (index < length)
|
||||
{
|
||||
if (length - index < HeaderLength)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(ProcessIncomingData)}: Remaining data in networkedData is not long enough to form a complete event!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
var eventSize = networkedData[index + HeaderEventSizeIndex];
|
||||
var eventType = (NetworkEventType)networkedData[index + HeaderEventTypeIndex];
|
||||
|
||||
if (eventType == NetworkEventType.FullSync)
|
||||
{
|
||||
ProcessIncomingFullSync(index, eventSize); // Immediately process full sync
|
||||
index += eventSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isSynced)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received event of type {eventType} while we are not yet synced to the remote time, ignoring event.");
|
||||
index += eventSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
AppendEventToBuffer(index, eventSize);
|
||||
index += eventSize;
|
||||
}
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received {networkedData.Length} bytes!\nBytes received:\n{BytesToString(networkedData)}");
|
||||
}
|
||||
|
||||
private void ProcessIncomingFullSync(int index, int size)
|
||||
{
|
||||
// Intentionally not doing a buffer size check here, since this is not appending to the buffer
|
||||
// (and there is no good way to continue if event is too large)
|
||||
|
||||
// Clear buffer and copy the full sync into it
|
||||
buffer = new byte[size];
|
||||
Array.Copy(networkedData, index, buffer, 0, size);
|
||||
this.index = size;
|
||||
|
||||
// Sync up to the time in the full sync
|
||||
var timestamp = BitConverter.ToUInt32(networkedData, index + HeaderTimestampIndex);
|
||||
SyncToTimestamp(timestamp);
|
||||
|
||||
// Immediately apply the full sync
|
||||
nextEventTimeTicks = GetTimeTicks(timestamp);
|
||||
isSynced = true;
|
||||
}
|
||||
|
||||
private void AppendEventToBuffer(int index, int size)
|
||||
{
|
||||
if (!EnsureSpaceToStoreEvent(size))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Array.Copy(networkedData, index, buffer, this.index, size);
|
||||
this.index += size;
|
||||
|
||||
UpdateNextEventTime();
|
||||
}
|
||||
|
||||
private void ProgressReplayTime()
|
||||
{
|
||||
while (index != 0 && nextEventTimeTicks <= CurrentTimeTicks)
|
||||
{
|
||||
ProcessIncomingEvent();
|
||||
UpdateNextEventTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNextEventTime()
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nextEventTimeTicks = GetTimeTicks(BitConverter.ToUInt32(buffer, HeaderTimestampIndex));
|
||||
if (nextEventTimeTicks >= this.nextEventTimeTicks)
|
||||
{
|
||||
this.nextEventTimeTicks = nextEventTimeTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) New event is earlier than previous event!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessIncomingEvent()
|
||||
{
|
||||
var eventTimeTicks = GetTimeTicks(BitConverter.ToUInt32(buffer, HeaderTimestampIndex));
|
||||
var eventType = (NetworkEventType)buffer[HeaderEventTypeIndex];
|
||||
var index = (int)HeaderLength; // Skip header
|
||||
|
||||
UpdateTime(eventTimeTicks);
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
obj.FixedUpdate();
|
||||
}
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
var success = obj.SetSyncedData(buffer, ref index, eventType);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Malformed data reported by {obj.name} during event type {eventType}!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > this.index)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer overflow during {nameof(SyncedObject.SetSyncedData)} for {obj.name} in event type {eventType}!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var eventSize = BitConverter.ToUInt16(buffer, HeaderEventSizeIndex);
|
||||
if (index != eventSize)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Amount of data read does not match event size! Expected {eventSize}, read {index}.");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveProcessedDataFromBuffer(index);
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Processed incoming event! Total {index} bytes.");
|
||||
|
||||
retriesWithoutSuccess = 0; // We had success!
|
||||
}
|
||||
|
||||
private void SyncToTimestamp(uint newTime)
|
||||
{
|
||||
var timeToSyncTo = newTime - Delay;
|
||||
startTimeTicks = DateTime.UtcNow.Ticks - timeToSyncTo * TimeSpan.TicksPerMillisecond;
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Synced to time {newTime}, startTimeTicks is now {startTimeTicks}");
|
||||
}
|
||||
|
||||
private bool EnsureSpaceToStoreEvent(int eventSize)
|
||||
{
|
||||
if (index + eventSize <= buffer.Length)
|
||||
{
|
||||
return true; // Enough space!
|
||||
}
|
||||
|
||||
var newBufferSize = ((index + eventSize) / BufferIncrementSizeBytes + 1) * BufferIncrementSizeBytes;
|
||||
|
||||
var success = IncreaseBufferSize(newBufferSize);
|
||||
if (success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer is not large enough to store event!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Too much data in buffer to store event!");
|
||||
}
|
||||
|
||||
HandleError(); // We can store event now that we cleared the buffer.
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IncreaseBufferSize(int newSize)
|
||||
{
|
||||
if (newSize < buffer.Length)
|
||||
{
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Cannot decrease the size of the buffer!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newSize > BufferMaxSizeBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldBuffer = buffer;
|
||||
buffer = new byte[newSize];
|
||||
oldBuffer.CopyTo(buffer, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitializeEvent(NetworkEventType eventType, uint eventTime, int maxSize, out byte[][] data, out int index)
|
||||
{
|
||||
data = new byte[maxSize][];
|
||||
index = 3;
|
||||
|
||||
data[0] = new byte[2]; // Placeholder for event size
|
||||
data[1] = BitConverter.GetBytes(eventTime);
|
||||
data[2] = new byte[] { GameManager.Int32ToByte((int)eventType) };
|
||||
}
|
||||
|
||||
private void FlattenAndCopy(byte[][] data, int length, byte[] target, ref int index)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var values = data[i];
|
||||
Array.Copy(values, 0, target, index, values.Length);
|
||||
index += values.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveProcessedDataFromBuffer(int amountProcessed)
|
||||
{
|
||||
var oldBuffer = buffer;
|
||||
index -= amountProcessed;
|
||||
buffer = new byte[BufferMaxSizeBytes];
|
||||
Array.Copy(oldBuffer, amountProcessed, buffer, 0, index);
|
||||
}
|
||||
|
||||
private void ClearBuffer()
|
||||
{
|
||||
buffer = new byte[BufferMaxSizeBytes];
|
||||
index = 0;
|
||||
}
|
||||
|
||||
private void HandleError()
|
||||
@@ -436,7 +265,323 @@ namespace Marro.PacManUdon
|
||||
DebugImageToIndicateOwner.SetFloat("Color", isOwner ? 1 : 0);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Sender
|
||||
public void SendEvent(NetworkEventType eventType)
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Attempted {nameof(SendEvent)} while not the owner!");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventTime = TimeToTimestamp(SyncedTime);
|
||||
|
||||
InitializeEvent(eventType, eventTime, BufferMaxSizeBytes, out byte[][] data, out var index);
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
obj.AppendSyncedData(data, ref index, eventType);
|
||||
}
|
||||
|
||||
// Get event size, skipping over the event size which is not yet included
|
||||
ushort eventSize = 0;
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
eventSize += (ushort)data[i].Length;
|
||||
}
|
||||
|
||||
if (!EnsureSpaceToStoreEvent(eventSize))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data[0] = BitConverter.GetBytes(eventSize);
|
||||
|
||||
var oldIndex = this.bufferIndex;
|
||||
|
||||
FlattenAndCopy(data, index, buffer, ref this.bufferIndex);
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Prepared event with {eventSize} bytes and timestamp {eventTime} for serialization, index went from {oldIndex} to {this.bufferIndex}");
|
||||
|
||||
RequestSerialization();
|
||||
|
||||
retriesWithoutSuccess = 0; // We had success!
|
||||
}
|
||||
|
||||
private static void InitializeEvent(NetworkEventType eventType, uint eventTime, int maxSize, out byte[][] data, out int index)
|
||||
{
|
||||
data = new byte[maxSize][];
|
||||
index = 3;
|
||||
|
||||
data[0] = new byte[2]; // Placeholder for event size
|
||||
data[1] = BitConverter.GetBytes(eventTime);
|
||||
data[2] = new byte[] { GameManager.Int32ToByte((int)eventType) };
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Receiver
|
||||
public void RequestEvent(NetworkEventType eventType)
|
||||
{
|
||||
if (isOwner)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Attempted {nameof(RequestEvent)} while we are the owner!");
|
||||
return;
|
||||
}
|
||||
|
||||
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "RequestEventReceived", eventType);
|
||||
}
|
||||
|
||||
private void ProcessIncomingData()
|
||||
{
|
||||
if (networkedData.Length == 0)
|
||||
{
|
||||
return; // Nothing to process
|
||||
}
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received {networkedData.Length} bytes!\nBytes received:\n{BytesToString(networkedData)}");
|
||||
|
||||
var length = networkedData.Length;
|
||||
int index = 0;
|
||||
while (index < length)
|
||||
{
|
||||
if (length - index < HeaderLength)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(ProcessIncomingData)}: Remaining data in networkedData is not long enough to form a complete event!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
var eventSize = networkedData[index + HeaderEventSizeIndex];
|
||||
var eventType = (NetworkEventType)networkedData[index + HeaderEventTypeIndex];
|
||||
|
||||
if (eventType == NetworkEventType.FullSync)
|
||||
{
|
||||
ProcessIncomingFullSync(index, eventSize); // Immediately process full sync
|
||||
index += eventSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isSynced)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Received event of type {eventType} while we are not yet synced to the remote time, ignoring event.");
|
||||
index += eventSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
AppendEventToBuffer(index, eventSize);
|
||||
index += eventSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessIncomingFullSync(int index, int size)
|
||||
{
|
||||
// Intentionally not doing a buffer size check here, since this is not appending to the buffer
|
||||
// (and there is no good way to continue if event is too large)
|
||||
|
||||
// Clear buffer and copy the full sync into it
|
||||
buffer = new byte[size];
|
||||
Array.Copy(networkedData, index, buffer, 0, size);
|
||||
this.bufferIndex = size;
|
||||
|
||||
// Sync up to the time in the full sync
|
||||
var timestamp = BitConverter.ToUInt32(networkedData, index + HeaderTimestampIndex);
|
||||
SyncToTimestamp(timestamp);
|
||||
|
||||
// Immediately apply the full sync
|
||||
UpdateNextEventTime();
|
||||
isSynced = true;
|
||||
}
|
||||
|
||||
private void ProgressEventTime()
|
||||
{
|
||||
IsEventUpdate = true;
|
||||
|
||||
while (bufferIndex != 0 && nextEventTime <= internalTime)
|
||||
{
|
||||
ProcessIncomingEvent();
|
||||
UpdateNextEventTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessIncomingEvent()
|
||||
{
|
||||
var eventTime = TimestampToTime(BitConverter.ToUInt32(buffer, HeaderTimestampIndex));
|
||||
var eventType = (NetworkEventType)buffer[HeaderEventTypeIndex];
|
||||
var index = (int)HeaderLength; // Skip header
|
||||
|
||||
ProgressSyncedTime(eventTime);
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
obj.SyncedUpdate();
|
||||
}
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
var success = obj.SetSyncedData(buffer, ref index, eventType);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Malformed data reported by {obj.name} during event type {eventType}!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > this.bufferIndex)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer overflow during {nameof(SyncedObject.SetSyncedData)} for {obj.name} in event type {eventType}!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var eventSize = BitConverter.ToUInt16(buffer, HeaderEventSizeIndex);
|
||||
if (index != eventSize)
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Amount of data read does not match event size! Expected {eventSize}, read {index}.");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveProcessedDataFromBuffer(index);
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Processed incoming event! Total {index} bytes.");
|
||||
|
||||
retriesWithoutSuccess = 0; // We had success!
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Buffer
|
||||
private void ClearBuffer()
|
||||
{
|
||||
buffer = new byte[BufferMaxSizeBytes];
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
private void RemoveProcessedDataFromBuffer(int amountProcessed)
|
||||
{
|
||||
var oldBuffer = buffer;
|
||||
bufferIndex -= amountProcessed;
|
||||
buffer = new byte[BufferMaxSizeBytes];
|
||||
Array.Copy(oldBuffer, amountProcessed, buffer, 0, bufferIndex);
|
||||
}
|
||||
|
||||
private bool IncreaseBufferSize(int newSize)
|
||||
{
|
||||
if (newSize < buffer.Length)
|
||||
{
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Cannot decrease the size of the buffer!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newSize > BufferMaxSizeBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldBuffer = buffer;
|
||||
buffer = new byte[newSize];
|
||||
oldBuffer.CopyTo(buffer, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnsureSpaceToStoreEvent(int eventSize)
|
||||
{
|
||||
if (bufferIndex + eventSize <= buffer.Length)
|
||||
{
|
||||
return true; // Enough space!
|
||||
}
|
||||
|
||||
var newBufferSize = ((bufferIndex + eventSize) / BufferIncrementSizeBytes + 1) * BufferIncrementSizeBytes;
|
||||
|
||||
var success = IncreaseBufferSize(newBufferSize);
|
||||
if (success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bufferIndex == 0)
|
||||
{
|
||||
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Buffer is not large enough to store event!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Too much data in buffer to store event!");
|
||||
}
|
||||
|
||||
HandleError(); // We can store event now that we cleared the buffer.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AppendEventToBuffer(int index, int size)
|
||||
{
|
||||
if (!EnsureSpaceToStoreEvent(size))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Array.Copy(networkedData, index, buffer, this.bufferIndex, size);
|
||||
this.bufferIndex += size;
|
||||
|
||||
UpdateNextEventTime();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Time
|
||||
private void ProgressSyncedTime(float newTime)
|
||||
{
|
||||
//Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) updating SyncedTime from {SyncedTime} to {newTime}");
|
||||
Dt = newTime - SyncedTime;
|
||||
SyncedTime = newTime;
|
||||
}
|
||||
|
||||
private void SyncToTimestamp(uint timestamp)
|
||||
{
|
||||
var oldOffset = offsetTime;
|
||||
var timeToSyncTo = timestamp / (float)TimestampMultiplier - Delay;
|
||||
offsetTime = Time.fixedTime - timeToSyncTo;
|
||||
|
||||
var delta = offsetTime - oldOffset;
|
||||
internalTime = internalTime - delta;
|
||||
SyncedTime = SyncedTime - delta;
|
||||
|
||||
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Synced to timestamp {timestamp}, current time is {Time.fixedTime}, timeToSyncTo is {timeToSyncTo}, offsetTime is now {offsetTime}, internalTime is now {internalTime}, SyncedTime is now {SyncedTime}");
|
||||
}
|
||||
|
||||
private void UpdateNextEventTime()
|
||||
{
|
||||
if (bufferIndex == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nextEventTime = TimestampToTime(BitConverter.ToUInt32(buffer, HeaderTimestampIndex));
|
||||
if (nextEventTime >= this.nextEventTime)
|
||||
{
|
||||
this.nextEventTime = nextEventTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) New event is earlier than previous event!");
|
||||
HandleError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint TimeToTimestamp(float time)
|
||||
{
|
||||
return (uint)((time * TimestampMultiplier) + TimestampZeroValue);
|
||||
}
|
||||
|
||||
public static float TimestampToTime(uint timeStamp)
|
||||
{
|
||||
return (timeStamp - (long)TimestampZeroValue) / (float)TimestampMultiplier; // Use a long here to prevent an underflow
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region VRC events
|
||||
public override void OnOwnershipTransferred(VRCPlayerApi newOwner)
|
||||
{
|
||||
SetOwner(newOwner == Networking.LocalPlayer);
|
||||
@@ -451,8 +596,8 @@ namespace Marro.PacManUdon
|
||||
{
|
||||
if (isOwner)
|
||||
{
|
||||
networkedData = new byte[index];
|
||||
Array.Copy(buffer, networkedData, index);
|
||||
networkedData = new byte[bufferIndex];
|
||||
Array.Copy(buffer, networkedData, bufferIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -487,30 +632,9 @@ namespace Marro.PacManUdon
|
||||
ProcessIncomingData();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void UpdateTime(long timeTicks)
|
||||
{
|
||||
CurrentTimeTicks = timeTicks;
|
||||
|
||||
foreach (var obj in syncedObjects)
|
||||
{
|
||||
obj.Dt = (timeTicks - obj.LastUpdateTicks) / (float)TimeSpan.TicksPerSecond;
|
||||
obj.LastUpdateTicks = timeTicks;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetTimestamp(long timeTicks)
|
||||
{
|
||||
return (uint)((timeTicks - startTimeTicks) / TimeSpan.TicksPerMillisecond);
|
||||
}
|
||||
|
||||
public long GetTimeTicks(uint timeStamp)
|
||||
{
|
||||
return timeStamp * TimeSpan.TicksPerMillisecond + startTimeTicks;
|
||||
}
|
||||
|
||||
public bool IsOwner => isOwner;
|
||||
|
||||
#region Utils
|
||||
public string BytesToString(byte[] bytes)
|
||||
{
|
||||
var sb = new StringBuilder("new byte[] { ");
|
||||
@@ -521,5 +645,23 @@ namespace Marro.PacManUdon
|
||||
sb.Append("}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void FlattenAndCopy(byte[][] data, int length, byte[] target, ref int index)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var values = data[i];
|
||||
Array.Copy(values, 0, target, index, values.Length);
|
||||
index += values.Length;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Debug
|
||||
public void SimulateSyncToTimestamp(uint timestamp)
|
||||
{
|
||||
SyncToTimestamp(timestamp);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user