further cleanup of repo

This commit is contained in:
NotAKidoS 2025-04-03 03:03:24 -05:00
parent 4f8dcb0cd0
commit 323eb92f2e
140 changed files with 1 additions and 2430 deletions

View file

@ -0,0 +1,41 @@
using MoonSharp.Interpreter;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
namespace NAK.LuaNetVars;
public struct LuaEventContext
{
private string SenderId { get; set; }
public string SenderName { get; private set; }
private DateTime LastInvokeTime { get; set; }
private double TimeSinceLastInvoke { get; set; }
private bool IsLocal { get; set; }
public static LuaEventContext Create(string senderId, DateTime lastInvokeTime)
{
var playerName = CVRPlayerManager.Instance.TryGetPlayerName(senderId);
return new LuaEventContext
{
SenderId = senderId,
SenderName = playerName ?? "Unknown",
LastInvokeTime = lastInvokeTime,
TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds,
IsLocal = senderId == MetaPort.Instance.ownerId
};
}
public Table ToLuaTable(Script script)
{
Table table = new(script)
{
["senderId"] = SenderId,
["senderName"] = SenderName,
["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"),
["timeSinceLastInvoke"] = TimeSinceLastInvoke,
["isLocal"] = IsLocal
};
return table;
}
}

View file

@ -0,0 +1,60 @@
namespace NAK.LuaNetVars;
internal class LuaEventTracker
{
private class EventMetadata
{
public DateTime LastInvokeTime { get; set; }
public Dictionary<string, DateTime> LastInvokeTimePerSender { get; } = new();
}
private readonly Dictionary<string, EventMetadata> _eventMetadata = new();
public DateTime GetLastInvokeTime(string eventName)
{
if (!_eventMetadata.TryGetValue(eventName, out var metadata))
{
metadata = new EventMetadata();
_eventMetadata[eventName] = metadata;
}
return metadata.LastInvokeTime;
}
public DateTime GetLastInvokeTimeForSender(string eventName, string senderId)
{
if (!_eventMetadata.TryGetValue(eventName, out var metadata))
{
metadata = new EventMetadata();
_eventMetadata[eventName] = metadata;
}
if (!metadata.LastInvokeTimePerSender.TryGetValue(senderId, out DateTime time))
{
return DateTime.MinValue;
}
return time;
}
public void UpdateInvokeTime(string eventName, string senderId)
{
if (!_eventMetadata.TryGetValue(eventName, out EventMetadata metadata))
{
metadata = new EventMetadata();
_eventMetadata[eventName] = metadata;
}
DateTime now = DateTime.Now;
metadata.LastInvokeTime = now;
metadata.LastInvokeTimePerSender[senderId] = now;
}
public void Clear()
{
_eventMetadata.Clear();
}
public void ClearEvent(string eventName)
{
_eventMetadata.Remove(eventName);
}
}

View file

@ -0,0 +1,146 @@
using ABI_RC.Core.Savior;
using ABI_RC.Core.Util;
using ABI_RC.Systems.ModNetwork;
using ABI.CCK.Components;
using ABI.Scripting.CVRSTL.Common;
using MoonSharp.Interpreter;
using UnityEngine;
namespace NAK.LuaNetVars;
public partial class LuaNetVarController : MonoBehaviour
{
private static readonly HashSet<int> _hashes = new();
private const string MODULE_ID = "NAK.LNV:";
private int _uniquePathHash;
private string ModNetworkID { get; set; }
private CVRLuaClientBehaviour _luaClientBehaviour;
private readonly Dictionary<string, DynValue> _registeredNetworkVars = new();
private readonly Dictionary<string, DynValue> _registeredNotifyCallbacks = new();
private readonly Dictionary<string, DynValue> _registeredEventCallbacks = new();
private readonly HashSet<string> _dirtyVariables = new();
private bool _requestInitialSync;
private CVRSpawnable _spawnable;
private CVRObjectSync _objectSync;
#region Unity Events
private void Awake()
{
if (!Initialize())
return;
// TODO: a manager script should be in charge of this
// TODO: disabling object will kill coroutine
StartCoroutine(SendVariableUpdatesCoroutine());
}
private void OnDestroy()
=> Cleanup();
#endregion Unity Events
#region Private Methods
private bool Initialize()
{
if (!TryGetComponent(out _luaClientBehaviour)) return false;
if (!TryGetUniqueNetworkID(out _uniquePathHash)) return false;
ModNetworkID = MODULE_ID + _uniquePathHash.ToString("X8");
if (ModNetworkID.Length > ModNetworkManager.MaxMessageIdLength)
{
LuaNetVarsMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!");
return false;
}
_hashes.Add(_uniquePathHash);
ModNetworkManager.Subscribe(ModNetworkID, OnMessageReceived);
LuaNetVarsMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}");
switch (_luaClientBehaviour.Context.objContext)
{
case CVRLuaObjectContext.AVATAR:
_requestInitialSync = !_luaClientBehaviour.Context.IsWornByMe;
break;
case CVRLuaObjectContext.PROP:
_spawnable = _luaClientBehaviour.Context.RootComponent as CVRSpawnable;
_requestInitialSync = !_luaClientBehaviour.Context.IsSpawnedByMe;
break;
case CVRLuaObjectContext.WORLD:
_objectSync = GetComponentInParent<CVRObjectSync>();
_requestInitialSync = true; // idk probably works
break;
default:
_requestInitialSync = true;
break;
}
return true;
}
// TODO: evaluate if having dedicated globals is better behaviour (i think so)
// private void ConfigureLuaEnvironment()
// {
// _luaClientBehaviour.script.Globals["SendLuaEvent"] = DynValue.NewCallback(SendLuaEventCallback);
// }
private void Cleanup()
{
_eventTracker.Clear();
if (_uniquePathHash == 0 || string.IsNullOrEmpty(ModNetworkID))
return;
ModNetworkManager.Unsubscribe(ModNetworkID);
LuaNetVarsMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}");
_hashes.Remove(_uniquePathHash);
}
private System.Collections.IEnumerator SendVariableUpdatesCoroutine()
{
while (true)
{
yield return new WaitForSeconds(0.1f);
if (IsSyncOwner()) SendVariableUpdates();
if (!_requestInitialSync) continue;
_requestInitialSync = false;
RequestVariableSync();
}
}
#endregion Private Methods
#region Ownership Methods
public bool IsSyncOwner()
{
if (_objectSync) return _objectSync.SyncedByMe; // idk
if (_spawnable)
{
if (_spawnable.IsSyncedByMe()) return true; // is held / attached locally
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId);
if (propData != null) return propData.syncedBy == MetaPort.Instance.ownerId; // last updated by me
return false; // not held / attached locally and not last updated by me
}
return false;
}
public string GetSyncOwner()
{
if (_objectSync) return _objectSync.syncedBy;
if (_spawnable)
{
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId);
return propData?.syncedBy ?? string.Empty;
}
return string.Empty;
}
#endregion Ownership Methods
}

View file

@ -0,0 +1,214 @@
using ABI_RC.Core.Savior;
using ABI_RC.Systems.ModNetwork;
using MoonSharp.Interpreter;
using Unity.Services.Authentication.Internal;
namespace NAK.LuaNetVars
{
public partial class LuaNetVarController
{
private enum MessageType : byte
{
LuaVariable = 0,
LuaEvent = 1,
SyncVariables = 2,
RequestSync = 3
}
private readonly LuaEventTracker _eventTracker = new();
#region Mod Network Events
private void OnMessageReceived(ModNetworkMessage msg)
{
msg.Read(out byte msgTypeRaw);
if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) return;
MessageType msgType = (MessageType)msgTypeRaw;
switch (msgType)
{
case MessageType.LuaVariable:
HandleLuaVariableUpdate(msg);
break;
case MessageType.LuaEvent:
HandleLuaEvent(msg);
break;
case MessageType.SyncVariables:
HandleSyncVariables(msg);
break;
case MessageType.RequestSync:
HandleRequestSyncVariables(msg);
break;
}
}
private void HandleLuaVariableUpdate(ModNetworkMessage msg)
{
msg.Read(out string varName);
DynValue newValue = DeserializeDynValue(msg);
LuaNetVarsMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}");
if (_registeredNetworkVars.TryGetValue(varName, out DynValue var))
{
UpdateNetworkVariable(varName, var, newValue);
}
else
{
LuaNetVarsMod.Logger.Warning($"Received update for unregistered variable {varName}");
}
}
private void HandleLuaEvent(ModNetworkMessage msg)
{
string senderId = msg.Sender;
msg.Read(out string eventName);
msg.Read(out int argsCount);
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime);
// Update tracking
_eventTracker.UpdateInvokeTime(eventName, senderId);
// Read event arguments
var args = new DynValue[argsCount + 1]; // +1 for context
args[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script));
for (int i = 0; i < argsCount; i++)
{
args[i + 1] = DeserializeDynValue(msg);
}
LuaNetVarsMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args");
InvokeLuaEvent(eventName, args);
}
private void HandleSyncVariables(ModNetworkMessage msg)
{
msg.Read(out int varCount);
for (int i = 0; i < varCount; i++)
{
msg.Read(out string varName);
DynValue newValue = DeserializeDynValue(msg);
if (_registeredNetworkVars.TryGetValue(varName, out DynValue var))
{
UpdateNetworkVariable(varName, var, newValue);
}
else
{
LuaNetVarsMod.Logger.Warning($"Received sync for unregistered variable {varName}");
}
}
}
private void HandleRequestSyncVariables(ModNetworkMessage msg)
{
if (!IsSyncOwner()) return;
SendVariableSyncToUser(msg.Sender);
}
#endregion
#region Event Invocation
private void InvokeLuaEvent(string eventName, DynValue[] args)
{
if (_registeredEventCallbacks.TryGetValue(eventName, out DynValue callback))
{
_luaClientBehaviour.script.Call(callback, args);
}
else
{
LuaNetVarsMod.Logger.Warning($"No registered callback for event {eventName}");
}
}
#endregion
#region Sending Methods
private void SendVariableUpdates()
{
if (_dirtyVariables.Count == 0) return;
using ModNetworkMessage modMsg = new(ModNetworkID); // can pass target userids as params if needed
modMsg.Write((byte)MessageType.SyncVariables);
modMsg.Write(_dirtyVariables.Count);
modMsg.Send();
foreach (var varName in _dirtyVariables)
{
modMsg.Write(varName);
SerializeDynValue(modMsg, _registeredNetworkVars[varName]);
}
_dirtyVariables.Clear();
}
private void SendVariableSyncToUser(string userId)
{
using ModNetworkMessage modMsg = new(ModNetworkID, userId);
modMsg.Write((byte)MessageType.SyncVariables);
modMsg.Write(_registeredNetworkVars.Count);
foreach (var kvp in _registeredNetworkVars)
{
modMsg.Write(kvp.Key);
SerializeDynValue(modMsg, kvp.Value);
}
modMsg.Send();
LuaNetVarsMod.Logger.Msg($"Sent variable sync to {userId}");
}
private void RequestVariableSync()
{
using ModNetworkMessage modMsg = new(ModNetworkID);
modMsg.Write((byte)MessageType.RequestSync);
modMsg.Send();
LuaNetVarsMod.Logger.Msg("Requested variable sync");
}
// private DynValue SendLuaEventCallback(ScriptExecutionContext context, CallbackArguments args)
// {
// if (args.Count < 1) return DynValue.Nil;
//
// var eventName = args[0].CastToString();
// var eventArgs = args.GetArray().Skip(1).ToArray();
//
// SendLuaEvent(eventName, eventArgs);
//
// return DynValue.Nil;
// }
internal void SendLuaEvent(string eventName, DynValue[] args)
{
string senderId = MetaPort.Instance.ownerId;
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime);
// Update tracking
_eventTracker.UpdateInvokeTime(eventName, senderId);
var argsWithContext = new DynValue[args.Length + 1];
argsWithContext[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script));
Array.Copy(args, 0, argsWithContext, 1, args.Length);
InvokeLuaEvent(eventName, argsWithContext);
using ModNetworkMessage modMsg = new(ModNetworkID);
modMsg.Write((byte)MessageType.LuaEvent);
modMsg.Write(eventName);
modMsg.Write(args.Length);
foreach (DynValue arg in args)
SerializeDynValue(modMsg, arg);
modMsg.Send();
}
#endregion
}
}

View file

