[LazyPrune] Initial Release

This commit is contained in:
NotAKidoS 2024-06-04 00:04:57 -05:00
parent 683c407358
commit aa9bc6fbc3
6 changed files with 210 additions and 0 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>LoadedObjectHack</RootNamespace>
</PropertyGroup>
</Project>

128
LazyPrune/Main.cs Normal file
View file

@ -0,0 +1,128 @@
using ABI_RC.Core.IO;
using ABI_RC.Systems.GameEventSystem;
using MelonLoader;
using UnityEngine;
namespace NAK.LazyPrune;
public class LazyPrune : MelonMod
{
private static MelonLogger.Instance Logger;
//private const int MAX_OBJECTS_UNLOADED_AT_ONCE = 5; // just to alleviate hitch on mass destruction
private const float OBJECT_CACHE_TIMEOUT = 3f; // minutes
private static readonly Dictionary<CVRObjectLoader.LoadedObject, float> _loadedObjects = new();
private static string _lastLoadedWorld;
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
// every minute, check for objects to prune
SchedulerSystem.AddJob(CheckForObjectsToPrune, 1f, 60f, -1);
// listen for world load
CVRGameEventSystem.World.OnLoad.AddListener(OnWorldLoaded);
// listen for local avatar load/clear events
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener((avatar) => OnObjectCreated(avatar.loadedObject));
CVRGameEventSystem.Avatar.OnLocalAvatarClear.AddListener((avatar) => OnObjectDestroyed(avatar ? avatar.loadedObject : null));
// listen for remote avatar load/clear events
CVRGameEventSystem.Avatar.OnRemoteAvatarLoad.AddListener((_, avatar) => OnObjectCreated(avatar.loadedObject));
CVRGameEventSystem.Avatar.OnRemoteAvatarClear.AddListener((_, avatar) => OnObjectDestroyed(avatar != null ? avatar.loadedObject : null));
// listen for spawnable instantiate/destroy events
CVRGameEventSystem.Spawnable.OnInstantiate.AddListener((_, spawnable) => OnObjectCreated(spawnable.loadedObject));
CVRGameEventSystem.Spawnable.OnDestroy.AddListener((_, spawnable) => OnObjectDestroyed(spawnable != null ? spawnable.loadedObject : null));
}
#region Game Events
private static void OnWorldLoaded(string guid)
{
if (_lastLoadedWorld != guid)
ForcePrunePendingObjects();
_lastLoadedWorld = guid;
}
private static void OnObjectCreated(CVRObjectLoader.LoadedObject loadedObject)
{
if (loadedObject == null)
return; // uhh
if (_loadedObjects.ContainsKey(loadedObject))
{
_loadedObjects[loadedObject] = -1; // mark as ineligible for pruning
return; // already in cache
}
loadedObject.refCount++; // increment ref count
_loadedObjects.Add(loadedObject, -1); // mark as ineligible for pruning
}
private static void OnObjectDestroyed(CVRObjectLoader.LoadedObject loadedObject)
{
if (loadedObject == null)
return; // handled by AttemptPruneObject
if (loadedObject.refCount > 2)
return; // we added our own ref, so begin death count at 2 (decrements one more after this callback)
if (_loadedObjects.ContainsKey(loadedObject))
_loadedObjects[loadedObject] = Time.time + OBJECT_CACHE_TIMEOUT * 60f;
}
#endregion Game Events
#region Lazy Pruning
private static void ForcePrunePendingObjects()
{
for (int i = _loadedObjects.Count - 1; i >= 0; i--)
{
(CVRObjectLoader.LoadedObject loadedObject, var killTime) = _loadedObjects.ElementAt(i);
if (killTime > 0) AttemptPruneObject(loadedObject); // prune all pending objects
}
}
private static void CheckForObjectsToPrune()
{
//int unloaded = 0;
float time = Time.time;
for (int i = _loadedObjects.Count - 1; i >= 0; i--)
{
(CVRObjectLoader.LoadedObject loadedObject, var killTime) = _loadedObjects.ElementAt(i);
if (!(killTime < time)) continue;
AttemptPruneObject(loadedObject); // prune expired objects
//if (unloaded++ >= MAX_OBJECTS_UNLOADED_AT_ONCE) break; // limit unloads per check
}
}
private static void AttemptPruneObject(CVRObjectLoader.LoadedObject loadedObject)
{
if (loadedObject == null)
{
Logger.Error("Attempted to prune null object. This happens on initial load sometimes.");
return;
}
if (loadedObject.refCount > 1)
{
Logger.Error($"Object {loadedObject.prefabName} has ref count {loadedObject.refCount}, expected 1");
_loadedObjects[loadedObject] = -1; // mark as ineligible for pruning ???
return; // is something somehow holding a reference?
}
Logger.Msg($"Pruning object {loadedObject.prefabName}");
_loadedObjects.Remove(loadedObject); // remove from cache
loadedObject.refCount--; // decrement ref count
if (CVRObjectLoader.Instance != null) // provoke destruction
CVRObjectLoader.Instance.CheckForDestruction(loadedObject);
}
#endregion Lazy Pruning
}

View file

@ -0,0 +1,30 @@
using MelonLoader;
using NAK.LazyPrune.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.LazyPrune))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.LazyPrune))]
[assembly: MelonInfo(
typeof(NAK.LazyPrune.LazyPrune),
nameof(NAK.LazyPrune),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: HarmonyDontPatchAll]
namespace NAK.LazyPrune.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

16
LazyPrune/README.md Normal file
View file

@ -0,0 +1,16 @@
# LazyPrune
Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection.
Unused objects are pruned after 3 minutes, or when loading into a different world.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

24
LazyPrune/format.json Normal file
View file

@ -0,0 +1,24 @@
{
"_id": -1,
"name": "LazyPrune",
"modversion": "1.0.0",
"gameversion": "2024r175",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection.\n\nUnused objects are pruned after 3 minutes, or when loading into a different world.",
"searchtags": [
"cache",
"prune",
"bundle",
"download",
"load",
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r30/LazyPrune.dll",
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune/",
"changelog": "- Initial Release",
"embedcolor": "#1c75f1"
}

View file

@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptingSpoofer", "Scripti
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaTTS", "LuaTTS\LuaTTS.csproj", "{24A069F4-4D69-4ABD-AA16-77765469245B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyPrune", "LazyPrune\LazyPrune.csproj", "{8FA6D481-5801-4E4C-822E-DE561155D22B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -159,6 +161,10 @@ Global
{24A069F4-4D69-4ABD-AA16-77765469245B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24A069F4-4D69-4ABD-AA16-77765469245B}.Release|Any CPU.Build.0 = Release|Any CPU
{8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FA6D481-5801-4E4C-822E-DE561155D22B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FA6D481-5801-4E4C-822E-DE561155D22B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE