This repository has been archived by the owner on Oct 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathOutflow.cs
169 lines (133 loc) · 6.41 KB
/
Outflow.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
using HarmonyLib;
using ResoniteModLoader;
using FrooxEngine;
using System.Reflection;
using System.Collections.Concurrent;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
namespace Outflow;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public class Outflow : ResoniteMod
{
public override string Name => "Outflow";
public override string Author => "Cyro";
public override string Version => typeof(Outflow).Assembly.GetName().Version.ToString();
public override string Link => "https://github.com/RileyGuy/Outflow";
public static ModConfiguration? Config;
public override void OnEngineInit()
{
Harmony harmony = new("net.Cyro.Outflow");
Config = GetConfiguration();
Config?.Save(true);
RuntimeHelpers.RunClassConstructor(typeof(Session_Patches).TypeHandle);
harmony.PatchAll();
}
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
/// <summary>
/// Patches for FrooxEngine.Session
/// </summary>
[HarmonyPatch(typeof(Session))]
public static class Session_Patches
{
/// <summary>
/// Transpiler for <see cref="Session.EnqueueForTransmission(SyncMessage, bool)"/> to add StreamMessages to their own queue and skip inserting them into the normal queue
/// </summary>
/// <param name="instructions"></param>
/// <param name="iLGen"></param>
/// <returns></returns>
[HarmonyTranspiler]
[HarmonyPatch("EnqueueForTransmission")]
public static IEnumerable<CodeInstruction> EnqueueForTransmission_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator iLGen)
{
yield return new(OpCodes.Ldarg_0); // Load the Session instance to pass to the enqueue method
yield return new(OpCodes.Ldarg_1); // Load the SyncMessage to pass to the enqueue method
yield return new(OpCodes.Call, ((Delegate)SessionHelpers.EnqueueStreamForTransmission).Method); // Call the enqueue method
Label targetLabel = iLGen.DefineLabel();
List<CodeInstruction> codes = instructions.ToList();
bool foundJump = false;
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].Calls(typeof(EventWaitHandle).GetMethod("Set")))
{
codes[i + 2].labels.Add(targetLabel); // Assign a label to the 'if' statement that appears right after streams are normally encoded
foundJump = true;
Msg("Successfully found jump point");
break;
}
}
if (!foundJump)
throw new KeyNotFoundException("Could not find jump point, aborting!");
// Jump straight to the 'if' statement and skip enqueueing into the normal message queue if we have a StreamMessage. Ensures stats are still incremented properly
yield return new(OpCodes.Brtrue_S, targetLabel);
// Return the rest of the function, unchanged
foreach (var code in codes)
{
yield return code;
}
Msg("Successfully patched Session.EnqueueForTransmission()");
}
/// <summary>
/// Prefixes Session.Run() to start a thread to process exclusively SyncMessages
/// </summary>
/// <param name="__instance">The session to process StreamMessages for</param>
[HarmonyPrefix]
[HarmonyPatch("Run")]
public static void Run_Prefix(Session __instance)
{
// Private so reflection is required, not really a big deal to do it the easy way since this isn't a hot piece of code
MethodInfo runThread =
typeof(Session)
.GetMethod("RunThreadLoop", BindingFlags.Instance | BindingFlags.NonPublic);
AutoResetEvent ev = new(false);
ConcurrentQueue<SyncMessage> queue = new();
// Start a new thread to process exclusively StreamMessages
Thread streamThread = new(() => runThread.Invoke(__instance, [() => SessionHelpers.StreamLoop(__instance, ev, queue)]))
{
IsBackground = true,
Priority = ThreadPriority.AboveNormal,
Name = "StreamMessage Encoding"
};
streamThread.Start();
// Add the event and the queue to a dictionary so it can be accessed from other methods
__instance.AddStreamQueue(ev, queue);
Msg("Starting StreamMessage processing on a separate thread");
}
/// <summary>
/// Applies a Transpiler patch on <see cref="Session.Dispose"/> to properly shut down the StreamMessage processing thread
/// </summary>
/// <param name="instructions"></param>
[HarmonyTranspiler]
[HarmonyPatch("Dispose")]
public static IEnumerable<CodeInstruction> Dispose_Transpiler(IEnumerable<CodeInstruction> instructions)
{
MethodInfo disposeCall = typeof(Session).GetProperty("IsDisposed").GetSetMethod(true);
foreach (var code in instructions)
{
if (code.Calls(disposeCall))
{
yield return code; // Emit the original IsDisposed setter
yield return new(OpCodes.Ldarg_0); // Load the Session instance to pass to the disposer method
yield return new(OpCodes.Call, ((Delegate)SessionHelpers.DisposeStreamMessageProcessor).Method); // Emit a call to the disposer method
}
else
{
yield return code;
}
}
}
/// <summary>
/// Postfixes the getter for <see cref="Session.MessagesToTransmitCount"/> to include the StreamMessage queue in the statistic to remain accurate
/// </summary>
/// <param name="__instance"></param>
/// <param name="__result"></param>
[HarmonyPostfix]
[HarmonyPatch("MessagesToTransmitCount", MethodType.Getter)]
public static void MessagesToTransmitCount_Postfix(Session __instance, ref int __result)
{
if (SessionHelpers.SessionStreamQueue.TryGetValue(__instance, out var data))
{
__result += data.streamMessagesToSend.Count;
}
}
}
}