├── .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 | --------------------------------------------------------------------------------