diff --git a/Assets/Mirror/Editor/Weaver/Extensions.cs b/Assets/Mirror/Editor/Weaver/Extensions.cs index 5b5acad21..21535d620 100644 --- a/Assets/Mirror/Editor/Weaver/Extensions.cs +++ b/Assets/Mirror/Editor/Weaver/Extensions.cs @@ -139,6 +139,22 @@ public static MethodReference MakeHostInstanceGeneric(this MethodReference self, return Weaver.CurrentAssembly.MainModule.ImportReference(reference); } + /// + /// Given a field of a generic class such as Writer.write, + /// and a generic instance such as ArraySegment`int + /// Creates a reference to the specialized method ArraySegment`int`.get_Count + /// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error + /// + /// + /// Generic Instance eg Writer + /// + public static FieldReference SpecializeField(this FieldReference self, GenericInstanceType instanceType) + { + FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType); + + return Weaver.CurrentAssembly.MainModule.ImportReference(reference); + } + public static CustomAttribute GetCustomAttribute(this ICustomAttributeProvider method) { foreach (CustomAttribute ca in method.CustomAttributes) diff --git a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs index cb6d269a1..bd860ed80 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs @@ -1,7 +1,11 @@ // finds all readers and writers and register them -using System.IO; +using System; +using System.Linq; using Mono.CecilX; +using Mono.CecilX.Cil; +using UnityEditor; using UnityEditor.Compilation; +using UnityEngine; namespace Mirror.Weaver { @@ -14,20 +18,12 @@ public static void Process(AssemblyDefinition CurrentAssembly) foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies()) { - if (unityAsm.name != CurrentAssembly.Name.Name) + if (unityAsm.name == "Mirror") { - try + using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver()) + using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver })) { - using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver()) - using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver })) - { - ProcessAssemblyClasses(CurrentAssembly, assembly); - } - } - catch (FileNotFoundException) - { - // During first import, this gets called before some assemblies - // are built, just skip them + ProcessAssemblyClasses(CurrentAssembly, assembly); } } } @@ -97,5 +93,54 @@ static void LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefiniti Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method)); } } + + private static bool IsEditorAssembly(AssemblyDefinition currentAssembly) + { + return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference => + assemblyReference.Name == nameof(UnityEditor) + ) ; + } + + /// + /// Creates a method that will store all the readers and writers into + /// and + /// + /// The method will be marked InitializeOnLoadMethodAttribute so it gets + /// executed before mirror runtime code + /// + /// + public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly) + { + var rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public | + MethodAttributes.Static, + WeaverTypes.Import(typeof(void))); + + System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new [] { typeof(RuntimeInitializeLoadType)}); + + CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor)); + customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import(), RuntimeInitializeLoadType.BeforeSceneLoad)); + rwInitializer.CustomAttributes.Add(customAttributeRef); + + if (IsEditorAssembly(currentAssembly)) + { + // editor assembly, add InitializeOnLoadMethod too. Useful for the editor tests + System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]); + CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor)); + rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef); + } + + ILProcessor worker = rwInitializer.Body.GetILProcessor(); + + Writers.InitializeWriters(worker); + Readers.InitializeReaders(worker); + + worker.Append(worker.Create(OpCodes.Ret)); + + Weaver.WeaveLists.ConfirmGeneratedCodeClass(); + TypeDefinition generateClass = Weaver.WeaveLists.generateContainerClass; + + generateClass.Methods.Add(rwInitializer); + } + } } diff --git a/Assets/Mirror/Editor/Weaver/Readers.cs b/Assets/Mirror/Editor/Weaver/Readers.cs index 244b21e20..6aa3a7ca9 100644 --- a/Assets/Mirror/Editor/Weaver/Readers.cs +++ b/Assets/Mirror/Editor/Weaver/Readers.cs @@ -411,5 +411,40 @@ static void ReadAllFields(TypeReference variable, ILProcessor worker) fields++; } } + + /// + /// Save a delegate for each one of the readers into + /// + /// + internal static void InitializeReaders(ILProcessor worker) + { + ModuleDefinition module = Weaver.CurrentAssembly.MainModule; + + TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>)); + + System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader.read)); + FieldReference fieldRef = module.ImportReference(fieldInfo); + TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader)); + TypeReference funcRef = module.ImportReference(typeof(Func<,>)); + MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]); + + foreach (MethodReference readFunc in readFuncs.Values) + { + TypeReference dataType = readFunc.ReturnType; + + // create a Func delegate + worker.Append(worker.Create(OpCodes.Ldnull)); + worker.Append(worker.Create(OpCodes.Ldftn, readFunc)); + GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, dataType); + MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(funcGenericInstance); + worker.Append(worker.Create(OpCodes.Newobj, funcConstructorInstance)); + + // save it in Writer.write + GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(dataType); + FieldReference specializedField = fieldRef.SpecializeField(genericInstance); + worker.Append(worker.Create(OpCodes.Stsfld, specializedField)); + } + + } } } diff --git a/Assets/Mirror/Editor/Weaver/Weaver.cs b/Assets/Mirror/Editor/Weaver/Weaver.cs index 721856f1a..1c2297d91 100644 --- a/Assets/Mirror/Editor/Weaver/Weaver.cs +++ b/Assets/Mirror/Editor/Weaver/Weaver.cs @@ -300,6 +300,8 @@ static bool Weave(string assName, IEnumerable dependencies) if (modified) { + ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly); + // write to outputDir if specified, otherwise perform in-place write WriterParameters writeParams = new WriterParameters { WriteSymbols = true }; CurrentAssembly.Write(writeParams); diff --git a/Assets/Mirror/Editor/Weaver/Writers.cs b/Assets/Mirror/Editor/Weaver/Writers.cs index 4b6761abd..3c1add46d 100644 --- a/Assets/Mirror/Editor/Weaver/Writers.cs +++ b/Assets/Mirror/Editor/Weaver/Writers.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Mono.CecilX; using Mono.CecilX.Cil; +using Mono.CecilX.Rocks; namespace Mirror.Weaver { @@ -433,5 +434,41 @@ private static void GenerateFor(ILProcessor worker, Action body) worker.Append(worker.Create(OpCodes.Ldloc_0)); worker.Append(worker.Create(OpCodes.Blt, labelBody)); } + + /// + /// Save a delegate for each one of the writers into + /// + /// + internal static void InitializeWriters(ILProcessor worker) + { + ModuleDefinition module = Weaver.CurrentAssembly.MainModule; + + TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>)); + + System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer.write)); + FieldReference fieldRef = module.ImportReference(fieldInfo); + TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter)); + TypeReference actionRef = module.ImportReference(typeof(Action<,>)); + MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]); + + foreach (MethodReference writerMethod in writeFuncs.Values) + { + + TypeReference dataType = writerMethod.Parameters[1].ParameterType; + + // create a Action delegate + worker.Append(worker.Create(OpCodes.Ldnull)); + worker.Append(worker.Create(OpCodes.Ldftn, writerMethod)); + GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, dataType); + MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(actionGenericInstance); + worker.Append(worker.Create(OpCodes.Newobj, actionRefInstance)); + + // save it in Writer.write + GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(dataType); + FieldReference specializedField = fieldRef.SpecializeField(genericInstance); + worker.Append(worker.Create(OpCodes.Stsfld, specializedField)); + } + } + } } diff --git a/Assets/Mirror/Runtime/NetworkReader.cs b/Assets/Mirror/Runtime/NetworkReader.cs index 37f901442..1d265b2b9 100644 --- a/Assets/Mirror/Runtime/NetworkReader.cs +++ b/Assets/Mirror/Runtime/NetworkReader.cs @@ -13,6 +13,18 @@ namespace Mirror { + /// + /// a class that holds readers for the different types + /// Note that c# creates a different static variable for each + /// type + /// This will be populated by the weaver + /// + /// + public static class Reader + { + public static Func read; + } + // Note: This class is intended to be extremely pedantic, and // throw exceptions whenever stuff is going slightly wrong. // The exceptions will be handled in NetworkServer/NetworkClient. @@ -108,6 +120,16 @@ public override string ToString() { return "NetworkReader pos=" + Position + " len=" + Length + " buffer=" + BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count); } + + /// + /// Reads any data type that mirror supports + /// + /// + /// + public T Read() + { + return Reader.read(this); + } } // Mirror's Weaver automatically detects all NetworkReader function types, diff --git a/Assets/Mirror/Runtime/NetworkWriter.cs b/Assets/Mirror/Runtime/NetworkWriter.cs index 05814e9b0..3fab0a26b 100644 --- a/Assets/Mirror/Runtime/NetworkWriter.cs +++ b/Assets/Mirror/Runtime/NetworkWriter.cs @@ -5,6 +5,18 @@ namespace Mirror { + /// + /// a class that holds writers for the different types + /// Note that c# creates a different static variable for each + /// type + /// This will be populated by the weaver + /// + /// + public static class Writer + { + public static Action write; + } + /// /// Binary stream Writer. Supports simple types, buffers, arrays, structs, and nested types /// Use NetworkWriter.GetWriter to reduce memory allocation @@ -154,6 +166,16 @@ public void WriteUInt64(ulong value) } public void WriteInt64(long value) => WriteUInt64((ulong)value); + + /// + /// Writes any type that mirror supports + /// + /// + /// + public void Write(T value) + { + Writer.write(this, value); + } } diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs new file mode 100644 index 000000000..32fa7838f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + [TestFixture] + public class NetworkReaderWriterTest + { + [Test] + public void TestIntWriterNotNull() + { + Assert.That(Writer.write, Is.Not.Null); + } + + [Test] + public void TestIntReaderNotNull() + { + Assert.That(Reader.read, Is.Not.Null); + } + + [Test] + public void TestAccessingCustomWriterAndReader() + { + NetworkWriter writer = new NetworkWriter(); + writer.Write(3); + NetworkReader reader = new NetworkReader(writer.ToArray()); + int copy = reader.Read(); + + Assert.That(copy, Is.EqualTo(3)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta new file mode 100644 index 000000000..1e4a29f9b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4db12e3e883ac4c3aac177c638ee93e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: