mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
feat: new generic Read and Write methods for all types (#2301)
Currently in mirror, there is no way for Mirror itself to invoke readers and writers for types it does not know about. This causes us to have to generate code for lists, dictionaries and messages. This PR makes it so that if there is a reader and writer anywhere in the code for type X, then the writer can be invoked like this: ```cs X x = ...; writer.Write<X>(x); ``` and the reader can be invoked like this: ```cs X x = reader.Read<X>(); ```
This commit is contained in:
parent
16c919eae9
commit
85252c3d84
@ -139,6 +139,22 @@ public static MethodReference MakeHostInstanceGeneric(this MethodReference self,
|
||||
return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a field of a generic class such as Writer<T>.write,
|
||||
/// and a generic instance such as ArraySegment`int
|
||||
/// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
||||
/// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="instanceType">Generic Instance eg Writer<int></param>
|
||||
/// <returns></returns>
|
||||
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<TAttribute>(this ICustomAttributeProvider method)
|
||||
{
|
||||
foreach (CustomAttribute ca in method.CustomAttributes)
|
||||
|
@ -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)
|
||||
) ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a method that will store all the readers and writers into
|
||||
/// <see cref="Writer{T}.write"/> and <see cref="Reader{T}.read"/>
|
||||
///
|
||||
/// The method will be marked InitializeOnLoadMethodAttribute so it gets
|
||||
/// executed before mirror runtime code
|
||||
/// </summary>
|
||||
/// <param name="currentAssembly"></param>
|
||||
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>(), 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -411,5 +411,40 @@ static void ReadAllFields(TypeReference variable, ILProcessor worker)
|
||||
fields++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a delegate for each one of the readers into <see cref="Reader{T}.read"/>
|
||||
/// </summary>
|
||||
/// <param name="worker"></param>
|
||||
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<object>.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<NetworkReader, T> 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<T>.write
|
||||
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(dataType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -300,6 +300,8 @@ static bool Weave(string assName, IEnumerable<string> 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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a delegate for each one of the writers into <see cref="Writer{T}.write"/>
|
||||
/// </summary>
|
||||
/// <param name="worker"></param>
|
||||
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<object>.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<NetworkWriter, T> 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<T>.write
|
||||
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(dataType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,18 @@
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static class Reader<T>
|
||||
{
|
||||
public static Func<NetworkReader, T> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads any data type that mirror supports
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T Read<T>()
|
||||
{
|
||||
return Reader<T>.read(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror's Weaver automatically detects all NetworkReader function types,
|
||||
|
@ -5,6 +5,18 @@
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static class Writer<T>
|
||||
{
|
||||
public static Action<NetworkWriter, T> write;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary stream Writer. Supports simple types, buffers, arrays, structs, and nested types
|
||||
/// <para>Use <see cref="NetworkWriterPool.GetWriter">NetworkWriter.GetWriter</see> to reduce memory allocation</para>
|
||||
@ -154,6 +166,16 @@ public void WriteUInt64(ulong value)
|
||||
}
|
||||
|
||||
public void WriteInt64(long value) => WriteUInt64((ulong)value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes any type that mirror supports
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
public void Write<T>(T value)
|
||||
{
|
||||
Writer<T>.write(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
31
Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs
Normal file
31
Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Mirror.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class NetworkReaderWriterTest
|
||||
{
|
||||
[Test]
|
||||
public void TestIntWriterNotNull()
|
||||
{
|
||||
Assert.That(Writer<int>.write, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIntReaderNotNull()
|
||||
{
|
||||
Assert.That(Reader<int>.read, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAccessingCustomWriterAndReader()
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter();
|
||||
writer.Write<int>(3);
|
||||
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||
int copy = reader.Read<int>();
|
||||
|
||||
Assert.That(copy, Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta
Normal file
11
Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4db12e3e883ac4c3aac177c638ee93e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user