├── .gitignore
├── LICENSE.md
├── LICENSE.md.meta
├── README.md
├── README.md.meta
├── ThreadingPatcher.meta
├── ThreadingPatcher
├── Editor.meta
├── Editor
│ ├── WebGLThreadingPatcher.asmdef
│ ├── WebGLThreadingPatcher.asmdef.meta
│ ├── WebGLThreadingPatcher.cs
│ └── WebGLThreadingPatcher.cs.meta
├── Plugins.meta
├── Plugins
│ ├── SystemThreadingTimer.jslib
│ └── SystemThreadingTimer.jslib.meta
├── Runtime.meta
└── Runtime
│ ├── TimerRunner.cs
│ ├── TimerRunner.cs.meta
│ ├── WebGLThreadingPatcher.Runtime.asmdef
│ └── WebGLThreadingPatcher.Runtime.asmdef.meta
├── package.json
└── package.json.meta
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Asset meta data should only be ignored when the corresponding asset is also ignored
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # Autogenerated Jetbrains Rider plugin
20 | [Aa]ssets/Plugins/Editor/JetBrains*
21 |
22 | # Visual Studio cache directory
23 | .vs/
24 |
25 | # Gradle cache directory
26 | .gradle/
27 |
28 | # Autogenerated VS/MD/Consulo solution and project files
29 | ExportedObj/
30 | .consulo/
31 | *.csproj
32 | *.unityproj
33 | *.sln
34 | *.suo
35 | *.tmp
36 | *.user
37 | *.userprefs
38 | *.pidb
39 | *.booproj
40 | *.svd
41 | *.pdb
42 | *.mdb
43 | *.opendb
44 | *.VC.db
45 |
46 | # Unity3D generated meta files
47 | *.pidb.meta
48 | *.pdb.meta
49 | *.mdb.meta
50 |
51 | # Unity3D generated file on crash reports
52 | sysinfo.txt
53 |
54 | # Builds
55 | *.apk
56 | *.unitypackage
57 |
58 | # Crashlytics generated file
59 | crashlytics-build.properties
60 |
61 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2021 Mattias Edlund
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 45ca8990b5f14f14801eaf02e0f140bd
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebGL Threading Patcher
2 |
3 | This plugin makes more async await code works in Unity WebGL.
4 | For instance all of following will work:
5 | ```charp
6 | task.ContinueWith(t => Debug.Log("ContinuWith works"));
7 |
8 | Task.Run(() => Debug.Log("Task.Run works!"));
9 |
10 | await task.ConfigureAwait(false);
11 |
12 | await Task.Delay(1000);
13 | ```
14 |
15 | ## How Does it work?
16 |
17 | This plug-in use IIl2CppProcessor callback to hijack mscorelib and change some methods implementation.
18 | It change ThreadPool methods that enqueue work items to delegate work to SynchronizationContext so all items will be executed in same thread.
19 | Also it patch Timer implementation to use Javascript timer functionality.
20 |
21 | ## Limitations
22 |
23 | All tasks will be executed by just one thread so any blocking calls will freeze whole application. Basically it similar to async await behavior in Blazor.
24 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b79445e2c47eb484089dccb488228aec
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/ThreadingPatcher.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6736eb05368ff3942bc06026df229e62
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 44bdc77b10d040149a51fdbc0e002f7a
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Editor/WebGLThreadingPatcher.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebGLThreadingPatcher.Editor",
3 | "rootNamespace": "WebGLThreadingPatcher.Editor",
4 | "references": [],
5 | "includePlatforms": [
6 | "Editor"
7 | ],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": true,
11 | "precompiledReferences": [
12 | "Mono.Cecil.dll",
13 | "Mono.Cecil.Mdb.dll",
14 | "Mono.Cecil.Pdb.dll",
15 | "Mono.Cecil.Rocks.dll"
16 | ],
17 | "autoReferenced": true,
18 | "defineConstraints": [],
19 | "versionDefines": [],
20 | "noEngineReferences": false
21 | }
--------------------------------------------------------------------------------
/ThreadingPatcher/Editor/WebGLThreadingPatcher.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 003589d5576fedd4b997cd470b948555
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs:
--------------------------------------------------------------------------------
1 | using Mono.Cecil;
2 | using Mono.Cecil.Cil;
3 | using System.IO;
4 | using System.Linq;
5 | using UnityEditor;
6 | using UnityEditor.Build;
7 | using UnityEditor.Build.Reporting;
8 | using UnityEngine;
9 |
10 | namespace WebGLThreadingPatcher.Editor
11 | {
12 | public class ThreadingPatcher : IPostBuildPlayerScriptDLLs
13 | {
14 | public int callbackOrder => 0;
15 |
16 | public void OnPostBuildPlayerScriptDLLs(BuildReport report)
17 | {
18 | if (report.summary.platform != BuildTarget.WebGL)
19 | return;
20 |
21 | var mscorLibDll = report.files.FirstOrDefault(f => f.path.EndsWith("mscorlib.dll")).path;
22 | if (mscorLibDll == null)
23 | {
24 | Debug.LogError("Can't find mscorlib.dll in build dll files");
25 | return;
26 | }
27 |
28 | using (var assembly = AssemblyDefinition.ReadAssembly(Path.Combine(mscorLibDll), new ReaderParameters(ReadingMode.Immediate) { ReadWrite = true }))
29 | {
30 | var mainModule = assembly.MainModule;
31 | if (!TryGetTypes(mainModule, out var threadPool, out var synchronizationContext, out var postCallback, out var waitCallback, out var taskExecutionItem, out var timeScheduler))
32 | return;
33 |
34 | PatchThreadPool(mainModule, threadPool, synchronizationContext, postCallback, waitCallback, taskExecutionItem);
35 |
36 | #if !UNITY_2021_2_OR_NEWER
37 | PatchTimerScheduler(mainModule, timeScheduler, threadPool, waitCallback);
38 | #endif
39 | assembly.Write();
40 | }
41 | }
42 |
43 | [MenuItem("Tools/PatchDll")]
44 | public static void TestMethod()
45 | {
46 | using (var assembly = AssemblyDefinition.ReadAssembly("D:\\mscorlib.dll", new ReaderParameters(ReadingMode.Immediate) { ReadWrite = true }))
47 | {
48 | var mainModule = assembly.MainModule;
49 | if (!TryGetTypes(mainModule, out var threadPool, out var synchronizationContext, out var postCallback, out var waitCallback, out var taskExecutionItem, out var timeScheduler))
50 | return;
51 |
52 | PatchThreadPool(mainModule, threadPool, synchronizationContext, postCallback, waitCallback, taskExecutionItem);
53 |
54 | #if !UNITY_2021_2_OR_NEWER
55 | PatchTimerScheduler(mainModule, timeScheduler, threadPool, waitCallback);
56 | #endif
57 | assembly.Write("D:\\mscorlib_p.dll");
58 | }
59 | }
60 |
61 | private static void PatchThreadPool(ModuleDefinition mainModule, TypeDefinition threadPool, TypeDefinition synchronizationContext, TypeDefinition postCallback, TypeDefinition waitCallback, TypeDefinition threadPoolWorkItem)
62 | {
63 | var taskExecutionCallcack = AddTaskExecutionPostCallback(threadPool, threadPoolWorkItem, mainModule);
64 |
65 | foreach (var methodDefinition in threadPool.Methods)
66 | {
67 | switch (methodDefinition.Name)
68 | {
69 | case "QueueUserWorkItem" when methodDefinition.HasGenericParameters:
70 | case "UnsafeQueueUserWorkItem" when methodDefinition.HasGenericParameters:
71 | PatchQueueUserWorkItemGeneric(mainModule, methodDefinition, synchronizationContext, waitCallback, postCallback);
72 | break;
73 | case "QueueUserWorkItem":
74 | case "UnsafeQueueUserWorkItem":
75 | PatchQueueUserWorkItem(mainModule, methodDefinition, synchronizationContext, waitCallback, postCallback);
76 | break;
77 | case "UnsafeQueueCustomWorkItem":
78 | PatchUnsafeQueueCustomWorkItem(mainModule, methodDefinition, synchronizationContext, taskExecutionCallcack, postCallback);
79 | break;
80 | case "TryPopCustomWorkItem":
81 | PatchTryPopCustomWorkItem(methodDefinition);
82 | break;
83 | case "GetAvailableThreads":
84 | case "GetMaxThreads":
85 | case "GetMinThreads":
86 | PatchGetThreads(methodDefinition);
87 | break;
88 | case "SetMaxThreads":
89 | case "SetMinThreads":
90 | PatchSetThreads(methodDefinition);
91 | break;
92 | }
93 | }
94 | }
95 |
96 | ///
97 | /// Creates following class
98 | ///
99 | /// class <>_GenericWrapper
100 | /// {
101 | /// public Action callabck;
102 | ///
103 | /// public void Invoke(object state)
104 | /// {
105 | /// callback((T)state);
106 | /// }
107 | /// }
108 | ///
109 | ///
110 | ///
111 | ///
112 | private static TypeDefinition GetGenericToObjectDelegateWrapper(ModuleDefinition moduleDefinition)
113 | {
114 | const string Namespace = "System.Threading";
115 | const string ClassName = "<>_GenericWrapper";
116 | if (moduleDefinition.Types.FirstOrDefault(t => t.Namespace == Namespace && t.Name == ClassName) is { } wrapper)
117 | {
118 | return wrapper;
119 | }
120 |
121 | var genericWrapper = new TypeDefinition(Namespace, ClassName, TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
122 | var genericParameter = new GenericParameter("T", genericWrapper);
123 | genericWrapper.GenericParameters.Add(genericParameter);
124 |
125 | var (actionOfT, callbackField) = CreateCallbackField(moduleDefinition, genericWrapper, genericParameter);
126 | var ctor = CreateConstructor(moduleDefinition, callbackField);
127 | var wrapMethod = CreateInvokeMethod(moduleDefinition, genericParameter, actionOfT, callbackField);
128 |
129 | genericWrapper.Methods.Add(ctor);
130 | genericWrapper.Methods.Add(wrapMethod);
131 |
132 | moduleDefinition.Types.Add(genericWrapper);
133 | return genericWrapper;
134 |
135 | static (TypeReference, FieldReference) CreateCallbackField(ModuleDefinition moduleDefinition, TypeDefinition genericWrapper, GenericParameter genericParameter)
136 | {
137 | var actionType = moduleDefinition.Types.First(t => t.FullName == "System.Action`1" && t.GenericParameters.Count == 1);
138 | var actionOfT = new GenericInstanceType(actionType);
139 | actionOfT.GenericArguments.Add(genericParameter);
140 | FieldDefinition callback = new FieldDefinition("callback", FieldAttributes.Public, actionOfT);
141 | genericWrapper.Fields.Add(callback);
142 |
143 | var wrapperOfT = new GenericInstanceType(genericWrapper);
144 | wrapperOfT.GenericArguments.Add(genericParameter);
145 | return (actionOfT, new FieldReference(callback.Name, actionOfT, wrapperOfT));
146 | }
147 |
148 | static MethodDefinition CreateInvokeMethod(ModuleDefinition moduleDefinition, GenericParameter genericParameter, TypeReference actionOfT, FieldReference callbackField)
149 | {
150 | var wrapMethod = new MethodDefinition("Invoke", MethodAttributes.Public, moduleDefinition.TypeSystem.Void);
151 | wrapMethod.Parameters.Add(new ParameterDefinition(moduleDefinition.TypeSystem.Object) { Name = "state" });
152 | var ilProcessor = wrapMethod.Body.GetILProcessor();
153 | ilProcessor.Emit(OpCodes.Ldarg_0);
154 | ilProcessor.Emit(OpCodes.Ldfld, callbackField);
155 | ilProcessor.Emit(OpCodes.Ldarg_1);
156 | ilProcessor.Emit(OpCodes.Unbox_Any, genericParameter);
157 | var invokeMethod = new MethodReference("Invoke", moduleDefinition.TypeSystem.Void, actionOfT)
158 | {
159 | HasThis = true
160 | };
161 | invokeMethod.Parameters.Add(new ParameterDefinition(genericParameter));
162 | ilProcessor.Emit(OpCodes.Callvirt, invokeMethod);
163 | ilProcessor.Emit(OpCodes.Ret);
164 |
165 | return wrapMethod;
166 | }
167 |
168 | static MethodDefinition CreateConstructor(ModuleDefinition moduleDefinition, FieldReference callbackField)
169 | {
170 | var ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig, moduleDefinition.TypeSystem.Void);
171 | ctor.Parameters.Add(new ParameterDefinition(callbackField.FieldType));
172 | var ilProcessor = ctor.Body.GetILProcessor();
173 | ilProcessor.Emit(OpCodes.Ldarg_0);
174 | ilProcessor.Emit(OpCodes.Call, new MethodReference(".ctor", moduleDefinition.TypeSystem.Void, moduleDefinition.TypeSystem.Object));
175 | ilProcessor.Emit(OpCodes.Ldarg_0);
176 | ilProcessor.Emit(OpCodes.Ldarg_1);
177 | ilProcessor.Emit(OpCodes.Stfld, callbackField);
178 | ilProcessor.Emit(OpCodes.Ret);
179 | return ctor;
180 | }
181 | }
182 |
183 | private static void PatchQueueUserWorkItemGeneric(ModuleDefinition moduleDefinition, MethodDefinition methodDefinition, TypeDefinition synchronizationContext, TypeDefinition waitCallback, TypeDefinition postCallback)
184 | {
185 | var genericWrapper = GetGenericToObjectDelegateWrapper(moduleDefinition);
186 | var wrapperOfT = new GenericInstanceType(genericWrapper);
187 | wrapperOfT.GenericArguments.Add(methodDefinition.GenericParameters[0]);
188 |
189 | var ilPProcessor = methodDefinition.Body.GetILProcessor();
190 | ilPProcessor.Body.Instructions.Clear();
191 | methodDefinition.Body.ExceptionHandlers.Clear();
192 |
193 | var actionType = moduleDefinition.Types.First(t => t.FullName == "System.Action`1" && t.GenericParameters.Count == 1);
194 | var actionOfT = new GenericInstanceType(actionType);
195 | actionOfT.GenericArguments.Add(genericWrapper.GenericParameters[0]);
196 |
197 | ilPProcessor.Emit(OpCodes.Ldarg_0);
198 | var wrapperCtor = new MethodReference(".ctor", moduleDefinition.TypeSystem.Void, wrapperOfT);
199 | wrapperCtor.Parameters.Add(new ParameterDefinition(actionOfT));
200 | wrapperCtor.HasThis = true;
201 | ilPProcessor.Emit(OpCodes.Newobj, wrapperCtor);
202 |
203 | var wrapperInvoke = new MethodReference("Invoke", moduleDefinition.TypeSystem.Void, wrapperOfT);
204 | wrapperInvoke.Parameters.Add(new ParameterDefinition(moduleDefinition.TypeSystem.Object));
205 | wrapperInvoke.HasThis = true;
206 | ilPProcessor.Emit(OpCodes.Ldftn, wrapperInvoke);
207 |
208 | ilPProcessor.Emit(OpCodes.Newobj, waitCallback.Methods.First(m => m.IsConstructor && m.Parameters.Count == 2));
209 |
210 | ilPProcessor.Emit(OpCodes.Ldarg_1);
211 | ilPProcessor.Emit(OpCodes.Box, methodDefinition.GenericParameters[0]);
212 | var notGenericVariant = new MethodReference(methodDefinition.Name, methodDefinition.ReturnType, methodDefinition.DeclaringType);
213 | notGenericVariant.Parameters.Add(new ParameterDefinition(moduleDefinition.Types.First(t => t.FullName == "System.Threading.WaitCallback")));
214 | notGenericVariant.Parameters.Add(new ParameterDefinition(moduleDefinition.TypeSystem.Object));
215 | ilPProcessor.Emit(OpCodes.Call, notGenericVariant);
216 |
217 |
218 | ilPProcessor.Emit(OpCodes.Ret);
219 | }
220 |
221 | private static void PatchQueueUserWorkItem(ModuleDefinition moduleDefinition, MethodDefinition methodDefinition, TypeDefinition synchronizationContext, TypeDefinition waitCallback, TypeDefinition postCallback)
222 | {
223 | var ilPProcessor = methodDefinition.Body.GetILProcessor();
224 | ilPProcessor.Body.Instructions.Clear();
225 | methodDefinition.Body.ExceptionHandlers.Clear();
226 | ilPProcessor.Emit(OpCodes.Call, moduleDefinition.ImportReference(synchronizationContext.Methods.Single(s => s.Name == "get_Current")));
227 | ilPProcessor.Emit(OpCodes.Ldarg_0);
228 | ilPProcessor.Emit(OpCodes.Ldftn, moduleDefinition.ImportReference(waitCallback.Methods.Single(s => s.Name == "Invoke")));
229 | ilPProcessor.Emit(OpCodes.Newobj, moduleDefinition.ImportReference(postCallback.Methods.First(s => s.IsConstructor)));
230 | if (methodDefinition.Parameters.Count == 2)
231 | ilPProcessor.Emit(OpCodes.Ldarg_1);
232 | else
233 | ilPProcessor.Emit(OpCodes.Ldnull);
234 | ilPProcessor.Emit(OpCodes.Callvirt, moduleDefinition.ImportReference(synchronizationContext.Methods.Single(s => s.Name == "Post")));
235 |
236 | ilPProcessor.Emit(OpCodes.Ldc_I4_1);
237 | ilPProcessor.Emit(OpCodes.Ret);
238 | }
239 |
240 | private static void PatchUnsafeQueueCustomWorkItem(ModuleDefinition moduleDefinition, MethodDefinition methodDefinition, TypeDefinition synchronizationContext, MethodDefinition taskExecutionCallcack, TypeDefinition postCallback)
241 | {
242 | var p = methodDefinition.Body.GetILProcessor();
243 | p.Body.Instructions.Clear();
244 | methodDefinition.Body.ExceptionHandlers.Clear();
245 | p.Emit(OpCodes.Call, moduleDefinition.ImportReference(synchronizationContext.Methods.Single(s => s.Name == "get_Current")));
246 | p.Emit(OpCodes.Ldnull);
247 | p.Emit(OpCodes.Ldftn, moduleDefinition.ImportReference(taskExecutionCallcack));
248 | p.Emit(OpCodes.Newobj, moduleDefinition.ImportReference(postCallback.Methods.First(s => s.IsConstructor)));
249 | p.Emit(OpCodes.Ldarg_0);
250 | p.Emit(OpCodes.Callvirt, moduleDefinition.ImportReference(synchronizationContext.Methods.Single(s => s.Name == "Post")));
251 |
252 | p.Emit(OpCodes.Ret);
253 | }
254 |
255 | private static void PatchTryPopCustomWorkItem(MethodDefinition methodDefinition)
256 | {
257 | var ilPProcessor = methodDefinition.Body.GetILProcessor();
258 | ilPProcessor.Body.Instructions.Clear();
259 | methodDefinition.Body.ExceptionHandlers.Clear();
260 | ilPProcessor.Emit(OpCodes.Ldc_I4_0);
261 | ilPProcessor.Emit(OpCodes.Ret);
262 | }
263 |
264 | private static void PatchGetThreads(MethodDefinition methodDefinition)
265 | {
266 | var ilPProcessor = methodDefinition.Body.GetILProcessor();
267 | ilPProcessor.Body.Instructions.Clear();
268 | methodDefinition.Body.ExceptionHandlers.Clear();
269 | ilPProcessor.Emit(OpCodes.Ldarg_0);
270 | ilPProcessor.Emit(OpCodes.Ldc_I4_1);
271 | ilPProcessor.Emit(OpCodes.Stind_I4);
272 |
273 | ilPProcessor.Emit(OpCodes.Ldarg_1);
274 | ilPProcessor.Emit(OpCodes.Ldc_I4_1);
275 | ilPProcessor.Emit(OpCodes.Stind_I4);
276 | ilPProcessor.Emit(OpCodes.Ret);
277 | }
278 |
279 | private static void PatchSetThreads(MethodDefinition methodDefinition)
280 | {
281 | var ilPProcessor = methodDefinition.Body.GetILProcessor();
282 | ilPProcessor.Body.Instructions.Clear();
283 | methodDefinition.Body.ExceptionHandlers.Clear();
284 | var falseRet = ilPProcessor.Create(OpCodes.Ldc_I4_0);
285 |
286 | ilPProcessor.Emit(OpCodes.Ldarg_0);
287 | ilPProcessor.Emit(OpCodes.Ldc_I4_1);
288 | ilPProcessor.Emit(OpCodes.Bne_Un_S, falseRet);
289 |
290 | ilPProcessor.Emit(OpCodes.Ldarg_1);
291 | ilPProcessor.Emit(OpCodes.Ldc_I4_1);
292 | ilPProcessor.Emit(OpCodes.Ceq);
293 | ilPProcessor.Emit(OpCodes.Ret);
294 |
295 | ilPProcessor.Append(falseRet);
296 | ilPProcessor.Emit(OpCodes.Ret);
297 | }
298 |
299 | private static MethodDefinition AddTaskExecutionPostCallback(TypeDefinition threadPool, TypeDefinition taskExecutionItem, ModuleDefinition moduleDefinition)
300 | {
301 | var method = new MethodDefinition("TaskExecutionItemExecute",
302 | MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig,
303 | moduleDefinition.TypeSystem.Void);
304 |
305 | method.Parameters.Add(new ParameterDefinition("state", ParameterAttributes.None, moduleDefinition.TypeSystem.Object));
306 |
307 | var ilProcessor = method.Body.GetILProcessor();
308 | ilProcessor.Emit(OpCodes.Ldarg_0);
309 | ilProcessor.Emit(OpCodes.Callvirt, moduleDefinition.ImportReference(taskExecutionItem.Methods.Single(s => s.Name == "ExecuteWorkItem")));
310 | ilProcessor.Emit(OpCodes.Ret);
311 |
312 | threadPool.Methods.Add(method);
313 |
314 | return method;
315 | }
316 |
317 | private static void PatchTimerScheduler(ModuleDefinition moduleDefinition, TypeDefinition timerScheduler, TypeDefinition threadPool, TypeDefinition waitCallback)
318 | {
319 | var monoPinvoke = AddMonoPInvokeCallbackAttribute(moduleDefinition);
320 |
321 | var timer = moduleDefinition.Types.Single(m => m.FullName == "System.Threading.Timer");
322 | var listGeneric = moduleDefinition.Types.Single(t => t.HasGenericParameters && t.FullName == "System.Collections.Generic.List`1");
323 | var timerListRef = MakeGenericType(listGeneric, timer);
324 | var tempTimerListField = new FieldDefinition("tempList", FieldAttributes.Private, timerListRef);
325 | timerScheduler.Fields.Add(tempTimerListField);
326 |
327 | var internalAssemblyReference = new ModuleReference("__Internal");
328 | moduleDefinition.ModuleReferences.Add(internalAssemblyReference);
329 |
330 | MethodDefinition setCallbackMethod = AddSetCallbackPImplMethod(moduleDefinition, internalAssemblyReference);
331 | MethodDefinition updateTimer = AddUpdateTimerPImplMethod(moduleDefinition, internalAssemblyReference);
332 | MethodDefinition processTimerMethods = AddProcessTimerMethod(moduleDefinition, timerScheduler, threadPool, waitCallback, updateTimer, tempTimerListField, monoPinvoke);
333 |
334 | PatchChangeMethod(moduleDefinition, timerScheduler, processTimerMethods);
335 | PatchTimerSchedulerCtor(moduleDefinition, timerScheduler, processTimerMethods, setCallbackMethod, tempTimerListField);
336 |
337 | timerScheduler.Methods.Add(setCallbackMethod);
338 | timerScheduler.Methods.Add(updateTimer);
339 | timerScheduler.Methods.Add(processTimerMethods);
340 |
341 | timerScheduler.Methods.Remove(timerScheduler.Methods.Single(m => m.Name == "SchedulerThread"));
342 | timerScheduler.Fields.Remove(timerScheduler.Fields.Single(m => m.Name == "changed"));
343 | }
344 |
345 | private static void PatchTimerSchedulerCtor(ModuleDefinition moduleDefinition, TypeDefinition timerScheduler, MethodDefinition precessTimers, MethodDefinition setCallback, FieldDefinition tempList)
346 | {
347 | var ctor = timerScheduler.Methods.Single(m => m.IsConstructor && !m.IsStatic);
348 | ctor.Body.Instructions.Clear();
349 | ctor.Body.ExceptionHandlers.Clear();
350 |
351 | var @object = moduleDefinition.Types.Single(m => m.FullName == "System.Object");
352 | var sortedList = moduleDefinition.Types.Single(m => m.FullName == "System.Collections.SortedList");
353 | var listGeneric = moduleDefinition.Types.Single(m => m.FullName == "System.Collections.Generic.List`1");
354 | var timer = moduleDefinition.Types.Single(m => m.FullName == "System.Threading.Timer");
355 | var timerComparer = timer.NestedTypes.Single(m => m.Name.Contains("TimerComparer"));
356 | var action = moduleDefinition.Types.Single(m => m.FullName == "System.Action");
357 |
358 | var sortedListCtor = sortedList.Methods.Single(m => m.IsConstructor && m.Parameters.Count == 2 && m.Parameters[1].ParameterType.FullName == "System.Int32");
359 | var timerComparerCtor = timerComparer.Methods.Single(m => m.IsConstructor && m.Parameters.Count == 0);
360 | var actionCtor = action.Methods.Single(m => m.IsConstructor);
361 | var objectCtor = @object.Methods.Single(m => m.IsConstructor);
362 | var listCtor = listGeneric.Methods.Single(m => m.IsConstructor && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "System.Int32");
363 |
364 |
365 | var ilProcessor = ctor.Body.GetILProcessor();
366 | ilProcessor.Emit(OpCodes.Ldarg_0);
367 | ilProcessor.Emit(OpCodes.Call, objectCtor);
368 |
369 | ilProcessor.Emit(OpCodes.Ldarg_0);
370 | ilProcessor.Emit(OpCodes.Newobj, timerComparerCtor);
371 | ilProcessor.Emit(OpCodes.Ldc_I4, 1024);
372 | ilProcessor.Emit(OpCodes.Newobj, sortedListCtor);
373 | ilProcessor.Emit(OpCodes.Stfld, timerScheduler.Fields.Single(f => f.Name == "list"));
374 |
375 | ilProcessor.Emit(OpCodes.Ldarg_0);
376 | ilProcessor.Emit(OpCodes.Ldc_I4, 512);
377 | ilProcessor.Emit(OpCodes.Newobj, MakeGeneric(listCtor, timer));
378 | ilProcessor.Emit(OpCodes.Stfld, tempList);
379 |
380 | ilProcessor.Emit(OpCodes.Ldnull);
381 | ilProcessor.Emit(OpCodes.Ldftn, precessTimers);
382 | ilProcessor.Emit(OpCodes.Newobj, actionCtor);
383 | ilProcessor.Emit(OpCodes.Call, setCallback);
384 | ilProcessor.Emit(OpCodes.Ret);
385 | }
386 |
387 | private static void PatchChangeMethod(ModuleDefinition moduleDefinition, TypeDefinition timerScheduler, MethodDefinition precessTimers)
388 | {
389 | var timer = moduleDefinition.Types.Single(m => m.FullName == "System.Threading.Timer");
390 | var sortedList = moduleDefinition.Types.Single(m => m.FullName == "System.Collections.SortedList");
391 | var method = timerScheduler.Methods.Single(m => m.Name == "Change");
392 | method.Body.Instructions.Clear();
393 | method.Body.ExceptionHandlers.Clear();
394 |
395 | method.Body.Variables.Clear();
396 |
397 | var ilProcessor = method.Body.GetILProcessor();
398 |
399 | ilProcessor.Emit(OpCodes.Ldarg_0);
400 | ilProcessor.Emit(OpCodes.Ldarg_1);
401 | ilProcessor.Emit(OpCodes.Call, timerScheduler.Methods.Single(m => m.Name == "InternalRemove"));
402 | ilProcessor.Emit(OpCodes.Pop);
403 |
404 | var checkDisposed = ilProcessor.Create(OpCodes.Ldarg_1);
405 |
406 | ilProcessor.Emit(OpCodes.Ldarg_2);
407 | ilProcessor.Emit(OpCodes.Ldc_I8, long.MaxValue);
408 | ilProcessor.Emit(OpCodes.Bne_Un_S, checkDisposed);
409 | ilProcessor.Emit(OpCodes.Ldarg_1);
410 | ilProcessor.Emit(OpCodes.Ldarg_2);
411 | ilProcessor.Emit(OpCodes.Stfld, timer.Fields.Single(f => f.Name == "next_run"));
412 | ilProcessor.Emit(OpCodes.Ret);
413 |
414 | var finalReturn = ilProcessor.Create(OpCodes.Ret);
415 |
416 | ilProcessor.Append(checkDisposed);
417 | ilProcessor.Emit(OpCodes.Ldfld, timer.Fields.Single(f => f.Name == "disposed"));
418 | ilProcessor.Emit(OpCodes.Brtrue_S, finalReturn);
419 |
420 | ilProcessor.Emit(OpCodes.Ldarg_1);
421 | ilProcessor.Emit(OpCodes.Ldarg_2);
422 | ilProcessor.Emit(OpCodes.Stfld, timer.Fields.Single(f => f.Name == "next_run"));
423 | ilProcessor.Emit(OpCodes.Ldarg_0);
424 | ilProcessor.Emit(OpCodes.Ldarg_1);
425 | ilProcessor.Emit(OpCodes.Call, timerScheduler.Methods.Single(m => m.Name == "Add"));
426 |
427 | ilProcessor.Emit(OpCodes.Ldarg_0);
428 | ilProcessor.Emit(OpCodes.Ldfld, timerScheduler.Fields.Single(f => f.Name == "list"));
429 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
430 | ilProcessor.Emit(OpCodes.Call, sortedList.Methods.Single(f => f.Name == "GetByIndex"));
431 | ilProcessor.Emit(OpCodes.Ldarg_1);
432 | ilProcessor.Emit(OpCodes.Bne_Un_S, finalReturn);
433 |
434 | ilProcessor.Emit(OpCodes.Call, precessTimers);
435 |
436 | ilProcessor.Append(finalReturn);
437 | }
438 |
439 | private static MethodDefinition AddSetCallbackPImplMethod(ModuleDefinition moduleDefinition, ModuleReference internalAssemblyReference)
440 | {
441 | var setCallbackMethod = new MethodDefinition("SetCallback", MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.Private | MethodAttributes.PInvokeImpl, moduleDefinition.TypeSystem.Void);
442 | setCallbackMethod.PInvokeInfo = new PInvokeInfo(PInvokeAttributes.CallConvWinapi, "SetCallback", internalAssemblyReference) { IsCharSetNotSpec = true, IsCallConvWinapi = true };
443 | setCallbackMethod.Parameters.Add(new ParameterDefinition("callback", ParameterAttributes.None, moduleDefinition.ImportReference(moduleDefinition.Types.First(t => t.FullName == "System.Action" && !t.HasGenericParameters))));
444 | return setCallbackMethod;
445 | }
446 |
447 | private static MethodDefinition AddUpdateTimerPImplMethod(ModuleDefinition moduleDefinition, ModuleReference internalAssemblyReference)
448 | {
449 | var updateTimer = new MethodDefinition("UpdateTimer", MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.Private | MethodAttributes.PInvokeImpl, moduleDefinition.TypeSystem.Void);
450 | updateTimer.PInvokeInfo = new PInvokeInfo(PInvokeAttributes.CallConvWinapi, "UpdateTimer", internalAssemblyReference) { IsCharSetNotSpec = true, IsCallConvWinapi = true };
451 | updateTimer.Parameters.Add(new ParameterDefinition("interval", ParameterAttributes.None, moduleDefinition.TypeSystem.Int64));
452 | return updateTimer;
453 | }
454 |
455 | private static MethodDefinition AddProcessTimerMethod(ModuleDefinition moduleDefinition, TypeDefinition timerScheduler, TypeDefinition threadPool, TypeDefinition waitCallback, MethodDefinition updateTimer, FieldDefinition tempList, TypeDefinition monoPinvokeAttr)
456 | {
457 | var shrinkIfNeededMethod = timerScheduler.Methods.Single(m => m.Name == "ShrinkIfNeeded");
458 | var getInstance = timerScheduler.Methods.Single(m => m.Name == "get_Instance");
459 | var unsafeQueue = threadPool.Methods.Single(m => m.Name == "UnsafeQueueUserWorkItem");
460 | var timer = moduleDefinition.Types.Single(m => m.FullName == "System.Threading.Timer");
461 | var getTimeMonotonic = timer.Methods.Single(m => m.Name == "GetTimeMonotonic");
462 | var listGeneric = moduleDefinition.Types.Single(t => t.HasGenericParameters && t.FullName.StartsWith("System.Collections.Generic.List"));
463 | var sortedListType = moduleDefinition.Types.Single(t => t.FullName == "System.Collections.SortedList");
464 | var timerListRef = MakeGenericType(listGeneric, timer);
465 |
466 | var processTimerMethods = new MethodDefinition("ProcessTimers", MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.Private, moduleDefinition.TypeSystem.Void);
467 | var ilProcessor = processTimerMethods.Body.GetILProcessor();
468 |
469 | var TimeToNext = new VariableDefinition(moduleDefinition.TypeSystem.Int64);
470 | var currentTime = new VariableDefinition(moduleDefinition.TypeSystem.Int64);
471 | var loopIterator = new VariableDefinition(moduleDefinition.TypeSystem.Int32);
472 | var loopEnd = new VariableDefinition(moduleDefinition.TypeSystem.Int32);
473 | var sortedList = new VariableDefinition(sortedListType);
474 | var list = new VariableDefinition(timerListRef);
475 | var currentTimer = new VariableDefinition(timer);
476 | var periodMs = new VariableDefinition(moduleDefinition.TypeSystem.Int64);
477 | var dueTimeMs = new VariableDefinition(moduleDefinition.TypeSystem.Int64);
478 | var instance = new VariableDefinition(timerScheduler);
479 |
480 | processTimerMethods.Body.Variables.Add(list);
481 | processTimerMethods.Body.Variables.Add(TimeToNext);
482 | processTimerMethods.Body.Variables.Add(currentTime);
483 | processTimerMethods.Body.Variables.Add(loopIterator);
484 | processTimerMethods.Body.Variables.Add(loopEnd);
485 | processTimerMethods.Body.Variables.Add(sortedList);
486 | processTimerMethods.Body.Variables.Add(currentTimer);
487 | processTimerMethods.Body.Variables.Add(periodMs);
488 | processTimerMethods.Body.Variables.Add(dueTimeMs);
489 | processTimerMethods.Body.Variables.Add(instance);
490 |
491 | var ret = ilProcessor.Create(OpCodes.Ret);
492 |
493 | ilProcessor.Emit(OpCodes.Call, getTimeMonotonic);
494 | ilProcessor.Emit(OpCodes.Stloc, currentTime);
495 |
496 | var loopCheck = ilProcessor.Create(OpCodes.Ldloc, loopIterator);
497 | ilProcessor.Emit(OpCodes.Call, getInstance);
498 | ilProcessor.Emit(OpCodes.Dup);
499 | ilProcessor.Emit(OpCodes.Dup);
500 | ilProcessor.Emit(OpCodes.Stloc, instance);
501 |
502 | ilProcessor.Emit(OpCodes.Ldfld, timerScheduler.Fields.Single(f => f.Name == "list"));
503 | ilProcessor.Emit(OpCodes.Stloc, sortedList);
504 |
505 | ilProcessor.Emit(OpCodes.Ldfld, tempList);
506 | ilProcessor.Emit(OpCodes.Stloc, list);
507 |
508 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
509 | ilProcessor.Emit(OpCodes.Call, sortedListType.Methods.Single(m => m.Name == "get_Count"));
510 | ilProcessor.Emit(OpCodes.Stloc, loopEnd);
511 |
512 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
513 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
514 | ilProcessor.Emit(OpCodes.Br, loopCheck);
515 |
516 | var loopStart = ilProcessor.Create(OpCodes.Ldloc, sortedList);
517 | var loopStart2 = ilProcessor.Create(OpCodes.Ldloc, instance);
518 | var loop2 = ilProcessor.Create(OpCodes.Ldloc, list);
519 | ilProcessor.Append(loopStart);
520 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
521 | ilProcessor.Emit(OpCodes.Call, sortedListType.Methods.Single(m => m.Name == "GetByIndex"));
522 | ilProcessor.Emit(OpCodes.Stloc, currentTimer);
523 | ilProcessor.Emit(OpCodes.Ldloc, currentTimer);
524 | ilProcessor.Emit(OpCodes.Ldfld, timer.Fields.Single(f => f.Name == "next_run"));
525 | ilProcessor.Emit(OpCodes.Ldloc, currentTime);
526 | ilProcessor.Emit(OpCodes.Bgt, loop2);
527 |
528 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
529 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
530 | ilProcessor.Emit(OpCodes.Call, sortedListType.Methods.Single(m => m.Name == "RemoveAt"));
531 |
532 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
533 | ilProcessor.Emit(OpCodes.Ldc_I4_1);
534 | ilProcessor.Emit(OpCodes.Sub);
535 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
536 |
537 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
538 | ilProcessor.Emit(OpCodes.Ldc_I4_1);
539 | ilProcessor.Emit(OpCodes.Sub);
540 | ilProcessor.Emit(OpCodes.Stloc, loopEnd);
541 |
542 | ilProcessor.Emit(OpCodes.Ldnull);
543 | ilProcessor.Emit(OpCodes.Ldftn, timerScheduler.Methods.Single(m => m.Name == "TimerCB"));
544 | ilProcessor.Emit(OpCodes.Newobj, waitCallback.Methods.Single(mbox => mbox.IsConstructor));
545 | ilProcessor.Emit(OpCodes.Ldloc, currentTimer);
546 | ilProcessor.Emit(OpCodes.Call, unsafeQueue);
547 | ilProcessor.Emit(OpCodes.Pop);
548 |
549 | ilProcessor.Emit(OpCodes.Ldloc, currentTimer);
550 | ilProcessor.Emit(OpCodes.Ldfld, timer.Fields.Single(f => f.Name == "period_ms"));
551 | ilProcessor.Emit(OpCodes.Stloc, periodMs);
552 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
553 |
554 | var setNextRunToMax = ilProcessor.Create(OpCodes.Ldloc, currentTimer);
555 |
556 | ilProcessor.Emit(OpCodes.Ldc_I4_M1);
557 | ilProcessor.Emit(OpCodes.Conv_I8);
558 | ilProcessor.Emit(OpCodes.Beq_S, setNextRunToMax);
559 |
560 | var checkDueTimeStart = ilProcessor.Create(OpCodes.Ldloc, currentTimer); ;
561 |
562 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
563 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
564 | ilProcessor.Emit(OpCodes.Conv_I8);
565 | ilProcessor.Emit(OpCodes.Beq_S, checkDueTimeStart);
566 |
567 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
568 | ilProcessor.Emit(OpCodes.Ldc_I4_M1);
569 | ilProcessor.Emit(OpCodes.Conv_I8);
570 | ilProcessor.Emit(OpCodes.Bne_Un_S, setNextRunToMax);
571 |
572 | ilProcessor.Append(checkDueTimeStart);
573 | ilProcessor.Emit(OpCodes.Ldfld, timer.Fields.Single(f => f.Name == "due_time_ms"));
574 |
575 | ilProcessor.Emit(OpCodes.Ldc_I4_M1);
576 | ilProcessor.Emit(OpCodes.Conv_I8);
577 | ilProcessor.Emit(OpCodes.Bne_Un_S, setNextRunToMax);
578 |
579 | ilProcessor.Emit(OpCodes.Ldloc, list);
580 | ilProcessor.Emit(OpCodes.Ldloc, currentTimer);
581 | ilProcessor.Emit(OpCodes.Dup);
582 | ilProcessor.Emit(OpCodes.Call, getTimeMonotonic);
583 | ilProcessor.Emit(OpCodes.Ldc_I4, 10000);
584 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
585 | ilProcessor.Emit(OpCodes.Mul);
586 | ilProcessor.Emit(OpCodes.Add);
587 | ilProcessor.Emit(OpCodes.Stfld, timer.Fields.Single(f => f.Name == "next_run"));
588 | var listAdd = listGeneric.Methods.Single(m => m.Name == "Add");
589 | ilProcessor.Emit(OpCodes.Call, MakeGeneric(listAdd, timer));
590 |
591 | var incrementStart = ilProcessor.Create(OpCodes.Ldloc, loopIterator);
592 | ilProcessor.Emit(OpCodes.Br, incrementStart);
593 |
594 | ilProcessor.Append(setNextRunToMax);
595 | ilProcessor.Emit(OpCodes.Ldc_I8, long.MaxValue);
596 | ilProcessor.Emit(OpCodes.Stfld, timer.Fields.Single(f => f.Name == "next_run"));
597 |
598 | ilProcessor.Append(incrementStart);
599 | ilProcessor.Emit(OpCodes.Ldc_I4_1);
600 | ilProcessor.Emit(OpCodes.Add);
601 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
602 | ilProcessor.Append(loopCheck);
603 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
604 | ilProcessor.Emit(OpCodes.Blt, loopStart);
605 |
606 |
607 | var loopCheck2 = ilProcessor.Create(OpCodes.Ldloc, loopIterator);
608 | ilProcessor.Append(loop2);
609 | var listCount = listGeneric.Methods.Single(m => m.Name == "get_Count");
610 | ilProcessor.Emit(OpCodes.Call, MakeGeneric(listCount, timer));
611 | ilProcessor.Emit(OpCodes.Stloc, loopEnd);
612 |
613 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
614 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
615 | ilProcessor.Emit(OpCodes.Br_S, loopCheck2);
616 |
617 | ilProcessor.Append(loopStart2);
618 | ilProcessor.Emit(OpCodes.Ldloc, list);
619 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
620 | var getItems = listGeneric.Methods.Single(m => m.Name == "get_Item");
621 | ilProcessor.Emit(OpCodes.Callvirt, MakeGeneric(getItems, timer));
622 | ilProcessor.Emit(OpCodes.Call, timerScheduler.Methods.Single(m => m.Name == "Add"));
623 |
624 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
625 | ilProcessor.Emit(OpCodes.Ldc_I4_1);
626 | ilProcessor.Emit(OpCodes.Add);
627 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
628 |
629 | ilProcessor.Append(loopCheck2);
630 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
631 | ilProcessor.Emit(OpCodes.Blt, loopStart2);
632 |
633 | ilProcessor.Emit(OpCodes.Ldloc, instance);
634 | ilProcessor.Emit(OpCodes.Ldloc, list);
635 | ilProcessor.Emit(OpCodes.Dup);
636 | var clearList = listGeneric.Methods.Single(m => m.Name == "Clear");
637 | ilProcessor.Emit(OpCodes.Call, MakeGeneric(clearList, timer));
638 | ilProcessor.Emit(OpCodes.Ldc_I4, 512);
639 | ilProcessor.Emit(OpCodes.Call, shrinkIfNeededMethod);
640 |
641 | var afterCapacityCheck = ilProcessor.Create(OpCodes.Ldloc, loopEnd);
642 |
643 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
644 | ilProcessor.Emit(OpCodes.Callvirt, sortedListType.Methods.Single(m => m.Name == "get_Capacity"));
645 | ilProcessor.Emit(OpCodes.Stloc, loopIterator);
646 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
647 | ilProcessor.Emit(OpCodes.Callvirt, sortedListType.Methods.Single(m => m.Name == "get_Count"));
648 | ilProcessor.Emit(OpCodes.Stloc, loopEnd);
649 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
650 | ilProcessor.Emit(OpCodes.Ldc_I4, 1024);
651 | ilProcessor.Emit(OpCodes.Blt_S, afterCapacityCheck);
652 |
653 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
654 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
655 | ilProcessor.Emit(OpCodes.Ble_S, afterCapacityCheck);
656 |
657 | ilProcessor.Emit(OpCodes.Ldloc, loopIterator);
658 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
659 | ilProcessor.Emit(OpCodes.Div);
660 | ilProcessor.Emit(OpCodes.Ldc_I4_3);
661 | ilProcessor.Emit(OpCodes.Ble_S, afterCapacityCheck);
662 |
663 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
664 | ilProcessor.Emit(OpCodes.Ldloc, loopEnd);
665 | ilProcessor.Emit(OpCodes.Ldc_I4_2);
666 | ilProcessor.Emit(OpCodes.Mul);
667 | ilProcessor.Emit(OpCodes.Callvirt, sortedListType.Methods.Single(m => m.Name == "set_Capacity"));
668 |
669 | ilProcessor.Append(afterCapacityCheck);
670 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
671 | ilProcessor.Emit(OpCodes.Ble_S, ret);
672 |
673 | ilProcessor.Emit(OpCodes.Ldloc, sortedList);
674 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
675 | ilProcessor.Emit(OpCodes.Call, sortedListType.Methods.Single(m => m.Name == "GetByIndex"));
676 | ilProcessor.Emit(OpCodes.Stloc, currentTimer);
677 | ilProcessor.Emit(OpCodes.Ldloc, currentTimer);
678 | ilProcessor.Emit(OpCodes.Ldfld, timer.Fields.Single(f => f.Name == "next_run"));
679 | ilProcessor.Emit(OpCodes.Call, getTimeMonotonic);
680 | ilProcessor.Emit(OpCodes.Sub);
681 | ilProcessor.Emit(OpCodes.Ldc_I4, 10000);
682 | ilProcessor.Emit(OpCodes.Div);
683 | ilProcessor.Emit(OpCodes.Stloc, periodMs);
684 |
685 | var updateTimerLabel = ilProcessor.Create(OpCodes.Ldloc, periodMs);
686 | var setMaxTime = ilProcessor.Create(OpCodes.Ldc_I8, 2147483646L);
687 |
688 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
689 | ilProcessor.Emit(OpCodes.Ldc_I8, 2147483647L);
690 | ilProcessor.Emit(OpCodes.Bgt_S, setMaxTime);
691 |
692 | ilProcessor.Emit(OpCodes.Ldloc, periodMs);
693 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
694 | ilProcessor.Emit(OpCodes.Conv_I8);
695 | ilProcessor.Emit(OpCodes.Bgt_S, updateTimerLabel);
696 | ilProcessor.Emit(OpCodes.Ldc_I4_0);
697 | ilProcessor.Emit(OpCodes.Conv_I8);
698 | ilProcessor.Emit(OpCodes.Stloc, periodMs);
699 | ilProcessor.Emit(OpCodes.Br_S, updateTimerLabel);
700 |
701 | ilProcessor.Append(setMaxTime);
702 | ilProcessor.Emit(OpCodes.Stloc, periodMs);
703 |
704 | ilProcessor.Append(updateTimerLabel);
705 | ilProcessor.Emit(OpCodes.Call, updateTimer);
706 |
707 | ilProcessor.Append(ret);
708 |
709 | var token = System.Text.Encoding.UTF8.GetBytes("System.Action");
710 | var blob = new byte[5 + token.Length];
711 | blob[0] = 1;
712 | blob[2] = (byte)token.Length;
713 | System.Array.Copy(token, 0, blob, 3, token.Length);
714 |
715 | processTimerMethods.CustomAttributes.Add(new CustomAttribute(monoPinvokeAttr.Methods.Single(m => m.IsConstructor), blob));
716 | return processTimerMethods;
717 | }
718 |
719 | public static TypeReference MakeGenericType(TypeReference self, params TypeReference[] arguments)
720 | {
721 | if (self.GenericParameters.Count != arguments.Length)
722 | throw new System.ArgumentException();
723 |
724 | var instance = new GenericInstanceType(self);
725 | foreach (var argument in arguments)
726 | instance.GenericArguments.Add(argument);
727 |
728 | return instance;
729 | }
730 |
731 | public static MethodReference MakeGeneric(MethodReference self, params TypeReference[] arguments)
732 | {
733 | var reference = new MethodReference(self.Name, self.ReturnType)
734 | {
735 | DeclaringType = MakeGenericType(self.DeclaringType, arguments),
736 | HasThis = self.HasThis,
737 | ExplicitThis = self.ExplicitThis,
738 | CallingConvention = self.CallingConvention,
739 | };
740 |
741 | foreach (var parameter in self.Parameters)
742 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
743 |
744 | foreach (var generic_parameter in self.GenericParameters)
745 | reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
746 |
747 | return reference;
748 | }
749 |
750 | private static TypeDefinition AddMonoPInvokeCallbackAttribute(ModuleDefinition moduleDefinition)
751 | {
752 | var type = new TypeDefinition("AOT", "MonoPInvokeCallbackAttribute", TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public)
753 | {
754 | BaseType = moduleDefinition.ImportReference(moduleDefinition.Types.First(t => t.FullName == "System.Attribute"))
755 | };
756 | var ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, moduleDefinition.TypeSystem.Void);
757 | ctor.Parameters.Add(new ParameterDefinition("type", ParameterAttributes.None, moduleDefinition.ImportReference(moduleDefinition.Types.First(t => t.FullName == "System.Type"))));
758 | ctor.Body.GetILProcessor().Emit(OpCodes.Ret);
759 |
760 | type.Methods.Add(ctor);
761 |
762 | moduleDefinition.Types.Add(type);
763 | return type;
764 | }
765 |
766 | private static bool TryGetTypes(ModuleDefinition moduleDefinition,
767 | out TypeDefinition threadPool,
768 | out TypeDefinition synchronizationContext,
769 | out TypeDefinition sendOrPostCallback,
770 | out TypeDefinition waitCallback,
771 | out TypeDefinition threadPoolWorkItem,
772 | out TypeDefinition timerScheduler)
773 | {
774 | threadPool = null;
775 | synchronizationContext = null;
776 | sendOrPostCallback = null;
777 | waitCallback = null;
778 | threadPoolWorkItem = null;
779 | timerScheduler = null;
780 |
781 | foreach (var type in moduleDefinition.Types)
782 | {
783 | if (type.FullName == "System.Threading.ThreadPool")
784 | threadPool = type;
785 | if (type.FullName == "System.Threading.SynchronizationContext")
786 | synchronizationContext = type;
787 | if (type.FullName == "System.Threading.SendOrPostCallback")
788 | sendOrPostCallback = type;
789 | if (type.FullName == "System.Threading.WaitCallback")
790 | waitCallback = type;
791 | if (type.FullName == "System.Threading.IThreadPoolWorkItem")
792 | threadPoolWorkItem = type;
793 | if (type.FullName == "System.Threading.Timer")
794 | foreach (var nested in type.NestedTypes)
795 | if (nested.FullName.Contains("Scheduler"))
796 | timerScheduler = nested;
797 | }
798 |
799 | return CheckTypeAssigned("System.Threading.ThreadPool", threadPool) &&
800 | CheckTypeAssigned("System.Threading.SynchronizationContext", synchronizationContext) &&
801 | CheckTypeAssigned("System.Threading.SendOrPostCallback", sendOrPostCallback) &&
802 | CheckTypeAssigned("System.Threading.WaitCallback", waitCallback) &&
803 | CheckTypeAssigned("System.Threading.IThreadPoolWorkItem", threadPoolWorkItem) &&
804 | CheckTypeAssigned("System.Threading.Timer.Scheduler", timerScheduler);
805 |
806 | bool CheckTypeAssigned(string name, TypeDefinition type)
807 | {
808 | if (type != null)
809 | return true;
810 |
811 | Debug.LogError("Can't find " + name);
812 | return false;
813 | }
814 | }
815 | }
816 | }
--------------------------------------------------------------------------------
/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ec6cffd6c8b8ada4fbc77144faf50a7b
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Plugins.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f7418791cadc25c4b9a6b9ef7f9b9c0e
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib:
--------------------------------------------------------------------------------
1 | var SystemThreadingTimerLib = {
2 | $vars: {
3 | currentCallbackId : 0,
4 | callback: {}
5 | },
6 |
7 | SetCallback: function (onCallback)
8 | {
9 | vars.callback = onCallback;
10 | },
11 |
12 | UpdateTimer: function(interval)
13 | {
14 | var id = ++vars.currentCallbackId;
15 | setTimeout(function()
16 | {
17 | if (id === vars.currentCallbackId)
18 | Runtime.dynCall('v', vars.callback);
19 | },
20 | interval);
21 | }
22 | };
23 |
24 | autoAddDeps(SystemThreadingTimerLib, '$vars');
25 | mergeInto(LibraryManager.library, SystemThreadingTimerLib);
--------------------------------------------------------------------------------
/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 21e89e0a510991940877830c6fce4164
3 | PluginImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | iconMap: {}
7 | executionOrder: {}
8 | defineConstraints: []
9 | isPreloaded: 0
10 | isOverridable: 0
11 | isExplicitlyReferenced: 0
12 | validateReferences: 1
13 | platformData:
14 | - first:
15 | Any:
16 | second:
17 | enabled: 0
18 | settings: {}
19 | - first:
20 | Editor: Editor
21 | second:
22 | enabled: 0
23 | settings:
24 | DefaultValueInitialized: true
25 | - first:
26 | WebGL: WebGL
27 | second:
28 | enabled: 1
29 | settings: {}
30 | userData:
31 | assetBundleName:
32 | assetBundleVariant:
33 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8e6227e5a60be841a4e45169c7387dd
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Runtime/TimerRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Reflection;
4 | using UnityEngine;
5 | using UnityEngine.Scripting;
6 |
7 | [assembly: AlwaysLinkAssembly]
8 |
9 | namespace WebGLThreadingPatcher.Runtime
10 | {
11 | [Preserve]
12 | public class TimerRunner : MonoBehaviour
13 | {
14 | private Func _timerSchedulerLoop;
15 |
16 | [Preserve]
17 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
18 | private static void Initialize()
19 | {
20 | var go = new GameObject(nameof(TimerRunner));
21 | go.AddComponent();
22 |
23 | DontDestroyOnLoad(go);
24 | }
25 |
26 | [Preserve]
27 | private void Awake()
28 | {
29 | var timer = typeof(System.Threading.Timer);
30 | var scheduler = timer.GetNestedType("Scheduler", BindingFlags.NonPublic);
31 |
32 | var timerSchedulerInstance = scheduler.GetProperty("Instance").GetValue(null);
33 | _timerSchedulerLoop = (Func)scheduler.GetMethod("RunSchedulerLoop", BindingFlags.Instance | BindingFlags.NonPublic)
34 | .CreateDelegate(typeof(Func), timerSchedulerInstance);
35 | }
36 |
37 | [Preserve]
38 | private void Start()
39 | {
40 | #if UNITY_2021_2_OR_NEWER
41 | StartCoroutine(TimerUpdateCoroutine());
42 | #endif
43 | }
44 |
45 | private IEnumerator TimerUpdateCoroutine()
46 | {
47 | #if UNITY_EDITOR
48 | yield break;
49 | #endif
50 | #pragma warning disable CS0162 // Unreachable code detected
51 | while (true)
52 | {
53 | var delay = _timerSchedulerLoop();
54 | if (delay == -1)
55 | yield return null;
56 | else
57 | yield return new WaitForSeconds(delay / 1000);
58 | }
59 | #pragma warning restore CS0162 // Unreachable code detected
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/ThreadingPatcher/Runtime/TimerRunner.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 400589cd5cbf85044ac305b4a90c8bf0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/ThreadingPatcher/Runtime/WebGLThreadingPatcher.Runtime.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebGLThreadingPatcher.Runtime",
3 | "rootNamespace": "WebGLThreadingPatcher.Runtime",
4 | "references": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [],
12 | "versionDefines": [],
13 | "noEngineReferences": false
14 | }
--------------------------------------------------------------------------------
/ThreadingPatcher/Runtime/WebGLThreadingPatcher.Runtime.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cfed221c79ccd5c4f91cc8d39524bdc3
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.tools.webglthreadingpatcher",
3 | "displayName": "WebGL Threading Patcher",
4 | "version": "0.0.1-preview.1",
5 | "unity": "2019.3",
6 | "src": "Assets/ThreadingPatcher",
7 | "upmSupport": true,
8 | "dependencies": {
9 | "nuget.mono-cecil": "0.1.6-preview"
10 | }
11 | }
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e40d2143d196a334b9ae6e519f677138
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------