Skip to content

Commit

Permalink
Add ability to hook scene preloads for custom operations, Remove prefab
Browse files Browse the repository at this point in the history
support

Remove special prefab support as that's unneeded with the new hooks
system and it was unused.
  • Loading branch information
fifty-six committed Jul 27, 2022
1 parent c2b0ad9 commit 3612f06
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 175 deletions.
10 changes: 9 additions & 1 deletion Assembly-CSharp/IMod.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Modding
Expand All @@ -20,6 +22,12 @@ public interface IMod : ILogger
/// </summary>
/// <returns>A List of tuples containing scene name, object name</returns>
List<(string, string)> GetPreloadNames();

/// <summary>
/// A list of requested scenes to be preloaded and actions to execute on loading of those scenes
/// </summary>
/// <returns>List of tuples containg scene names and the respective actions.</returns>
(string, Func<IEnumerator>)[] PreloadSceneHooks();

/// <summary>
/// Called after preloading of all mods.
Expand Down
7 changes: 7 additions & 0 deletions Assembly-CSharp/Mod.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using JetBrains.Annotations;
Expand Down Expand Up @@ -124,6 +125,12 @@ public string GetName()
return null;
}

/// <summary>
/// A list of requested scenes to be preloaded and actions to execute on loading of those scenes
/// </summary>
/// <returns>List of tuples containg scene names and the respective actions.</returns>
public virtual (string, Func<IEnumerator>)[] PreloadSceneHooks() => Array.Empty<(string, Func<IEnumerator>)>();

/// <inheritdoc />
/// <summary>
/// Called after preloading of all mods.
Expand Down
96 changes: 27 additions & 69 deletions Assembly-CSharp/ModLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,20 @@ Assembly Resolve(object sender, ResolveEventArgs args)

// dict<scene name, list<(mod, list<objectNames>)>
var toPreload = new Dictionary<string, List<(ModInstance, List<string> objectNames)>>();
// dict<scene name, list<(mod, list<objectNames>)>
var preloadPrefabs = new Dictionary<string, List<(ModInstance, List<string> objectNames)>>();
// dict<mod, dict<real scene name, scene name in getpreloadobjects>>
var sceneNamesMap = new Dictionary<ModInstance, Dictionary<string, string>>();
// dict<mod, dict<scene, dict<objName, object>>>
var preloadedObjects = new Dictionary<ModInstance, Dictionary<string, Dictionary<string, GameObject>>>();

// scene -> respective hooks
var sceneHooks = new Dictionary<string, List<Func<IEnumerator>>>();

Logger.APILogger.Log("Creating mod preloads");

// Setup dict of scene preloads
GetPreloads(orderedMods, scenes, toPreload, preloadPrefabs, sceneNamesMap);
if (toPreload.Count > 0 || preloadPrefabs.Count > 0)
GetPreloads(orderedMods, scenes, toPreload, sceneHooks);

if (toPreload.Count > 0)
{
Preloader pld = coroutineHolder.GetOrAddComponent<Preloader>();
yield return pld.Preload(toPreload, preloadPrefabs, sceneNamesMap, preloadedObjects);
yield return pld.Preload(toPreload, preloadedObjects, sceneHooks);
}

foreach (ModInstance mod in orderedMods)
Expand Down Expand Up @@ -285,12 +285,12 @@ Assembly Resolve(object sender, ResolveEventArgs args)
UObject.Destroy(coroutineHolder.gameObject);
}

