Skip to content

Commit

Permalink
Merge pull request #241 from Eddio0141/234-pause-coroutines-on-monobe…
Browse files Browse the repository at this point in the history
…haviour-pause

234 pause coroutines on monobehaviour pause
  • Loading branch information
Eddio0141 committed Jul 10, 2023
2 parents 23b529d + 68c7162 commit c65fdc1
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 58 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed some games using switch statement skips over UniTAS tracking the end of static constructors
- Fixed not using the right ILCode for returning a default value from a method
- Fixed duplicate AssetBundle loading causing an exception
- Fixed coroutines causing exception on restart

## Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using UniTAS.Patcher.Interfaces.DependencyInjection;
using UniTAS.Patcher.Interfaces.Events.SoftRestart;
using UniTAS.Patcher.Services.Trackers;
using UnityEngine;

namespace UniTAS.Patcher.Implementations.GameRestart;

[Singleton]
public class CoroutinesStopOnRestart : ICoroutineRunningObjectsTracker, IOnPreGameRestart
{
private readonly List<MonoBehaviour> _instances = new();

public void NewCoroutine(MonoBehaviour instance)
{
if (!_instances.Contains(instance) && instance != null)
{
_instances.Add(instance);
}
}

public void OnPreGameRestart()
{
foreach (var coroutine in _instances)
{
if (coroutine == null) continue;
coroutine.StopAllCoroutines();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public GameVideoRenderer(ILogger logger, IFfmpegProcessFactory ffmpegProcessFact

if (!ffmpegProcessFactory.Available)
{
_logger.LogError("ffmpeg not available");
_logger.LogWarning("ffmpeg not available");
Available = false;
return;
}
Expand Down
6 changes: 3 additions & 3 deletions UniTAS/Patcher/Interfaces/TASRenderer/GameRender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,23 @@ public GameRender(ILogger logger, IEnumerable<VideoRenderer> videoRenderers,

if (_videoRenderer == null)
{
_logger.LogError("No video renderer available");
_logger.LogWarning("No video renderer available");
return;
}

_hasVideoRenderer = true;

if (audioRenderer == null)
{
_logger.LogError("No audio renderer available");
_logger.LogWarning("No audio renderer available");
return;
}

_hasAudioRenderer = true;

if (!ffmpegProcessFactory.Available)
{
_logger.LogError("ffmpeg not available");
_logger.LogWarning("ffmpeg not available");
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using UniTAS.Patcher.Interfaces.Patches.PatchTypes;
using UniTAS.Patcher.Services.Trackers;
using UniTAS.Patcher.Utils;
using UnityEngine;

namespace UniTAS.Patcher.Patches.Harmony;

[RawPatch]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class CoroutineRunningObjectsTrackerPatch
{
private static readonly ICoroutineRunningObjectsTracker Tracker =
ContainerStarter.Kernel.GetInstance<ICoroutineRunningObjectsTracker>();

[HarmonyPatch]
private class RunCoroutine
{
private static Exception Cleanup(MethodBase original, Exception ex)
{
return PatchHelper.CleanupIgnoreFail(original, ex);
}

private static IEnumerable<MethodBase> TargetMethods()
{
// just patch them all, duplicate instances will be detected anyway
return AccessTools.GetDeclaredMethods(typeof(MonoBehaviour))
.Where(x => !x.IsStatic && x.Name == "StartCoroutine").Select(x => (MethodBase)x);
}

private static void Prefix(MonoBehaviour __instance)
{
Tracker.NewCoroutine(__instance);
}
}
}
97 changes: 43 additions & 54 deletions UniTAS/Patcher/Patches/Preloader/MonoBehaviourPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,22 +153,6 @@ public class MonoBehaviourPatch : PreloadPatcher
new("OnGUI", new string[0])
};

private static readonly string[] ExcludeNamespaces =
{
// TODO remove this exclusion, i dont know why i added it
"TMPro",
"UnityEngine",
"Unity",
"UniTAS.Plugin",
"UniTAS.Patcher",
"BepInEx"
};

private static readonly string[] IncludeNamespaces =
{
"UnityEngine.AI"
};

public override void Patch(ref AssemblyDefinition assembly)
{
var types = assembly.Modules.SelectMany(m => m.GetAllTypes());
Expand Down Expand Up @@ -200,57 +184,62 @@ public override void Patch(ref AssemblyDefinition assembly)
StaticLogger.Log.LogDebug($"Patching MonoBehaviour type: {type.FullName}");

// method invoke pause
if (!ExcludeNamespaces.Any(type.Namespace.StartsWith) || IncludeNamespaces.Any(type.Namespace.StartsWith))
foreach (var eventMethodPair in PauseEventMethods)
{
foreach (var eventMethodPair in PauseEventMethods)
var eventMethodName = eventMethodPair.Key;

// try finding method with no parameters
var eventMethodsMatch = type.GetMethods().Where(x => x.Name == eventMethodName).ToList();
var foundMethod =
eventMethodsMatch.FirstOrDefault(m => !m.HasParameters);

// ok try finding method with parameters one by one
// it doesn't matter if the method only has part of the parameters, it just matters it comes in the right order
if (foundMethod == null)
{
var foundMethod =
type.Methods.FirstOrDefault(m => m.Name == eventMethodPair.Key && !m.HasParameters);
var eventMethodArgs = eventMethodPair.Value;

if (foundMethod == null)
for (var i = 0; i < eventMethodArgs.Length; i++)
{
for (var i = 0; i < eventMethodPair.Value.Length; i++)
{
var parameterTypes = eventMethodPair.Value.Take(i + 1).ToArray();
foundMethod = type.Methods.FirstOrDefault(m =>
m.Name == eventMethodPair.Key && m.HasParameters &&
m.Parameters.Select(x => x.ParameterType.FullName).SequenceEqual(parameterTypes));

if (foundMethod != null) break;
}
var parameterTypes = eventMethodArgs.Take(i + 1).ToArray();
foundMethod = eventMethodsMatch.FirstOrDefault(m =>
m.HasParameters && m.Parameters.Select(x => x.ParameterType.FullName)
.SequenceEqual(parameterTypes));

if (foundMethod != null) break;
}
}

if (foundMethod == null) continue;
if (foundMethod == null) continue;

StaticLogger.Log.LogDebug($"Patching method for pausing execution {foundMethod.FullName}");
StaticLogger.Log.LogDebug($"Patching method for pausing execution {foundMethod.FullName}");

var il = foundMethod.Body.GetILProcessor();
var firstInstruction = il.Body.Instructions.First();
var il = foundMethod.Body.GetILProcessor();
var firstInstruction = il.Body.Instructions.First();

// return early check
il.InsertBefore(firstInstruction, il.Create(OpCodes.Call, pauseExecutionReference));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Brfalse_S, firstInstruction));
// return early check
il.InsertBefore(firstInstruction, il.Create(OpCodes.Call, pauseExecutionReference));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Brfalse_S, firstInstruction));

// if the return type isn't void, we need to return a default value
if (foundMethod.ReturnType != assembly.MainModule.TypeSystem.Void)
// if the return type isn't void, we need to return a default value
if (foundMethod.ReturnType != assembly.MainModule.TypeSystem.Void)
{
// if value type, we need to return a default value
if (foundMethod.ReturnType.IsValueType)
{
// if value type, we need to return a default value
if (foundMethod.ReturnType.IsValueType)
{
var local = new VariableDefinition(foundMethod.ReturnType);
il.Body.Variables.Add(local);
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldloca_S, local));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Initobj, foundMethod.ReturnType));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldloc_S, local));
}
else
{
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldnull));
}
var local = new VariableDefinition(foundMethod.ReturnType);
il.Body.Variables.Add(local);
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldloca_S, local));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Initobj, foundMethod.ReturnType));
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldloc_S, local));
}
else
{
il.InsertBefore(firstInstruction, il.Create(OpCodes.Ldnull));
}

il.InsertBefore(firstInstruction, il.Create(OpCodes.Ret));
}

il.InsertBefore(firstInstruction, il.Create(OpCodes.Ret));
}

// event methods invoke
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using UnityEngine;

namespace UniTAS.Patcher.Services.Trackers;

public interface ICoroutineRunningObjectsTracker
{
void NewCoroutine(MonoBehaviour instance);
}

0 comments on commit c65fdc1

Please sign in to comment.