A lot of older work

This commit is contained in:
2026-06-09 17:45:51 +02:00
parent a8b395b1d3
commit cf39fd5dd9
8 changed files with 3760 additions and 1710 deletions

View File

@@ -3,6 +3,7 @@ using System.Text;
using TMPro;
using UdonSharp;
using UnityEngine;
using UnityEngine.Android;
using VRC.SDK3.UdonNetworkCalling;
using VRC.SDKBase;
using VRC.Udon.Common;
@@ -17,9 +18,13 @@ namespace Marro.PacManUdon
StartGameButtonPressed = 3,
SyncPellets = 4,
GhostUpdate = 5,
TimeSequenceSync = 6,
Pause = 7,
Resume = 8,
Step = 9,
}
public class NetworkManager : UdonSharpBehaviour
public class NetworkManager : SyncedObject
{
// The network manager works by serializing event and state data into a byte array, including a timestamp for each event.
// If user is owner, this data is created and stored in a buffer which is cleared upon transmission.
@@ -42,6 +47,11 @@ namespace Marro.PacManUdon
// [7]: (byte) Type of event. 0 = Full Sync, which is used to sync up from an undefinted state.
// [+]: Event-specific data.
#region External settings
[SerializeField] private SyncedObject[] networkEventSubscribersFlat;
[SerializeField] private int[] networkEventSubscribersFlatSegmentLengths;
#endregion
#region Settings
/// <summary>
/// The root from which this <see cref="NetworkManager"/> will look for <see cref="SyncedObject"/> to control.
@@ -60,9 +70,9 @@ namespace Marro.PacManUdon
/// </summary>
[SerializeField] private float pingDelay = 0.3f;
/// <summary>
/// The rate at which updates occur.
/// The time delta at which updates occur.
/// </summary>
[SerializeField] private float updateRate = 0.0166666667f;
[SerializeField] private float updateDelta = 0.0166666667f;
#endregion
#region Constants
@@ -99,9 +109,19 @@ namespace Marro.PacManUdon
#region Private attributes
/// <summary>
/// Objects which are controlled by this <see cref="NetworkManager"/>.
/// Whether <see cref="Initialize"/> has been called successfully.
/// </summary>
private SyncedObject[] syncedObjects;
private bool initialized = false;
/// <summary>
/// Subscribers to each <see cref="NetworkEventType"/>.
/// </summary>
private SyncedObject[][] networkEventSubscribers;
/// <summary>
/// Subscribers for <see cref="SyncedObject.SyncedUpdate"/>.
/// </summary>
private SyncedObject[] syncedUpdateSubscribers;
/// <summary>
/// Offset from system time to network time, including delay.
@@ -113,7 +133,17 @@ namespace Marro.PacManUdon
private float internalTime;
/// <summary>
/// Time at which the latest update occured
/// True if time is paused
/// </summary>
private bool paused;
/// <summary>
/// True if a step should happen next update
/// </summary>
private bool stepNext;
/// <summary>
/// Time at which the next update should occur.
/// </summary>
private float nextUpdateTime;
/// <summary>
@@ -191,10 +221,23 @@ namespace Marro.PacManUdon
/// </summary>
public bool Ready { get; private set; } = false;
/// <summary>
/// Whether the current perspective is synced with the owner. (Always true if current perspective is owner.)
/// </summary>
public bool Synced { get; private set; } = false;
public bool Synced
{
get => synced;
private set
{
synced = value;
if (DebugImageToIndicateSynced != null)
{
DebugImageToIndicateSynced.SetFloat("Color", value ? 1 : 0);
}
}
}
private bool synced = false;
/// <summary>
/// The time since last full sync which is currently being simulated.
@@ -216,16 +259,36 @@ namespace Marro.PacManUdon
/// <summary>
/// Is the local user owner?
/// </summary>
public bool IsOwner { get; private set; }
public bool IsOwner
{
get => isOwner;
private set
{
isOwner = value;
if (DebugImageToIndicateOwner != null)
{
DebugImageToIndicateOwner.SetFloat("Color", value ? 1 : 0);
}
}
}
private bool isOwner = false;
#endregion
#region General
/// <summary>
/// Initializes the <see cref="NetworkManager"/>. Call <see cref="Reset"/> afterwards to activate networking.
/// </summary>
public void Initialize()
{
if (initialized)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Tried to call {nameof(Initialize)} when already initialized!");
return;
}
if (!BitConverter.IsLittleEndian)
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: NetworkManager only supports little endian! Network sync will not be possible.");
Ready = false;
return;
}
@@ -234,15 +297,75 @@ namespace Marro.PacManUdon
root = transform.parent.gameObject;
}
syncedObjects = root.GetComponentsInChildren<SyncedObject>(includeInactive: true);
foreach (var obj in syncedObjects)
if (!TryGetNetworkEventSubscribers(out networkEventSubscribers))
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Fatal: Invalid NetworkEventSubscribers configuration! Network sync will not be possible.");
return;
}
syncedUpdateSubscribers = root.GetComponentsInChildren<SyncedObject>(includeInactive: true);
AssignNetworkManagerReferencesToSubscribers();
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Found {syncedUpdateSubscribers.Length} {nameof(SyncedObject)} in children of {root.name}.");
initialized = true;
}
private bool TryGetNetworkEventSubscribers(out SyncedObject[][] result)
{
result = new SyncedObject[0][];
return true;
var values = networkEventSubscribersFlat;
var lengths = networkEventSubscribersFlatSegmentLengths;
result = new SyncedObject[lengths.Length][];
var index = 0;
foreach (var length in lengths)
{
if (index + length >= values.Length)
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(TryGetNetworkEventSubscribers)}: Lengths sum is larger than values.");
return false;
}
result[index] = new SyncedObject[length];
Array.Copy(values, index, result, 0, length);
index += length;
}
if (index != values.Length)
{
Debug.LogError($"({nameof(PacManUdon)} {nameof(NetworkManager)}) {nameof(TryGetNetworkEventSubscribers)}: Lengths sum is smaller than values.");
}
return true;
}
private void AssignNetworkManagerReferencesToSubscribers()
{
// This results in a lot of duplicated assignments, but the alternative is deduplicating and unfortunately Udon does not have a good method for this.
foreach (var obj in syncedUpdateSubscribers)
{
obj.networkManager = this;
}
Debug.Log($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Found {syncedObjects.Length} {nameof(SyncedObject)} in children of {root.name}.");
foreach (var obj in networkEventSubscribersFlat)
{
obj.networkManager = this;
}
}
SetOwner(Networking.IsOwner(gameObject));
public void Reset()
{
if (!initialized)
{
Debug.LogWarning($"({nameof(PacManUdon)} {nameof(NetworkManager)}) Tried to call {nameof(Reset)} while not initialized. Call {nameof(Initialize)} first.");
return;
}
IsOwner = Networking.IsOwner(gameObject);
ClearBuffer();
@@ -256,9 +379,14 @@ namespace Marro.PacManUdon
SyncedDeltaTime = Time.fixedDeltaTime;
nextUpdateTime = SyncedTime;
if (!Synced)
// Sync up
if (IsOwner)
{
RequestEvent(NetworkEventType.FullSync); // See if we can sync up
SendEventSoon(NetworkEventType.FullSyncForced);
}
else
{
RequestEvent(NetworkEventType.FullSync);
}
Ready = true;
@@ -268,12 +396,12 @@ namespace Marro.PacManUdon
public void Update()
{
if (!Ready)
if (!initialized)
{
return;
}
// Fetch the current time
// Get our target time
UpdateInternalTime();
if (Ready)
@@ -289,27 +417,45 @@ namespace Marro.PacManUdon
}
}
// Forwards simulated time at the updateRate pace
// Forwards simulated time by updateDelta until we're caught up
while (nextUpdateTime <= internalTime)
{
ProgressSyncedTime(nextUpdateTime);
PerformFixedSyncedUpdate();
nextUpdateTime = SyncedTime + updateRate;
nextUpdateTime = SyncedTime + updateDelta;
}
}
private void UpdateInternalTime()
{
internalTime = Time.fixedTime - offsetTime;
var delta = Time.fixedTime - offsetTime - internalTime;
if (!paused)
{
// Continue time like normal
internalTime += delta;
}
else if (paused && !stepNext)
{
// Since we're paused, increase our offset from Unity's time
offsetTime += delta;
}
else
{
// Step forward by exactly updateDelta, apply the remainder to our offset
offsetTime += delta - updateDelta;
internalTime += updateDelta;
stepNext = false;
}
}
private void PerformFixedSyncedUpdate()
{
IsEventUpdate = false;
for (int i = 0; i < syncedObjects.Length; i++)
for (int i = 0; i < syncedUpdateSubscribers.Length; i++)
{
var obj = syncedObjects[i];
var obj = syncedUpdateSubscribers[i];
if (obj.gameObject.activeInHierarchy)
{
@@ -348,15 +494,9 @@ namespace Marro.PacManUdon
}
}
private void SetOwner(bool isOwner)
{
IsOwner = isOwner;
if (DebugImageToIndicateOwner != null)
{
DebugImageToIndicateOwner.SetFloat("Color", isOwner ? 1 : 0);
}
}
private SyncedObject[] GetEventSubscribers(NetworkEventType eventType) =>
//networkEventSubscribers[(int)eventType];
syncedUpdateSubscribers;
#endregion
#region Sender
@@ -412,7 +552,9 @@ namespace Marro.PacManUdon
InitializeEvent(eventType, timestamp, eventId, out byte[] data, out var index);
foreach (var obj in syncedObjects)
var subscibers = GetEventSubscribers(eventType);
foreach (var obj in subscibers)
{
obj.CollectSyncedData(data, ref index, eventType);
}
@@ -686,12 +828,14 @@ namespace Marro.PacManUdon
ProgressSyncedTime(timestamp);
foreach (var obj in syncedObjects)
var subscibers = GetEventSubscribers(eventType);
foreach (var obj in subscibers)
{
obj.SyncedUpdate();
}
foreach (var obj in syncedObjects)
foreach (var obj in subscibers)
{
var success = obj.WriteSyncedData(@event, ref index, eventType);
@@ -869,7 +1013,7 @@ namespace Marro.PacManUdon
}
bool newOwnerIsLocalPlayer = newOwner == Networking.LocalPlayer;
SetOwner(newOwnerIsLocalPlayer);
IsOwner = newOwnerIsLocalPlayer;
if (newOwnerIsLocalPlayer)
{
@@ -936,6 +1080,31 @@ namespace Marro.PacManUdon
}
#endregion
#region SyncedData
public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{
// Nothing
}
public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
{
switch (eventType)
{
case NetworkEventType.Pause:
Pause();
break;
case NetworkEventType.Resume:
Resume();
break;
case NetworkEventType.Step:
Step();
break;
}
return true;
}
#endregion
#region Debug
public void SimulateSyncToTimestamp(float timestamp)
{
@@ -965,6 +1134,11 @@ namespace Marro.PacManUdon
/// </summary>
[SerializeField] private Animator DebugImageToIndicateOwner;
/// <summary>
/// An animator which visualizes whether the NetworkManager is synced.
/// </summary>
[SerializeField] private Animator DebugImageToIndicateSynced;
public void DoFullSync()
{
SendEventSoon(NetworkEventType.FullSync);
@@ -979,6 +1153,47 @@ namespace Marro.PacManUdon
{
SendEventSoon(NetworkEventType.GhostUpdate);
}
public void DoTimeSequenceSync()
{
SendEventSoon(NetworkEventType.TimeSequenceSync);
}
private void Pause()
{
paused = true;
stepNext = false;
}
private void Resume()
{
paused = false;
stepNext = false;
}
private void Step()
{
paused = true;
stepNext = true;
}
public void PauseButtonPressed()
{
Pause();
SendEventSoon(NetworkEventType.Pause);
}
public void ResumeButtonPressed()
{
Resume();
SendEventSoon(NetworkEventType.Resume);
}
public void StepButtonPressed()
{
Step();
SendEventSoon(NetworkEventType.Step);
}
#endregion
}
}