diff --git a/CHANGELOG.md b/CHANGELOG.md index 67661f08..2eb12d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Compatibility - AsyncOperation's InvokeCompletionEvent is only disabled for tracked AsyncOperation instances +- Implemented LoadScene forcing pending LoadSceneAsync to be all loaded +- Fixed accidentally obliterating return value of LoadScene instances # [v0.6.0] - 2024-11-18 diff --git a/UniTAS/Patcher/Implementations/PatchReverseInvoker.cs b/UniTAS/Patcher/Implementations/PatchReverseInvoker.cs index 91bf7d15..fbaef174 100644 --- a/UniTAS/Patcher/Implementations/PatchReverseInvoker.cs +++ b/UniTAS/Patcher/Implementations/PatchReverseInvoker.cs @@ -19,6 +19,14 @@ public void Invoke(Action method) _invoking.Value = false; } + public void Invoke(Action method, T1 arg1) + { + _invoking.Value = true; + method(arg1); + _invoking.Value = false; + } + + public TRet Invoke(Func method) { _invoking.Value = true; diff --git a/UniTAS/Patcher/Patches/Harmony/UnityInit/SceneManagerAsyncLoadPatch.cs b/UniTAS/Patcher/Patches/Harmony/UnityInit/SceneManagerAsyncLoadPatch.cs index 35d73ce1..3e038063 100644 --- a/UniTAS/Patcher/Patches/Harmony/UnityInit/SceneManagerAsyncLoadPatch.cs +++ b/UniTAS/Patcher/Patches/Harmony/UnityInit/SceneManagerAsyncLoadPatch.cs @@ -6,6 +6,7 @@ using UniTAS.Patcher.Implementations.UnitySafeWrappers.SceneManagement; using UniTAS.Patcher.Interfaces.Patches.PatchTypes; using UniTAS.Patcher.Models.UnitySafeWrappers.SceneManagement; +using UniTAS.Patcher.Services; using UniTAS.Patcher.Services.Logging; using UniTAS.Patcher.Services.UnityAsyncOperationTracker; using UniTAS.Patcher.Services.UnityEvents; @@ -56,12 +57,30 @@ public class SceneManagerAsyncLoadPatch private static readonly ILogger Logger = ContainerStarter.Kernel.GetInstance(); + private static readonly IPatchReverseInvoker ReverseInvoker = + ContainerStarter.Kernel.GetInstance(); + private static bool AsyncSceneLoad(bool mustCompleteNextFrame, string sceneName, int sceneBuildIndex, - object parameters, bool? isAdditive, AsyncOperation __result) + object parameters, bool? isAdditive, ref AsyncOperation __result) { + // everything goes through here, so yeah why not + if (ReverseInvoker.Invoking) + return true; + + __result = new(); + if (mustCompleteNextFrame) { SceneLoadInvoke.SceneLoadCall(); + + // uh oh, time to do the wacky thing unity does!!! + // + // https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html + /* + * Because loading is set to complete in the next rendered frame, calling SceneManager.LoadScene + * forces all previous AsyncOperations to complete, even if AsyncOperation.allowSceneActivation is set to false + */ + SceneLoadTracker.NonAsyncSceneLoad(); return true; } @@ -226,8 +245,7 @@ private static Exception Cleanup(MethodBase original, Exception ex) private static bool Prefix(string sceneName, int sceneBuildIndex, bool isAdditive, bool mustCompleteNextFrame, ref AsyncOperation __result) { - __result = new(); - return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, null, isAdditive, __result); + return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, null, isAdditive, ref __result); } } @@ -254,8 +272,7 @@ private static Exception Cleanup(MethodBase original, Exception ex) private static bool Prefix(bool mustCompleteNextFrame, string sceneName, int sceneBuildIndex, object parameters, ref AsyncOperation __result) { - __result = new(); - return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, parameters, null, __result); + return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, parameters, null, ref __result); } } @@ -275,8 +292,7 @@ private static Exception Cleanup(MethodBase original, Exception ex) private static bool Prefix(string sceneName, int sceneBuildIndex, object parameters, bool mustCompleteNextFrame, ref AsyncOperation __result) { - __result = new(); - return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, parameters, null, __result); + return AsyncSceneLoad(mustCompleteNextFrame, sceneName, sceneBuildIndex, parameters, null, ref __result); } } } \ No newline at end of file diff --git a/UniTAS/Patcher/Services/IPatchReverseInvoker.cs b/UniTAS/Patcher/Services/IPatchReverseInvoker.cs index 8b0e36e2..c9c2099b 100644 --- a/UniTAS/Patcher/Services/IPatchReverseInvoker.cs +++ b/UniTAS/Patcher/Services/IPatchReverseInvoker.cs @@ -6,6 +6,7 @@ public interface IPatchReverseInvoker { bool Invoking { get; } void Invoke(Action method); + void Invoke(Action method, T1 arg1); TRet Invoke(Func method); TRet Invoke(Func method, T arg1); TRet Invoke(Func method, T1 arg1, T2 arg2); diff --git a/UniTAS/Patcher/Services/UnityAsyncOperationTracker/AsyncOperationTracker.cs b/UniTAS/Patcher/Services/UnityAsyncOperationTracker/AsyncOperationTracker.cs index 0a0a5536..83a56537 100644 --- a/UniTAS/Patcher/Services/UnityAsyncOperationTracker/AsyncOperationTracker.cs +++ b/UniTAS/Patcher/Services/UnityAsyncOperationTracker/AsyncOperationTracker.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; using HarmonyLib; using UniTAS.Patcher.Interfaces.DependencyInjection; @@ -18,7 +19,7 @@ namespace UniTAS.Patcher.Services.UnityAsyncOperationTracker; // ReSharper disable once ClassNeverInstantiated.Global [Singleton] -public class AsyncOperationTracker(ISceneWrapper sceneWrapper, ILogger logger) +public class AsyncOperationTracker(ISceneWrapper sceneWrapper, ILogger logger, IPatchReverseInvoker reverseInvoker) : ISceneLoadTracker, IAssetBundleCreateRequestTracker, IAssetBundleRequestTracker, IOnLastUpdateUnconditional, IAsyncOperationIsInvokingOnComplete, IOnPreGameRestart, IOnUpdateActual { @@ -133,6 +134,22 @@ public void AsyncSceneLoad(string sceneName, int sceneBuildIndex, LoadSceneMode new(sceneName, sceneBuildIndex, asyncOperation, loadSceneMode, localPhysicsMode)); } + public void NonAsyncSceneLoad() + { + foreach (var pair in _asyncLoads.Concat(_asyncLoadStalls)) + { + var scene = pair.Value; + logger.LogDebug( + $"force loading scene, name: {scene.SceneName}, index: {scene.SceneBuildIndex}, manually loading in loop"); + reverseInvoker.Invoke(s => sceneWrapper.LoadSceneAsync(s.SceneName, s.SceneBuildIndex, + s.LoadSceneMode, + s.LocalPhysicsMode, true), scene); + _pendingLoadCallbacks.Add(scene.AsyncOperationInstance); + } + + _asyncLoads.Clear(); + } + public void AllowSceneActivation(bool allow, AsyncOperation asyncOperation) { logger.LogDebug($"allow scene activation {allow}, {new StackTrace()}"); diff --git a/UniTAS/Patcher/Services/UnityAsyncOperationTracker/ISceneLoadTracker.cs b/UniTAS/Patcher/Services/UnityAsyncOperationTracker/ISceneLoadTracker.cs index f4a5f86c..4adaf3c4 100644 --- a/UniTAS/Patcher/Services/UnityAsyncOperationTracker/ISceneLoadTracker.cs +++ b/UniTAS/Patcher/Services/UnityAsyncOperationTracker/ISceneLoadTracker.cs @@ -7,7 +7,9 @@ public interface ISceneLoadTracker { void AsyncSceneLoad(string sceneName, int sceneBuildIndex, LoadSceneMode loadSceneMode, LocalPhysicsMode localPhysicsMode, AsyncOperation asyncOperation); - + + void NonAsyncSceneLoad(); + void AsyncSceneUnload(AsyncOperation asyncOperation); void AllowSceneActivation(bool allow, AsyncOperation asyncOperation);