261 lines
8.5 KiB
C#
261 lines
8.5 KiB
C#
|
using UdonSharp;
|
|||
|
using UnityEngine;
|
|||
|
using VRC.SDKBase;
|
|||
|
using VRC.Udon.Common;
|
|||
|
using VRC.Udon.Common.Interfaces;
|
|||
|
|
|||
|
#pragma warning disable IDE0090, IDE1006
|
|||
|
|
|||
|
namespace QvPen.UdonScript
|
|||
|
{
|
|||
|
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
|
|||
|
public class QvPen_LateSync : UdonSharpBehaviour
|
|||
|
{
|
|||
|
public QvPen_Pen pen { get; set; }
|
|||
|
|
|||
|
private LineRenderer[] linesBuffer = { };
|
|||
|
private int inkIndex = -1;
|
|||
|
|
|||
|
private VRCPlayerApi master = null;
|
|||
|
|
|||
|
public override void OnPlayerJoined(VRCPlayerApi player)
|
|||
|
{
|
|||
|
if (master == null || player.playerId < master.playerId)
|
|||
|
master = player;
|
|||
|
|
|||
|
if (VRCPlayerApi.GetPlayerCount() > 1 && !Networking.IsOwner(gameObject))
|
|||
|
SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(StartSync));
|
|||
|
}
|
|||
|
|
|||
|
public override void OnOwnershipTransferred(VRCPlayerApi player)
|
|||
|
{
|
|||
|
master = player;
|
|||
|
|
|||
|
if (VRCPlayerApi.GetPlayerCount() > 1 && Networking.IsOwner(gameObject))
|
|||
|
SendCustomEventDelayedSeconds(nameof(StartSync), 1.84f * (1f + Random.value));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#region Data protocol
|
|||
|
|
|||
|
#region Base
|
|||
|
|
|||
|
// Pen mode
|
|||
|
private const int MODE_UNKNOWN = QvPen_Pen.MODE_UNKNOWN;
|
|||
|
private const int MODE_DRAW = QvPen_Pen.MODE_DRAW;
|
|||
|
private const int MODE_ERASE = QvPen_Pen.MODE_ERASE;
|
|||
|
private const int MODE_DRAW_PLANE = QvPen_Pen.MODE_DRAW_PLANE;
|
|||
|
|
|||
|
// Footer element
|
|||
|
private const int FOOTER_ELEMENT_DATA_INFO = QvPen_Pen.FOOTER_ELEMENT_DATA_INFO;
|
|||
|
private const int FOOTER_ELEMENT_PEN_ID = QvPen_Pen.FOOTER_ELEMENT_PEN_ID;
|
|||
|
|
|||
|
//private const int FOOTER_ELEMENT_DRAW_INK_INFO = QvPen_Pen.FOOTER_ELEMENT_DRAW_INK_INFO;
|
|||
|
//private const int FOOTER_ELEMENT_DRAW_LENGTH = QvPen_Pen.FOOTER_ELEMENT_DRAW_LENGTH;
|
|||
|
|
|||
|
//private const int FOOTER_ELEMENT_ERASE_POINTER_POSITION = QvPen_Pen.FOOTER_ELEMENT_ERASE_POINTER_POSITION;
|
|||
|
//private const int FOOTER_ELEMENT_ERASE_LENGTH = QvPen_Pen.FOOTER_ELEMENT_ERASE_LENGTH;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
// Pen sync mode
|
|||
|
private const int SYNC_STATE_Idle = QvPen_Pen.SYNC_STATE_Idle;
|
|||
|
private const int SYNC_STATE_Started = QvPen_Pen.SYNC_STATE_Started;
|
|||
|
private const int SYNC_STATE_Finished = QvPen_Pen.SYNC_STATE_Finished;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
private bool forceStart = false;
|
|||
|
|
|||
|
public void StartSync()
|
|||
|
{
|
|||
|
forceStart = true;
|
|||
|
retryCount = 0;
|
|||
|
|
|||
|
SendBiginSignal();
|
|||
|
}
|
|||
|
|
|||
|
[UdonSynced]
|
|||
|
private Vector3[] _syncedData;
|
|||
|
private Vector3[] syncedData
|
|||
|
{
|
|||
|
get => _syncedData;
|
|||
|
set
|
|||
|
{
|
|||
|
if (forceStart)
|
|||
|
{
|
|||
|
_syncedData = new Vector3[] { pen.penIdVector, beginSignal };
|
|||
|
|
|||
|
if (Networking.IsOwner(gameObject))
|
|||
|
_RequestSendPackage();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_syncedData = value;
|
|||
|
|
|||
|
if (Networking.IsOwner(gameObject))
|
|||
|
_RequestSendPackage();
|
|||
|
else if (_syncedData != null && _syncedData.Length > 0)
|
|||
|
UnpackData(_syncedData);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private bool _isNetworkSettled = false;
|
|||
|
private bool isNetworkSettled
|
|||
|
=> _isNetworkSettled || (_isNetworkSettled = Networking.IsNetworkSettled);
|
|||
|
|
|||
|
private bool isInUseSyncBuffer = false;
|
|||
|
public void _RequestSendPackage()
|
|||
|
{
|
|||
|
if (VRCPlayerApi.GetPlayerCount() > 1 && Networking.IsOwner(gameObject))
|
|||
|
{
|
|||
|
if (!isNetworkSettled)
|
|||
|
{
|
|||
|
SendCustomEventDelayedSeconds(nameof(_RequestSendPackage), 1.84f);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
isInUseSyncBuffer = true;
|
|||
|
RequestSerialization();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SendData(Vector3[] data)
|
|||
|
{
|
|||
|
if (!isInUseSyncBuffer)
|
|||
|
syncedData = data;
|
|||
|
}
|
|||
|
|
|||
|
public override void OnPreSerialization()
|
|||
|
=> _syncedData = syncedData;
|
|||
|
|
|||
|
public override void OnDeserialization()
|
|||
|
=> syncedData = _syncedData;
|
|||
|
|
|||
|
private const int maxRetryCount = 3;
|
|||
|
private int retryCount = 0;
|
|||
|
public override void OnPostSerialization(SerializationResult result)
|
|||
|
{
|
|||
|
isInUseSyncBuffer = false;
|
|||
|
|
|||
|
if (!result.success)
|
|||
|
{
|
|||
|
if (retryCount++ < maxRetryCount)
|
|||
|
SendCustomEventDelayedSeconds(nameof(_RequestSendPackage), 1.84f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
retryCount = 0;
|
|||
|
|
|||
|
var signal = GetCalibrationSignal(syncedData);
|
|||
|
if (signal == errorSignal)
|
|||
|
return;
|
|||
|
else if (signal == beginSignal)
|
|||
|
{
|
|||
|
forceStart = false;
|
|||
|
|
|||
|
linesBuffer = pen.inkPoolSynced.GetComponentsInChildren<LineRenderer>();
|
|||
|
|
|||
|
inkIndex = -1;
|
|||
|
}
|
|||
|
else if (signal == endSignal)
|
|||
|
{
|
|||
|
linesBuffer = new LineRenderer[] { };
|
|||
|
|
|||
|
syncedData = new Vector3[] { };
|
|||
|
isInUseSyncBuffer = false;
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var ink = GetNextInk();
|
|||
|
if (ink)
|
|||
|
SendData(pen._PackData(ink, MODE_DRAW));
|
|||
|
else
|
|||
|
SendEndSignal();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private readonly Vector3 beginSignal = new Vector3(2.7182818e8f, 1f, 6.2831853e4f);
|
|||
|
private readonly Vector3 endSignal = new Vector3(2.7182818e8f, 0f, 6.2831853e4f);
|
|||
|
private readonly Vector3 errorSignal = new Vector3(2.7182818e8f, -1f, 6.2831853e4f);
|
|||
|
|
|||
|
private void UnpackData(Vector3[] data)
|
|||
|
{
|
|||
|
var penIdVector = GetPenIdVector(data);
|
|||
|
|
|||
|
if (pen && pen._CheckId(penIdVector))
|
|||
|
{
|
|||
|
if (pen.currentSyncState == SYNC_STATE_Finished)
|
|||
|
return;
|
|||
|
|
|||
|
var signal = GetCalibrationSignal(data);
|
|||
|
if (signal == beginSignal)
|
|||
|
{
|
|||
|
if (pen.currentSyncState == SYNC_STATE_Idle)
|
|||
|
pen.currentSyncState = SYNC_STATE_Started;
|
|||
|
}
|
|||
|
else if (signal == endSignal)
|
|||
|
{
|
|||
|
if (pen.currentSyncState == SYNC_STATE_Started)
|
|||
|
pen.currentSyncState = SYNC_STATE_Finished;
|
|||
|
}
|
|||
|
else
|
|||
|
pen._UnpackData(data);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SendBiginSignal()
|
|||
|
=> SendData(new Vector3[] { pen.penIdVector, beginSignal });
|
|||
|
|
|||
|
private void SendEndSignal()
|
|||
|
=> SendData(new Vector3[] { pen.penIdVector, endSignal });
|
|||
|
|
|||
|
private Vector3 GetCalibrationSignal(Vector3[] data)
|
|||
|
=> data.Length > 1 ? data[1] : errorSignal;
|
|||
|
|
|||
|
private Vector3 GetData(Vector3[] data, int index)
|
|||
|
=> data.Length > index ? data[data.Length - 1 - index] : errorSignal;
|
|||
|
|
|||
|
private Vector3 GetPenIdVector(Vector3[] data)
|
|||
|
=> data.Length > FOOTER_ELEMENT_PEN_ID ? GetData(data, FOOTER_ELEMENT_PEN_ID) : errorSignal;
|
|||
|
|
|||
|
private LineRenderer GetNextInk()
|
|||
|
{
|
|||
|
inkIndex = Mathf.Max(-1, inkIndex);
|
|||
|
|
|||
|
while (++inkIndex < linesBuffer.Length)
|
|||
|
{
|
|||
|
var ink = linesBuffer[inkIndex];
|
|||
|
if (ink)
|
|||
|
return ink;
|
|||
|
}
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
#region Log
|
|||
|
|
|||
|
private void Log(object o) => Debug.Log($"{logPrefix}{o}", this);
|
|||
|
private void Warning(object o) => Debug.LogWarning($"{logPrefix}{o}", this);
|
|||
|
private void Error(object o) => Debug.LogError($"{logPrefix}{o}", this);
|
|||
|
|
|||
|
private readonly Color logColor = new Color(0xf2, 0x7d, 0x4a, 0xff) / 0xff;
|
|||
|
private string ColorBeginTag(Color c) => $"<color=\"#{ToHtmlStringRGB(c)}\">";
|
|||
|
private const string ColorEndTag = "</color>";
|
|||
|
|
|||
|
private string _logPrefix;
|
|||
|
private string logPrefix
|
|||
|
=> string.IsNullOrEmpty(_logPrefix)
|
|||
|
? (_logPrefix = $"[{ColorBeginTag(logColor)}{nameof(QvPen)}.{nameof(QvPen.Udon)}.{nameof(QvPen_LateSync)}{ColorEndTag}] ") : _logPrefix;
|
|||
|
|
|||
|
private string ToHtmlStringRGB(Color c)
|
|||
|
{
|
|||
|
c *= 0xff;
|
|||
|
return $"{Mathf.RoundToInt(c.r):x2}{Mathf.RoundToInt(c.g):x2}{Mathf.RoundToInt(c.b):x2}";
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|