private static void GetPreloads(
private static void GetPreloads
(
ModInstance[] orderedMods,
List<string> scenes,
Dictionary<string, List<(ModInstance, List<string> objectNames)>> toPreload,
Dictionary<string, List<(ModInstance, List<string> objectNames)>> preloadedPrefabs,
Dictionary<ModInstance, Dictionary<string, string>> sceneNamesMap
Dictionary<string, List<Func<IEnumerator>>> sceneHooks
)
{
foreach (var mod in orderedMods)
Expand All @@ -311,18 +311,27 @@ Dictionary<ModInstance, Dictionary<string, string>> sceneNamesMap
{
Logger.APILogger.LogError($"Error getting preload names for mod {mod.Name}\n" + ex);
}
if (preloadNames == null)

try
{
continue;
foreach (var (scene, hook) in mod.Mod.PreloadSceneHooks())
{
if (!sceneHooks.TryGetValue(scene, out var hooks))
sceneHooks[scene] = hooks = new List<Func<IEnumerator>>();

hooks.Add(hook);
}
}
catch (Exception ex)
{
Logger.APILogger.LogError($"Error getting preload hooks for mod {mod.Name}\n" + ex);
}

if (preloadNames == null)
continue;

// dict<scene, list<objects>>
Dictionary<string, List<string>> modPreloads = new();
// dict<scene, list<objects>>
Dictionary<string, List<string>> prefabPreloads = new();

if (!sceneNamesMap.TryGetValue(mod, out var sceneNames))
sceneNamesMap[mod] = sceneNames = new Dictionary<string, string>();

foreach ((string scene, string obj) in preloadNames)
{
Expand All @@ -331,43 +340,6 @@ Dictionary<ModInstance, Dictionary<string, string>> sceneNamesMap
Logger.APILogger.LogWarn($"Mod `{mod.Mod.GetName()}` passed null values to preload");
continue;
}
if (scene.StartsWith("sharedassets") || scene.Equals("resources"))
{
var sceneName = scene;

if (!sceneName.Equals("resources"))
{
if (!int.TryParse(sceneName.Substring(12), out var sceneId))
continue;

if (sceneId >= UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings)
{
Logger.APILogger.LogWarn(
$"Mod `{mod.Mod.GetName()}` attempted preload from non-existent assets file `{scene}.assets`"
);
continue;
}

string origSceneName = sceneName;

sceneName = Path.GetFileNameWithoutExtension(
SceneUtility.GetScenePathByBuildIndex(sceneId)
);

sceneNames[sceneName] = origSceneName;
}

if (!prefabPreloads.TryGetValue(sceneName, out List<string> prefabs))
{
prefabs = new List<string>();
prefabPreloads[sceneName] = prefabs;
}

prefabs.Add(obj);

Logger.APILogger.LogFine($"Found prefab `{scene}.{obj}`");
continue;
}

if (!scenes.Contains(scene))
{
Expand Down Expand Up @@ -401,20 +373,6 @@ Dictionary<ModInstance, Dictionary<string, string>> sceneNamesMap
scenePreloads.Add((mod, objects));
toPreload[scene] = scenePreloads;
}

foreach ((string scene, List<string> objects) in prefabPreloads)
{
if (!preloadedPrefabs.TryGetValue(scene, out var scenePreloads))
{
scenePreloads = new List<(ModInstance, List<string>)>();
preloadedPrefabs[scene] = scenePreloads;
}

Logger.APILogger.LogFine($"`{mod.Name}` mod preloads {objects.Count} prefabs in the `{scene}` scene");

scenePreloads.Add((mod, objects));
preloadedPrefabs[scene] = scenePreloads;
}
}
}

Expand Down
133 changes: 28 additions & 105 deletions Assembly-CSharp/Preloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ internal class Preloader : MonoBehaviour
public IEnumerator Preload
(
Dictionary<string, List<(ModLoader.ModInstance, List<string>)>> toPreload,
Dictionary<string, List<(ModLoader.ModInstance, List<string>)>> preloadPrefabs,
Dictionary<ModLoader.ModInstance, Dictionary<string, string>> preloadSceneNameMap,
Dictionary<ModLoader.ModInstance, Dictionary<string, Dictionary<string, GameObject>>> preloadedObjects
Dictionary<ModLoader.ModInstance, Dictionary<string, Dictionary<string, GameObject>>> preloadedObjects,
Dictionary<string, List<Func<IEnumerator>>> sceneHooks
)
{
MuteAllAudio();
Expand All @@ -46,7 +45,7 @@ public IEnumerator Preload

CreateLoadingBar();

yield return DoPreload(toPreload, preloadPrefabs, preloadSceneNameMap, preloadedObjects);
yield return DoPreload(toPreload, preloadedObjects, sceneHooks);

yield return CleanUpPreloading();

Expand Down Expand Up @@ -158,27 +157,21 @@ private void UpdateLoadingBarProgress(float progress)
/// <returns></returns>
private IEnumerator DoPreload
(
Dictionary<string, List<(ModLoader.ModInstance, List<string>)>> toPreload,
IReadOnlyDictionary<string, List<(ModLoader.ModInstance, List<string>)>> preloadPrefabs,
IReadOnlyDictionary<ModLoader.ModInstance, Dictionary<string, string>> preloadSceneNameMap,
IDictionary<ModLoader.ModInstance, Dictionary<string, Dictionary<string, GameObject>>> preloadedObjects
Dictionary<string, List<(ModLoader.ModInstance Mod, List<string> Preloads)>> toPreload,
IDictionary<ModLoader.ModInstance, Dictionary<string, Dictionary<string, GameObject>>> preloadedObjects,
Dictionary<string, List<Func<IEnumerator>>> sceneHooks
)
{
List<string> sceneNames = toPreload.Keys.ToList();
List<string> sceneNames = toPreload.Keys.Union(sceneHooks.Keys).ToList();
Dictionary<string, int> scenePriority = new();
Dictionary<string, (AsyncOperation load, AsyncOperation unload)> sceneAsyncOperationHolder = new();

sceneNames.AddRange(preloadPrefabs.Select(kvp => kvp.Key));

foreach (string sceneName in sceneNames)
{
int priority = 0;

if (toPreload.TryGetValue(sceneName, out var preloadObjs0))
priority += preloadObjs0.Select(x => x.Item2.Count).Sum();

if (preloadPrefabs.TryGetValue(sceneName, out var preloadObjs1))
priority += preloadObjs1.Select(x => x.Item2.Count).Sum();
if (toPreload.TryGetValue(sceneName, out var requests))
priority += requests.Select(x => x.Preloads.Count).Sum();

scenePriority[sceneName] = priority;
sceneAsyncOperationHolder[sceneName] = (null, null);
Expand Down Expand Up @@ -210,58 +203,24 @@ out Dictionary<string, GameObject> modScenePreloadedObjects

var preloadOperationQueue = new List<AsyncOperation>(5);

void GetPreloadObjectsOperation(string sceneName)
IEnumerator GetPreloadObjectsOperation(string sceneName)
{
Scene scene = USceneManager.GetSceneByName(sceneName);

GameObject[] rootObjects = scene.GetRootGameObjects();

foreach (var go in rootObjects)
go.SetActive(false);
if (preloadPrefabs.TryGetValue(sceneName, out var sharedObjects))

if (sceneHooks.TryGetValue(scene.name, out List<Func<IEnumerator>> hooks))
{
List<string> allObjects = sharedObjects.SelectMany(x => x.Item2).ToList();
Dictionary<string, GameObject> objectsMap = new();

foreach (
var obj in Resources.FindObjectsOfTypeAll<GameObject>()
.Where(x => !x.scene.IsValid())
.Where(x => x.transform.parent == null)
.Where(x => allObjects.Contains(x.name))
)
{
objectsMap[obj.name] = obj;
allObjects.Remove(obj.name);
}

foreach ((ModLoader.ModInstance mod, List<string> objNames) in sharedObjects)
{
Logger.APILogger.LogFine($"Fetching prefab for mod \"{mod.Mod.GetName()}\"");

string sn = preloadSceneNameMap[mod][sceneName];

var modScenePreloadedObjects = GetModScenePreloadedObjects(mod, sn);

foreach (string objName in objNames)
{
Logger.APILogger.LogFine($"Fetching prefab \"{objName}\"");

if (!objectsMap.TryGetValue(objName, out GameObject obj))
{
Logger.APILogger.LogWarn(
$"Could not find prefab \"{objName}\" in scene \"{scene}\"," + $" requested by mod `{mod.Mod.GetName()}`"
);
continue;
}

modScenePreloadedObjects[objName] = obj;
}
}
// ToArray to force a strict select, that way we start them all simultaneously
foreach (IEnumerator hook in hooks.Select(x => x()).ToArray())
yield return hook;
}

if (!toPreload.TryGetValue(sceneName, out var sceneObjects))
return;
yield break;

// Fetch object names to preload
foreach ((ModLoader.ModInstance mod, List<string> objNames) in sceneObjects)
Expand Down Expand Up @@ -320,19 +279,24 @@ void CleanupPreloadOperation(string sceneName)

void StartPreloadOperation(string sceneName)
{
Logger.APILogger.LogFine($"Loading scene \"{sceneName}\"");
IEnumerator DoLoad(AsyncOperation load)
{
yield return load;

preloadOperationQueue.Remove(load);
yield return GetPreloadObjectsOperation(sceneName);
CleanupPreloadOperation(sceneName);
}

Logger.APILogger.LogFine($"Loading scene \"{sceneName}\"");

AsyncOperation loadOp = USceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

StartCoroutine(DoLoad(loadOp));

sceneAsyncOperationHolder[sceneName] = (loadOp, null);

loadOp.priority = scenePriority[sceneName];
loadOp.completed += _ =>
{
preloadOperationQueue.Remove(loadOp);
GetPreloadObjectsOperation(sceneName);
CleanupPreloadOperation(sceneName);
};

preloadOperationQueue.Add(loadOp);
}
Expand Down Expand Up @@ -362,47 +326,6 @@ void StartPreloadOperation(string sceneName)
UpdateLoadingBarProgress(sceneProgressAverage);
}

if (!preloadPrefabs.TryGetValue("resources", out var prefabs))
{
UpdateLoadingBarProgress(1f);
yield break;
}

List<string> allObjects = prefabs.SelectMany(x => x.Item2).ToList();
Dictionary<string, GameObject> objectsMap = new();

foreach (
var obj in Resources.LoadAll<GameObject>("")
.Where(x => !x.scene.IsValid())
.Where(x => x.transform.parent == null)
.Where(x => allObjects.Contains(x.name))
)
{
objectsMap[obj.name] = obj;
allObjects.Remove(obj.name);
}

foreach ((ModLoader.ModInstance mod, List<string> objNames) in prefabs)
{
Logger.APILogger.LogFine($"Fetching prefabs for mod \"{mod.Mod.GetName()}\"");

var preloads = GetModScenePreloadedObjects(mod, "resources");

foreach (string objName in objNames)
{
Logger.APILogger.LogFine($"Fetching prefab \"{objName}\"");

if (!objectsMap.TryGetValue(objName, out GameObject obj))
{
Logger.APILogger.LogWarn($"Could not find prefab \"{objName}\" in \"resources.assets\", requested by mod `{mod.Mod.GetName()}`");

continue;
}

preloads[objName] = obj;
}
}

UpdateLoadingBarProgress(1.0f);
}

Expand Down

0 comments on commit 3612f06

Please sign in to comment.