@ -0,0 +1,101 @@
using MoonSharp.Interpreter;
namespace NAK.LuaNetVars;
public partial class LuaNetVarController
{
internal void RegisterNetworkVar(string varName)
{
if (_registeredNetworkVars.ContainsKey(varName))
{
LuaNetVarsMod.Logger.Warning($"Network variable {varName} already registered!");
return;
}
_registeredNetworkVars[varName] = DynValue.Nil;
_luaClientBehaviour.script.Globals[varName] = DynValue.Nil;
RegisterGetterFunction(varName);
RegisterSetterFunction(varName);
LuaNetVarsMod.Logger.Msg($"Registered network variable {varName}");
}
private void RegisterGetterFunction(string varName)
{
_luaClientBehaviour.script.Globals["Get" + varName] = DynValue.NewCallback((context, args) =>
{
return _registeredNetworkVars.TryGetValue(varName, out var value) ? value : DynValue.Nil;
});
}
private void RegisterSetterFunction(string varName)
{
_luaClientBehaviour.script.Globals["Set" + varName] = DynValue.NewCallback((context, args) =>
{
if (args.Count < 1) return DynValue.Nil;
var newValue = args[0];
if (!IsSupportedDynValue(newValue))
{
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}");
return DynValue.Nil;
}
if (_registeredNetworkVars.TryGetValue(varName, out var oldValue))
{
UpdateNetworkVariable(varName, oldValue, newValue);
}
return DynValue.Nil;
});
}
private void UpdateNetworkVariable(string varName, DynValue oldValue, DynValue newValue)
{
_registeredNetworkVars[varName] = newValue;
_luaClientBehaviour.script.Globals[varName] = newValue;
_dirtyVariables.Add(varName);
if (_registeredNotifyCallbacks.TryGetValue(varName, out var callback))
{
_luaClientBehaviour.script.Call(callback, DynValue.NewString(varName), oldValue, newValue);
}
}
internal void RegisterNotifyCallback(string varName, DynValue callback)
{
if (!ValidateCallback(callback) || !ValidateNetworkVar(varName)) return;
if (_registeredNotifyCallbacks.ContainsKey(varName))
LuaNetVarsMod.Logger.Warning($"Overwriting notify callback for {varName}");
_registeredNotifyCallbacks[varName] = callback;
LuaNetVarsMod.Logger.Msg($"Registered notify callback for {varName}");
}
internal void RegisterEventCallback(string eventName, DynValue callback)
{
if (!ValidateCallback(callback)) return;
if (_registeredEventCallbacks.ContainsKey(eventName))
LuaNetVarsMod.Logger.Warning($"Overwriting event callback for {eventName}");
_registeredEventCallbacks[eventName] = callback;
LuaNetVarsMod.Logger.Msg($"Registered event callback for {eventName}");
}
private bool ValidateCallback(DynValue callback)
{
if (callback?.Function != null) return true;
LuaNetVarsMod.Logger.Error("Passed DynValue must be a function");
return false;
}
private bool ValidateNetworkVar(string varName)
{
if (_registeredNetworkVars.ContainsKey(varName)) return true;
LuaNetVarsMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}.");
return false;
}
}

View file

@ -0,0 +1,62 @@
using ABI_RC.Systems.ModNetwork;
using MoonSharp.Interpreter;
namespace NAK.LuaNetVars;
public partial class LuaNetVarController
{
private static DynValue DeserializeDynValue(ModNetworkMessage msg)
{
msg.Read(out byte dataTypeByte);
DataType dataType = (DataType)dataTypeByte;
switch (dataType)
{
case DataType.Boolean:
msg.Read(out bool boolValue);
return DynValue.NewBoolean(boolValue);
case DataType.Number:
msg.Read(out double numberValue);
return DynValue.NewNumber(numberValue);
case DataType.String:
msg.Read(out string stringValue);
return DynValue.NewString(stringValue);
case DataType.Nil:
return DynValue.Nil;
default:
LuaNetVarsMod.Logger.Error($"Unsupported data type received: {dataType}");
return DynValue.Nil;
}
}
private static void SerializeDynValue(ModNetworkMessage msg, DynValue value)
{
switch (value.Type)
{
case DataType.Boolean:
msg.Write((byte)DataType.Boolean);
msg.Write(value.Boolean);
break;
case DataType.Number:
msg.Write((byte)DataType.Number);
msg.Write(value.Number);
break;
case DataType.String:
msg.Write((byte)DataType.String);
msg.Write(value.String);
break;
case DataType.Nil:
msg.Write((byte)DataType.Nil);
break;
default:
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {value.Type}");
msg.Write((byte)DataType.Nil);
break;
}
}
private static bool IsSupportedDynValue(DynValue value)
{
return value.Type is DataType.Boolean or DataType.Number or DataType.String or DataType.Nil;
}
}

View file

@ -0,0 +1,57 @@
using ABI_RC.Core.Savior;
using UnityEngine;
namespace NAK.LuaNetVars;
public partial class LuaNetVarController
{
private bool TryGetUniqueNetworkID(out int hash)
{
string path = GetGameObjectPath(transform);
hash = path.GetHashCode();
// Check if it already exists (this **should** only matter in worlds)
if (_hashes.Contains(hash))
{
LuaNetVarsMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}");
if (!FindAvailableHash(ref hash)) // Super lazy fix idfc
{
LuaNetVarsMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}");
return false;
}
}
_hashes.Add(hash);
return true;
static bool FindAvailableHash(ref int hash)
{
for (int i = 0; i < 16; i++)
{
hash += 1;
if (!_hashes.Contains(hash)) return true;
}
return false; // Failed to find a hash in 16 tries
}
}
private static string GetGameObjectPath(Transform transform)
{
string path = transform.name;
while (transform.parent != null)
{
transform = transform.parent;
// Only true at root of local player object
if (transform.CompareTag("Player"))
{
path = MetaPort.Instance.ownerId + "/" + path;
break;
}
path = transform.name + "/" + path;
}
return path;
}
}