quantum-space-buddies/MirrorWeaver/Weaver/Weaver.cs
2021-12-27 22:31:23 -08:00

215 lines
8.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mono.CecilX;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
internal class Weaver
{
public const string InvokeRpcPrefix = "InvokeUserCode_";
// generated code class
public const string GeneratedCodeNamespace = "Mirror";
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
TypeDefinition GeneratedCodeClass;
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know
// Mirror.dll name
public const string MirrorAssemblyName = "Mirror";
WeaverTypes weaverTypes;
SyncVarAccessLists syncVarAccessLists;
IAssemblyResolver Resolver;
AssemblyDefinition CurrentAssembly;
Writers writers;
Readers readers;
bool WeavingFailed;
// logger functions can be set from the outside.
// for example, Debug.Log or ILPostProcessor Diagnostics log for
// multi threaded logging.
public Logger Log;
public Weaver(Logger Log)
{
this.Log = Log;
}
// returns 'true' if modified (=if we did anything)
bool WeaveNetworkBehavior(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!td.IsDerivedFrom<NetworkBehaviour>())
{
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
return false;
}
// process this and base classes from parent to child order
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
TypeDefinition parent = td;
while (parent != null)
{
if (parent.Is<NetworkBehaviour>())
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool modified = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
}
return modified;
}
bool WeaveModule(ModuleDefinition moduleDefinition)
{
bool modified = false;
Stopwatch watch = Stopwatch.StartNew();
watch.Start();
foreach (TypeDefinition td in moduleDefinition.Types)
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
}
}
watch.Stop();
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
return modified;
}
void CreateGeneratedCodeClass()
{
// create "Mirror.GeneratedNetworkCode" class which holds all
// Readers<T> and Writers<T>
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
weaverTypes.Import<object>());
}
// Weave takes an AssemblyDefinition to be compatible with both old and
// new weavers:
// * old takes a filepath, new takes a in-memory byte[]
// * old uses DefaultAssemblyResolver with added dependencies paths,
// new uses ...?
//
// => assembly: the one we are currently weaving (MyGame.dll)
// => resolver: useful in case we need to resolve any of the assembly's
// assembly.MainModule.AssemblyReferences.
// -> we can resolve ANY of them given that the resolver
// works properly (need custom one for ILPostProcessor)
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
// those from assembly.MainModule.AssemblyReferences are
// guaranteed to be resolve-able.
// Parsing from a string for Library/.../Mirror.dll
// would not be guaranteed to be resolve-able because
// for ILPostProcessor we can't assume where Mirror.dll
// is etc.
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
{
WeavingFailed = false;
modified = false;
try
{
Resolver = resolver;
CurrentAssembly = assembly;
// fix "No writer found for ..." error
// https://github.com/vis2k/Mirror/issues/2579
// -> when restarting Unity, weaver would try to weave a DLL
// again
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
// -> the second one wouldn't have all the writer types setup
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
{
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
return true;
}
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
// weaverTypes are needed for CreateGeneratedCodeClass
CreateGeneratedCodeClass();
// WeaverList depends on WeaverTypes setup because it uses Import
syncVarAccessLists = new SyncVarAccessLists();
// initialize readers & writers with this assembly.
// we need to do this in every Process() call.
// otherwise we would get
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
// errors when still using the previous module's reader/writer funcs.
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
Stopwatch rwstopwatch = Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition);
if (WeavingFailed)
{
return false;
}
if (modified)
{
SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists);
// add class that holds read/write functions
moduleDefinition.Types.Add(GeneratedCodeClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
// DO NOT WRITE here.
// CompilationFinishedHook writes to the file.
// ILPostProcessor writes to in-memory assembly.
// it depends on the caller.
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
}
return true;
}
catch (Exception e)
{
Log.Error($"Exception :{e}");
WeavingFailed = true;
return false;
}
}
}
}