mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-01 22:09:23 +00:00
[LazyPrune] Initial Release
This commit is contained in:
parent
683c407358
commit
aa9bc6fbc3
6 changed files with 210 additions and 0 deletions
6
LazyPrune/LazyPrune.csproj
Normal file
6
LazyPrune/LazyPrune.csproj
Normal 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
128
LazyPrune/Main.cs
Normal 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
|
||||
}
|
30
LazyPrune/Properties/AssemblyInfo.cs
Normal file
30
LazyPrune/Properties/AssemblyInfo.cs
Normal 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
16
LazyPrune/README.md
Normal 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
24
LazyPrune/format.json
Normal 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"
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue