mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
further cleanup of repo
This commit is contained in:
parent
4f8dcb0cd0
commit
323eb92f2e
140 changed files with 1 additions and 2430 deletions
|
@ -0,0 +1,44 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.CVRLuaToolsExtension;
|
||||
|
||||
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
|
||||
{
|
||||
private static T _instance;
|
||||
private static readonly object _lock = new();
|
||||
private static bool _isShuttingDown;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isShuttingDown)
|
||||
{
|
||||
Debug.LogWarning($"[Singleton] Instance of {typeof(T)} already destroyed. Returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance != null) return _instance;
|
||||
_instance = (T)FindObjectOfType(typeof(T));
|
||||
if (_instance != null) return _instance;
|
||||
GameObject singletonObject = new($"{typeof(T).Name} (Singleton)");
|
||||
_instance = singletonObject.AddComponent<T>();
|
||||
DontDestroyOnLoad(singletonObject);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
_isShuttingDown = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_instance == this)
|
||||
_isShuttingDown = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI.CCK.Components;
|
||||
using ABI.Scripting.CVRSTL.Common;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.CVRLuaToolsExtension;
|
||||
|
||||
public static class CVRBaseLuaBehaviourExtensions
|
||||
{
|
||||
// check if the script is eligible for hot reload
|
||||
public static bool IsScriptEligibleForHotReload(this CVRBaseLuaBehaviour script)
|
||||
{
|
||||
return script.Context.IsWornByMe // avatar if worn
|
||||
|| script.Context.IsSpawnedByMe // prop if spawned
|
||||
|| script.Context.objContext == CVRLuaObjectContext.WORLD; // always world scripts
|
||||
}
|
||||
|
||||
// gets the asset id from the script
|
||||
public static string GetAssetIdFromScript(this CVRBaseLuaBehaviour script)
|
||||
{
|
||||
switch (script.Context.objContext)
|
||||
{
|
||||
case CVRLuaObjectContext.WORLD:
|
||||
//return MetaPort.Instance.CurrentWorldId; // do not trust CVRAssetInfo, can be destroyed at runtime
|
||||
return "SYSTEM"; // default for world scripts is SYSTEM, but TODO: use actual world id
|
||||
case CVRLuaObjectContext.AVATAR:
|
||||
Component rootComponent = script.Context.RootComponent;
|
||||
return rootComponent switch
|
||||
{
|
||||
PlayerSetup => MetaPort.Instance.currentAvatarGuid, // local avatar
|
||||
PuppetMaster puppetMaster => puppetMaster.CVRPlayerEntity.AvatarId, // remote avatar
|
||||
_ => string.Empty // fuck
|
||||
};
|
||||
case CVRLuaObjectContext.PROP:
|
||||
{
|
||||
CVRSpawnable spawnable = (CVRSpawnable)script.Context.RootComponent;
|
||||
if (!string.IsNullOrEmpty(spawnable.guid)) return spawnable.guid; // after filtering has occured
|
||||
return spawnable.TryGetComponent(out CVRAssetInfo assetInfo)
|
||||
? assetInfo.objectId // before filtering has occured
|
||||
: string.Empty; // well shit
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI.Scripting.CVRSTL.Client;
|
||||
using System.Diagnostics;
|
||||
using MTJobSystem;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.CVRLuaToolsExtension;
|
||||
|
||||
public static class CVRLuaClientBehaviourExtensions
|
||||
{
|
||||
internal static readonly Dictionary<CVRLuaClientBehaviour, bool> _isRestarting = new();
|
||||
private static string PersistentDataPath;
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static void Restart(this CVRLuaClientBehaviour behaviour)
|
||||
{
|
||||
PersistentDataPath ??= Application.persistentDataPath; // needs to be set on main
|
||||
|
||||
if (_isRestarting.TryGetValue(behaviour, out bool isRestarting) && isRestarting)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Warning($"Restart is already in progress for {behaviour.ScriptName}.");
|
||||
return;
|
||||
}
|
||||
|
||||
_isRestarting[behaviour] = true;
|
||||
|
||||
bool wasEnabled = behaviour.enabled;
|
||||
if (behaviour.Crashed)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Warning($"Restarting a script ({behaviour.ScriptName}) in a crashed state. Unable to determine original enabled state, defaulting to true.");
|
||||
wasEnabled = true;
|
||||
}
|
||||
|
||||
behaviour.enabled = false;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
behaviour.ResetScriptCompletely();
|
||||
if (CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Warning("Attempting to initialize the lua script off main thread. This may cause crashes or corruption if you are accessing UnityEngine Objects outside of Unity's callbacks!");
|
||||
behaviour.DoInitialLoadOfScript();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die in task
|
||||
}
|
||||
|
||||
MTJobManager.RunOnMainThread("RestartScript", () =>
|
||||
{
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
try
|
||||
{
|
||||
if (!CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
|
||||
behaviour.DoInitialLoadOfScript();
|
||||
|
||||
if (behaviour.InitialCodeRun) behaviour.IsScriptInitialized = true; // allow callbacks to run again
|
||||
|
||||
// invoke custom event on reset
|
||||
if (!behaviour.ShouldSkipEvent("CVRLuaTools_OnReset")) behaviour.ExecuteEvent(5000, "CVRLuaTools_OnReset");
|
||||
|
||||
// re-enable the script & invoke the lifecycle events
|
||||
if (!behaviour.ShouldSkipEvent("Awake")) behaviour.ExecuteEvent(1000, "Awake");
|
||||
behaviour.enabled = wasEnabled;
|
||||
if (wasEnabled)
|
||||
{
|
||||
if (!behaviour.ShouldSkipEvent("OnEnable")) behaviour.ExecuteEvent(1000, "OnEnable");
|
||||
if (!behaviour.ShouldSkipEvent("Start")) behaviour.ExecuteEvent(1000, "Start");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die prior to resetting restart flag
|
||||
}
|
||||
|
||||
_isRestarting.Remove(behaviour);
|
||||
|
||||
stopwatch.Stop();
|
||||
CVRLuaToolsExtensionMod.Logger.Msg($"Restarted {behaviour.ScriptName} in {stopwatch.ElapsedMilliseconds}ms.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void ResetScriptCompletely(this CVRLuaClientBehaviour behaviour)
|
||||
{
|
||||
var boundObjectEntries = new List<LuaScriptFactory.BoundObjectEntry>();
|
||||
foreach (var kvp in behaviour.BoundObjects)
|
||||
{
|
||||
LuaScriptFactory.BoundObjectEntry entry = new(behaviour, behaviour.Context, kvp.Key, kvp.Value);
|
||||
boundObjectEntries.Add(entry);
|
||||
}
|
||||
|
||||
behaviour.IsScriptInitialized = false; // prevent callbacks from causing null refs while restarting
|
||||
behaviour.InitialCodeRun = false; // needs to be set so LoadAndRunScriptIfNeeded will run the script again
|
||||
behaviour.Crashed = false; // reset the crash flag
|
||||
|
||||
behaviour._scriptGlobalFunctions.Clear(); // will be repopulated
|
||||
behaviour._startupMessageQueue.Clear(); // will be repopulated
|
||||
behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n");
|
||||
|
||||
behaviour.script = null;
|
||||
behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform, PersistentDataPath);
|
||||
|
||||
behaviour.InitTimerIfNeeded(); // only null if crashed prior
|
||||
behaviour.script.AttachDebugger(behaviour.timer); // reattach the debugger
|
||||
}
|
||||
|
||||
private static void DoInitialLoadOfScript(this CVRLuaClientBehaviour behaviour)
|
||||
{
|
||||
behaviour.LogInfo("[CVRLuaToolsExtension] Interpreting " + behaviour.ScriptName + "...\n");
|
||||
behaviour.LoadAndRunScript(behaviour.ScriptText); // fucking slow
|
||||
behaviour.PopulateScriptGlobalFunctionsCache(behaviour.script.Globals); // tbh don't think we need to clear in first place
|
||||
behaviour.HandleMessageQueueEntries(); // shouldn't be any
|
||||
behaviour.InitialCodeRun = true;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using System.Text;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI.CCK.Components;
|
||||
using ABI.CCK.Components.ScriptableObjects;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.CVRLuaToolsExtension;
|
||||
|
||||
public static class LuaHotReloadManager
|
||||
{
|
||||
// (asset id + lua component id) -> index is component reference
|
||||
private static readonly List<int> s_CombinedKeys = new();
|
||||
private static readonly List<CVRLuaClientBehaviour> s_LuaComponentInstances = new();
|
||||
|
||||
#region Game Events
|
||||
|
||||
public static void OnCVRLuaBaseBehaviourLoadAndRunScript(CVRLuaClientBehaviour clientBehaviour)
|
||||
{
|
||||
if (!clientBehaviour.IsScriptEligibleForHotReload())
|
||||
return;
|
||||
|
||||
// check if the component is already in the list (shouldn't happen)
|
||||
if (s_LuaComponentInstances.Contains(clientBehaviour))
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Warning($"[LuaHotReloadManager] Script already added: {clientBehaviour.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
// combine the assetId and instanceId into a single key, so multiple instances of the same script can be tracked
|
||||
string assetId = clientBehaviour.GetAssetIdFromScript();
|
||||
int instanceId = GetGameObjectPathHashCode(clientBehaviour.transform);
|
||||
int combinedKey = GenerateCombinedKey(assetId.GetHashCode(), instanceId);
|
||||
|
||||
s_CombinedKeys.Add(combinedKey);
|
||||
s_LuaComponentInstances.Add(clientBehaviour);
|
||||
|
||||
CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Added script: {clientBehaviour.name}");
|
||||
}
|
||||
|
||||
public static void OnCVRLuaBaseBehaviourDestroy(CVRLuaClientBehaviour clientBehaviour)
|
||||
{
|
||||
if (!clientBehaviour.IsScriptEligibleForHotReload())
|
||||
return;
|
||||
|
||||
if (!s_LuaComponentInstances.Contains(clientBehaviour))
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Warning($"[LuaHotReloadManager] Eligible for Hot Reload script destroyed without being tracked first: {clientBehaviour.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = s_LuaComponentInstances.IndexOf(clientBehaviour);
|
||||
s_CombinedKeys.RemoveAt(index);
|
||||
s_LuaComponentInstances.RemoveAt(index);
|
||||
|
||||
CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Removed script: {clientBehaviour.name}");
|
||||
}
|
||||
|
||||
public static void OnReceiveUpdatedScript(ScriptInfo info)
|
||||
{
|
||||
int combinedKey = GenerateCombinedKey(info.AssetId.GetHashCode(), info.LuaComponentId);
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < s_CombinedKeys.Count; i++)
|
||||
{
|
||||
if (combinedKey != s_CombinedKeys[i])
|
||||
continue;
|
||||
|
||||
CVRLuaClientBehaviour clientBehaviour = s_LuaComponentInstances[i];
|
||||
|
||||
if (clientBehaviour.asset.m_ScriptPath == info.ScriptPath)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path match, updating script.");
|
||||
clientBehaviour.asset.name = info.ScriptName;
|
||||
clientBehaviour.asset.m_ScriptText = info.ScriptText;
|
||||
clientBehaviour.Restart();
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path mismatch, creating new script.");
|
||||
|
||||
clientBehaviour.asset = null;
|
||||
clientBehaviour.asset = ScriptableObject.CreateInstance<CVRLuaScript>();
|
||||
|
||||
clientBehaviour.asset.name = info.ScriptName;
|
||||
clientBehaviour.asset.m_ScriptPath = info.ScriptPath;
|
||||
clientBehaviour.asset.m_ScriptText = info.ScriptText;
|
||||
|
||||
clientBehaviour.Restart();
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
CohtmlHud.Instance.ViewDropTextImmediate("(Local) CVRLuaTools", "Received script update", "Reloaded script: " + info.ScriptName);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Game Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static int GenerateCombinedKey(int assetId, int instanceId)
|
||||
{
|
||||
return (assetId << 16) | instanceId; // yes
|
||||
}
|
||||
|
||||
private static int GetGameObjectPathHashCode(Transform transform)
|
||||
{
|
||||
// both CVRAvatar & CVRSpawnable *should* have an asset info component
|
||||
Transform rootComponentTransform = null;
|
||||
CVRAssetInfo rootComponent = transform.GetComponentInParent<CVRAssetInfo>(true);
|
||||
if (rootComponent != null && rootComponent.type != CVRAssetInfo.AssetType.World) // ignore if under world instance
|
||||
rootComponentTransform = rootComponent.transform;
|
||||
|
||||
// easy case, no need to crawl up the hierarchy
|
||||
if (rootComponentTransform == transform)
|
||||
return 581452743; // hash code for "[Root]"
|
||||
|
||||
StringBuilder pathBuilder = new(transform.name);
|
||||
Transform parentTransform = transform.parent;
|
||||
|
||||
while (parentTransform != null)
|
||||
{
|
||||
// reached root component
|
||||
// due to object loader renaming root, we can't rely on transform name, so we use "[Root]" instead
|
||||
if (parentTransform == rootComponentTransform)
|
||||
{
|
||||
pathBuilder.Insert(0, "[Root]/");
|
||||
break;
|
||||
}
|
||||
|
||||
pathBuilder.Insert(0, parentTransform.name + "/");
|
||||
parentTransform = parentTransform.parent;
|
||||
}
|
||||
|
||||
string path = pathBuilder.ToString();
|
||||
|
||||
//Debug.Log($"[LuaComponentManager] Path: {path}");
|
||||
|
||||
return path.GetHashCode();
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using ABI_RC.Core;
|
||||
|
||||
namespace NAK.CVRLuaToolsExtension.NamedPipes;
|
||||
|
||||
public class NamedPipeServer : Singleton<NamedPipeServer>
|
||||
{
|
||||
private const string PipeName = "UnityPipe";
|
||||
|
||||
public async void StartListening()
|
||||
{
|
||||
Stopwatch stopwatch = new();
|
||||
while (!CommonTools.IsQuitting)
|
||||
{
|
||||
await using NamedPipeServerStream namedPipeServer = new(PipeName, PipeDirection.In);
|
||||
try
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Msg("Waiting for client...");
|
||||
await namedPipeServer.WaitForConnectionAsync();
|
||||
stopwatch.Restart();
|
||||
if (!namedPipeServer.IsConnected)
|
||||
continue;
|
||||
|
||||
//Debug.Log("Client connected.");
|
||||
|
||||
// receive and process the ScriptInfo
|
||||
ScriptInfo receivedScript = await ReceiveScriptInfo(namedPipeServer);
|
||||
ProcessScriptInfo(receivedScript);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CVRLuaToolsExtensionMod.Logger.Error(e);
|
||||
}
|
||||
|
||||
if (namedPipeServer.IsConnected)
|
||||
{
|
||||
namedPipeServer.Disconnect();
|
||||
CVRLuaToolsExtensionMod.Logger.Msg("Client disconnected.");
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
CVRLuaToolsExtensionMod.Logger.Msg($"Elapsed time: {stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
CVRLuaToolsExtensionMod.Logger.Msg("Quitting...");
|
||||
}
|
||||
|
||||
private async Task<ScriptInfo> ReceiveScriptInfo(NamedPipeServerStream stream)
|
||||
{
|
||||
// Read LuaComponentId (4 bytes for an int)
|
||||
byte[] intBuffer = new byte[4];
|
||||
await stream.ReadAsync(intBuffer, 0, intBuffer.Length);
|
||||
int luaComponentId = BitConverter.ToInt32(intBuffer, 0);
|
||||
|
||||
string assetId = await ReadStringAsync(stream);
|
||||
//CVRLuaToolsExtensionMod.Logger.Msg($"Received assetId: {assetId}");
|
||||
|
||||
string scriptName = await ReadStringAsync(stream);
|
||||
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptName: {scriptName}");
|
||||
|
||||
string scriptPath = await ReadStringAsync(stream);
|
||||
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptPath: {scriptPath}");
|
||||
|
||||
string scriptText = await ReadStringAsync(stream);
|
||||
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptText: {scriptText}");
|
||||
|
||||
//Debug.Log("ScriptInfo received.");
|
||||
|
||||
return new ScriptInfo
|
||||
{
|
||||
LuaComponentId = luaComponentId,
|
||||
AssetId = assetId,
|
||||
ScriptName = scriptName,
|
||||
ScriptPath = scriptPath,
|
||||
ScriptText = scriptText
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<string> ReadStringAsync(Stream stream)
|
||||
{
|
||||
// Define a buffer size and initialize a memory stream
|
||||
byte[] buffer = new byte[1024];
|
||||
using MemoryStream memoryStream = new();
|
||||
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
memoryStream.Write(buffer, 0, bytesRead);
|
||||
//CVRLuaToolsExtensionMod.Logger.Msg($"Read {bytesRead} bytes.");
|
||||
if (bytesRead < buffer.Length) break; // Assuming no partial writes
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
private static void ProcessScriptInfo(ScriptInfo scriptInfo)
|
||||
{
|
||||
// Handle the received ScriptInfo data
|
||||
//Debug.Log($"Received ScriptInfo: {scriptInfo.AssetId}, {scriptInfo.LuaComponentId}, {scriptInfo.ScriptName}");
|
||||
|
||||
LuaHotReloadManager.OnReceiveUpdatedScript(scriptInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace NAK.CVRLuaToolsExtension;
|
||||
|
||||
[Serializable]
|
||||
public struct ScriptInfo
|
||||
{
|
||||
public string AssetId; // we will reload all scripts with this asset id
|
||||
public int LuaComponentId; // the target lua component id
|
||||
|
||||
public string ScriptName; // the new script name
|
||||
public string ScriptPath;
|
||||
public string ScriptText; // the new script text
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue