alap
This commit is contained in:
113
Assets/Mirror/Editor/AndroidManifestHelper.cs
Normal file
113
Assets/Mirror/Editor/AndroidManifestHelper.cs
Normal file
@ -0,0 +1,113 @@
|
||||
// Android NetworkDiscovery Multicast fix
|
||||
// https://github.com/vis2k/Mirror/pull/2887
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
#if UNITY_ANDROID
|
||||
using UnityEditor.Android;
|
||||
#endif
|
||||
|
||||
|
||||
[InitializeOnLoad]
|
||||
public class AndroidManifestHelper : IPreprocessBuildWithReport, IPostprocessBuildWithReport
|
||||
#if UNITY_ANDROID
|
||||
, IPostGenerateGradleAndroidProject
|
||||
#endif
|
||||
{
|
||||
public int callbackOrder { get { return 99999; } }
|
||||
|
||||
#if UNITY_ANDROID
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
string manifestFolder = Path.Combine(path, "src/main");
|
||||
string sourceFile = manifestFolder + "/AndroidManifest.xml";
|
||||
// Load android manifest file
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(sourceFile);
|
||||
|
||||
string androidNamepsaceURI;
|
||||
XmlElement element = (XmlElement)doc.SelectSingleNode("/manifest");
|
||||
if (element == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError("Could not find manifest tag in android manifest.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get android namespace URI from the manifest
|
||||
androidNamepsaceURI = element.GetAttribute("xmlns:android");
|
||||
if (string.IsNullOrEmpty(androidNamepsaceURI))
|
||||
{
|
||||
UnityEngine.Debug.LogError("Could not find Android Namespace in manifest.");
|
||||
return;
|
||||
}
|
||||
AddOrRemoveTag(doc,
|
||||
androidNamepsaceURI,
|
||||
"/manifest",
|
||||
"uses-permission",
|
||||
"android.permission.CHANGE_WIFI_MULTICAST_STATE",
|
||||
true,
|
||||
false);
|
||||
AddOrRemoveTag(doc,
|
||||
androidNamepsaceURI,
|
||||
"/manifest",
|
||||
"uses-permission",
|
||||
"android.permission.INTERNET",
|
||||
true,
|
||||
false);
|
||||
doc.Save(sourceFile);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void AddOrRemoveTag(XmlDocument doc, string @namespace, string path, string elementName, string name, bool required, bool modifyIfFound, params string[] attrs) // name, value pairs
|
||||
{
|
||||
var nodes = doc.SelectNodes(path + "/" + elementName);
|
||||
XmlElement element = null;
|
||||
foreach (XmlElement e in nodes)
|
||||
{
|
||||
if (name == null || name == e.GetAttribute("name", @namespace))
|
||||
{
|
||||
element = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (required)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
var parent = doc.SelectSingleNode(path);
|
||||
element = doc.CreateElement(elementName);
|
||||
element.SetAttribute("name", @namespace, name);
|
||||
parent.AppendChild(element);
|
||||
}
|
||||
|
||||
for (int i = 0; i < attrs.Length; i += 2)
|
||||
{
|
||||
if (modifyIfFound || string.IsNullOrEmpty(element.GetAttribute(attrs[i], @namespace)))
|
||||
{
|
||||
if (attrs[i + 1] != null)
|
||||
{
|
||||
element.SetAttribute(attrs[i], @namespace, attrs[i + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
element.RemoveAttribute(attrs[i], @namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element != null && modifyIfFound)
|
||||
{
|
||||
element.ParentNode.RemoveChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPostprocessBuild(BuildReport report) {}
|
||||
public void OnPreprocessBuild(BuildReport report) {}
|
||||
}
|
11
Assets/Mirror/Editor/AndroidManifestHelper.cs.meta
Normal file
11
Assets/Mirror/Editor/AndroidManifestHelper.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80cc70189403d7444bbffd185ca28462
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
41
Assets/Mirror/Editor/EditorHelper.cs
Normal file
41
Assets/Mirror/Editor/EditorHelper.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public static class EditorHelper
|
||||
{
|
||||
public static string FindPath<T>()
|
||||
{
|
||||
string typeName = typeof(T).Name;
|
||||
|
||||
string[] guidsFound = AssetDatabase.FindAssets($"t:Script {typeName}");
|
||||
if (guidsFound.Length >= 1 && !string.IsNullOrWhiteSpace(guidsFound[0]))
|
||||
{
|
||||
if (guidsFound.Length > 1)
|
||||
{
|
||||
Debug.LogWarning($"Found more than one{typeName}");
|
||||
}
|
||||
|
||||
string path = AssetDatabase.GUIDToAssetPath(guidsFound[0]);
|
||||
return Path.GetDirectoryName(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Could not find path of {typeName}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<string> IterateOverProject(string filter)
|
||||
{
|
||||
foreach (string guid in AssetDatabase.FindAssets(filter))
|
||||
{
|
||||
yield return AssetDatabase.GUIDToAssetPath(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/EditorHelper.cs.meta
Normal file
11
Assets/Mirror/Editor/EditorHelper.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dba787f167ff29c4288532af1ec3584c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Editor/Icon.meta
Normal file
8
Assets/Mirror/Editor/Icon.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5f1356ad059a1243910a4e82cd68c5f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Mirror/Editor/Icon/MirrorIcon.png
Normal file
BIN
Assets/Mirror/Editor/Icon/MirrorIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
110
Assets/Mirror/Editor/Icon/MirrorIcon.png.meta
Normal file
110
Assets/Mirror/Editor/Icon/MirrorIcon.png.meta
Normal file
@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7453abfe9e8b2c04a8a47eb536fe21eb
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
77
Assets/Mirror/Editor/InspectorHelper.cs
Normal file
77
Assets/Mirror/Editor/InspectorHelper.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public static class InspectorHelper
|
||||
{
|
||||
/// <summary>Gets all public and private fields for a type</summary>
|
||||
// deepestBaseType: Stops at this base type (exclusive)
|
||||
public static IEnumerable<FieldInfo> GetAllFields(Type type, Type deepestBaseType)
|
||||
{
|
||||
const BindingFlags publicFields = BindingFlags.Public | BindingFlags.Instance;
|
||||
const BindingFlags privateFields = BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
// get public fields (includes fields from base type)
|
||||
FieldInfo[] allPublicFields = type.GetFields(publicFields);
|
||||
foreach (FieldInfo field in allPublicFields)
|
||||
{
|
||||
yield return field;
|
||||
}
|
||||
|
||||
// get private fields in current type, then move to base type
|
||||
while (type != null)
|
||||
{
|
||||
FieldInfo[] allPrivateFields = type.GetFields(privateFields);
|
||||
foreach (FieldInfo field in allPrivateFields)
|
||||
{
|
||||
yield return field;
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
|
||||
// stop early
|
||||
if (type == deepestBaseType)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSyncVar(this FieldInfo field)
|
||||
{
|
||||
object[] fieldMarkers = field.GetCustomAttributes(typeof(SyncVarAttribute), true);
|
||||
return fieldMarkers.Length > 0;
|
||||
}
|
||||
|
||||
public static bool IsSerializeField(this FieldInfo field)
|
||||
{
|
||||
object[] fieldMarkers = field.GetCustomAttributes(typeof(SerializeField), true);
|
||||
return fieldMarkers.Length > 0;
|
||||
}
|
||||
|
||||
public static bool IsVisibleField(this FieldInfo field)
|
||||
{
|
||||
return field.IsPublic || IsSerializeField(field);
|
||||
}
|
||||
|
||||
public static bool ImplementsInterface<T>(this FieldInfo field)
|
||||
{
|
||||
return typeof(T).IsAssignableFrom(field.FieldType);
|
||||
}
|
||||
|
||||
public static bool HasShowInInspector(this FieldInfo field)
|
||||
{
|
||||
object[] fieldMarkers = field.GetCustomAttributes(typeof(ShowInInspectorAttribute), true);
|
||||
return fieldMarkers.Length > 0;
|
||||
}
|
||||
|
||||
// checks if SyncObject is public or has our custom [ShowInInspector] field
|
||||
public static bool IsVisibleSyncObject(this FieldInfo field)
|
||||
{
|
||||
return field.IsPublic || HasShowInInspector(field);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/InspectorHelper.cs.meta
Normal file
11
Assets/Mirror/Editor/InspectorHelper.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 047c894c2a5ccc1438b7e59302f62744
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
19
Assets/Mirror/Editor/Mirror.Editor.asmdef
Normal file
19
Assets/Mirror/Editor/Mirror.Editor.asmdef
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Mirror.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:30817c1a0e6d646d99c048fc403f5979",
|
||||
"GUID:1d0b9d21c3ff546a4aa32399dfd33474"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
7
Assets/Mirror/Editor/Mirror.Editor.asmdef.meta
Normal file
7
Assets/Mirror/Editor/Mirror.Editor.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c7c33eb5480dd24c9e29a8250c1a775
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
104
Assets/Mirror/Editor/NetworkBehaviourInspector.cs
Normal file
104
Assets/Mirror/Editor/NetworkBehaviourInspector.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomEditor(typeof(NetworkBehaviour), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkBehaviourInspector : Editor
|
||||
{
|
||||
bool syncsAnything;
|
||||
SyncObjectCollectionsDrawer syncObjectCollectionsDrawer;
|
||||
|
||||
// does this type sync anything? otherwise we don't need to show syncInterval
|
||||
bool SyncsAnything(Type scriptClass)
|
||||
{
|
||||
// check for all SyncVar fields, they don't have to be visible
|
||||
foreach (FieldInfo field in InspectorHelper.GetAllFields(scriptClass, typeof(NetworkBehaviour)))
|
||||
{
|
||||
if (field.IsSyncVar())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// has OnSerialize that is not in NetworkBehaviour?
|
||||
// then it either has a syncvar or custom OnSerialize. either way
|
||||
// this means we have something to sync.
|
||||
MethodInfo method = scriptClass.GetMethod("OnSerialize");
|
||||
if (method != null && method.DeclaringType != typeof(NetworkBehaviour))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// SyncObjects are serialized in NetworkBehaviour.OnSerialize, which
|
||||
// is always there even if we don't use SyncObjects. so we need to
|
||||
// search for SyncObjects manually.
|
||||
// Any SyncObject should be added to syncObjects when unity creates an
|
||||
// object so we can check length of list so see if sync objects exists
|
||||
return ((NetworkBehaviour)serializedObject.targetObject).HasSyncObjects();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// sometimes target is null. just return early.
|
||||
if (target == null) return;
|
||||
|
||||
// If target's base class is changed from NetworkBehaviour to MonoBehaviour
|
||||
// then Unity temporarily keep using this Inspector causing things to break
|
||||
if (!(target is NetworkBehaviour)) { return; }
|
||||
|
||||
Type scriptClass = target.GetType();
|
||||
|
||||
syncObjectCollectionsDrawer = new SyncObjectCollectionsDrawer(serializedObject.targetObject);
|
||||
|
||||
syncsAnything = SyncsAnything(scriptClass);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
DrawSyncObjectCollections();
|
||||
DrawDefaultSyncSettings();
|
||||
}
|
||||
|
||||
// Draws Sync Objects that are IEnumerable
|
||||
protected void DrawSyncObjectCollections()
|
||||
{
|
||||
// Need this check in case OnEnable returns early
|
||||
if (syncObjectCollectionsDrawer == null) return;
|
||||
|
||||
syncObjectCollectionsDrawer.Draw();
|
||||
}
|
||||
|
||||
// Draws SyncSettings if the NetworkBehaviour has anything to sync
|
||||
protected void DrawDefaultSyncSettings()
|
||||
{
|
||||
// does it sync anything? then show extra properties
|
||||
// (no need to show it if the class only has Cmds/Rpcs and no sync)
|
||||
if (!syncsAnything)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Sync Settings", EditorStyles.boldLabel);
|
||||
|
||||
// sync direction
|
||||
SerializedProperty syncDirection = serializedObject.FindProperty("syncDirection");
|
||||
EditorGUILayout.PropertyField(syncDirection);
|
||||
|
||||
// sync mdoe: only show for ServerToClient components
|
||||
if (syncDirection.enumValueIndex == (int)SyncDirection.ServerToClient)
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
|
||||
|
||||
// sync interval
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
|
||||
|
||||
// apply
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/NetworkBehaviourInspector.cs.meta
Normal file
11
Assets/Mirror/Editor/NetworkBehaviourInspector.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f02853db46b6346e4866594a96c3b0e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
305
Assets/Mirror/Editor/NetworkInformationPreview.cs
Normal file
305
Assets/Mirror/Editor/NetworkInformationPreview.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomPreview(typeof(GameObject))]
|
||||
class NetworkInformationPreview : ObjectPreview
|
||||
{
|
||||
struct NetworkIdentityInfo
|
||||
{
|
||||
public GUIContent name;
|
||||
public GUIContent value;
|
||||
}
|
||||
|
||||
struct NetworkBehaviourInfo
|
||||
{
|
||||
// This is here just so we can check if it's enabled/disabled
|
||||
public NetworkBehaviour behaviour;
|
||||
public GUIContent name;
|
||||
}
|
||||
|
||||
class Styles
|
||||
{
|
||||
public GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
|
||||
public GUIStyle componentName = new GUIStyle(EditorStyles.boldLabel);
|
||||
public GUIStyle disabledName = new GUIStyle(EditorStyles.miniLabel);
|
||||
|
||||
public Styles()
|
||||
{
|
||||
Color fontColor = new Color(0.7f, 0.7f, 0.7f);
|
||||
labelStyle.padding.right += 20;
|
||||
labelStyle.normal.textColor = fontColor;
|
||||
labelStyle.active.textColor = fontColor;
|
||||
labelStyle.focused.textColor = fontColor;
|
||||
labelStyle.hover.textColor = fontColor;
|
||||
labelStyle.onNormal.textColor = fontColor;
|
||||
labelStyle.onActive.textColor = fontColor;
|
||||
labelStyle.onFocused.textColor = fontColor;
|
||||
labelStyle.onHover.textColor = fontColor;
|
||||
|
||||
componentName.normal.textColor = fontColor;
|
||||
componentName.active.textColor = fontColor;
|
||||
componentName.focused.textColor = fontColor;
|
||||
componentName.hover.textColor = fontColor;
|
||||
componentName.onNormal.textColor = fontColor;
|
||||
componentName.onActive.textColor = fontColor;
|
||||
componentName.onFocused.textColor = fontColor;
|
||||
componentName.onHover.textColor = fontColor;
|
||||
|
||||
disabledName.normal.textColor = fontColor;
|
||||
disabledName.active.textColor = fontColor;
|
||||
disabledName.focused.textColor = fontColor;
|
||||
disabledName.hover.textColor = fontColor;
|
||||
disabledName.onNormal.textColor = fontColor;
|
||||
disabledName.onActive.textColor = fontColor;
|
||||
disabledName.onFocused.textColor = fontColor;
|
||||
disabledName.onHover.textColor = fontColor;
|
||||
}
|
||||
}
|
||||
|
||||
GUIContent title;
|
||||
Styles styles = new Styles();
|
||||
|
||||
public override GUIContent GetPreviewTitle()
|
||||
{
|
||||
if (title == null)
|
||||
{
|
||||
title = new GUIContent("Network Information");
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
public override bool HasPreviewGUI()
|
||||
{
|
||||
// need to check if target is null to stop MissingReferenceException
|
||||
return target != null && target is GameObject gameObject && gameObject.GetComponent<NetworkIdentity>() != null;
|
||||
}
|
||||
|
||||
public override void OnPreviewGUI(Rect r, GUIStyle background)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
GameObject targetGameObject = target as GameObject;
|
||||
|
||||
if (targetGameObject == null)
|
||||
return;
|
||||
|
||||
NetworkIdentity identity = targetGameObject.GetComponent<NetworkIdentity>();
|
||||
|
||||
if (identity == null)
|
||||
return;
|
||||
|
||||
if (styles == null)
|
||||
styles = new Styles();
|
||||
|
||||
|
||||
// padding
|
||||
RectOffset previewPadding = new RectOffset(-5, -5, -5, -5);
|
||||
Rect paddedr = previewPadding.Add(r);
|
||||
|
||||
//Centering
|
||||
float initialX = paddedr.x + 10;
|
||||
float Y = paddedr.y + 10;
|
||||
|
||||
Y = DrawNetworkIdentityInfo(identity, initialX, Y);
|
||||
|
||||
Y = DrawNetworkBehaviors(identity, initialX, Y);
|
||||
|
||||
Y = DrawObservers(identity, initialX, Y);
|
||||
|
||||
_ = DrawOwner(identity, initialX, Y);
|
||||
|
||||
}
|
||||
|
||||
float DrawNetworkIdentityInfo(NetworkIdentity identity, float initialX, float Y)
|
||||
{
|
||||
IEnumerable<NetworkIdentityInfo> infos = GetNetworkIdentityInfo(identity);
|
||||
// Get required label size for the names of the information values we're going to show
|
||||
// There are two columns, one with label for the name of the info and the next for the value
|
||||
Vector2 maxNameLabelSize = new Vector2(140, 16);
|
||||
Vector2 maxValueLabelSize = GetMaxNameLabelSize(infos);
|
||||
|
||||
Rect labelRect = new Rect(initialX, Y, maxNameLabelSize.x, maxNameLabelSize.y);
|
||||
Rect idLabelRect = new Rect(maxNameLabelSize.x, Y, maxValueLabelSize.x, maxValueLabelSize.y);
|
||||
|
||||
foreach (NetworkIdentityInfo info in infos)
|
||||
{
|
||||
GUI.Label(labelRect, info.name, styles.labelStyle);
|
||||
GUI.Label(idLabelRect, info.value, styles.componentName);
|
||||
labelRect.y += labelRect.height;
|
||||
labelRect.x = initialX;
|
||||
idLabelRect.y += idLabelRect.height;
|
||||
}
|
||||
|
||||
return labelRect.y;
|
||||
}
|
||||
|
||||
float DrawNetworkBehaviors(NetworkIdentity identity, float initialX, float Y)
|
||||
{
|
||||
IEnumerable<NetworkBehaviourInfo> behavioursInfo = GetNetworkBehaviorInfo(identity);
|
||||
|
||||
// Show behaviours list in a different way than the name/value pairs above
|
||||
Vector2 maxBehaviourLabelSize = GetMaxBehaviourLabelSize(behavioursInfo);
|
||||
Rect behaviourRect = new Rect(initialX, Y + 10, maxBehaviourLabelSize.x, maxBehaviourLabelSize.y);
|
||||
|
||||
GUI.Label(behaviourRect, new GUIContent("Network Behaviours"), styles.labelStyle);
|
||||
// indent names
|
||||
behaviourRect.x += 20;
|
||||
behaviourRect.y += behaviourRect.height;
|
||||
|
||||
foreach (NetworkBehaviourInfo info in behavioursInfo)
|
||||
{
|
||||
if (info.behaviour == null)
|
||||
{
|
||||
// could be the case in the editor after existing play mode.
|
||||
continue;
|
||||
}
|
||||
|
||||
GUI.Label(behaviourRect, info.name, info.behaviour.enabled ? styles.componentName : styles.disabledName);
|
||||
behaviourRect.y += behaviourRect.height;
|
||||
Y = behaviourRect.y;
|
||||
}
|
||||
|
||||
return Y;
|
||||
}
|
||||
|
||||
float DrawObservers(NetworkIdentity identity, float initialX, float Y)
|
||||
{
|
||||
if (identity.observers.Count > 0)
|
||||
{
|
||||
Rect observerRect = new Rect(initialX, Y + 10, 200, 20);
|
||||
|
||||
GUI.Label(observerRect, new GUIContent("Network observers"), styles.labelStyle);
|
||||
// indent names
|
||||
observerRect.x += 20;
|
||||
observerRect.y += observerRect.height;
|
||||
|
||||
foreach (KeyValuePair<int, NetworkConnectionToClient> kvp in identity.observers)
|
||||
{
|
||||
GUI.Label(observerRect, $"{kvp.Value.address}:{kvp.Value}", styles.componentName);
|
||||
observerRect.y += observerRect.height;
|
||||
Y = observerRect.y;
|
||||
}
|
||||
}
|
||||
|
||||
return Y;
|
||||
}
|
||||
|
||||
float DrawOwner(NetworkIdentity identity, float initialX, float Y)
|
||||
{
|
||||
if (identity.connectionToClient != null)
|
||||
{
|
||||
Rect ownerRect = new Rect(initialX, Y + 10, 400, 20);
|
||||
GUI.Label(ownerRect, new GUIContent($"Client Authority: {identity.connectionToClient}"), styles.labelStyle);
|
||||
Y += ownerRect.height;
|
||||
}
|
||||
return Y;
|
||||
}
|
||||
|
||||
// Get the maximum size used by the value of information items
|
||||
Vector2 GetMaxNameLabelSize(IEnumerable<NetworkIdentityInfo> infos)
|
||||
{
|
||||
Vector2 maxLabelSize = Vector2.zero;
|
||||
foreach (NetworkIdentityInfo info in infos)
|
||||
{
|
||||
Vector2 labelSize = styles.labelStyle.CalcSize(info.value);
|
||||
if (maxLabelSize.x < labelSize.x)
|
||||
{
|
||||
maxLabelSize.x = labelSize.x;
|
||||
}
|
||||
if (maxLabelSize.y < labelSize.y)
|
||||
{
|
||||
maxLabelSize.y = labelSize.y;
|
||||
}
|
||||
}
|
||||
return maxLabelSize;
|
||||
}
|
||||
|
||||
Vector2 GetMaxBehaviourLabelSize(IEnumerable<NetworkBehaviourInfo> behavioursInfo)
|
||||
{
|
||||
Vector2 maxLabelSize = Vector2.zero;
|
||||
foreach (NetworkBehaviourInfo behaviour in behavioursInfo)
|
||||
{
|
||||
Vector2 labelSize = styles.labelStyle.CalcSize(behaviour.name);
|
||||
if (maxLabelSize.x < labelSize.x)
|
||||
{
|
||||
maxLabelSize.x = labelSize.x;
|
||||
}
|
||||
if (maxLabelSize.y < labelSize.y)
|
||||
{
|
||||
maxLabelSize.y = labelSize.y;
|
||||
}
|
||||
}
|
||||
return maxLabelSize;
|
||||
}
|
||||
|
||||
IEnumerable<NetworkIdentityInfo> GetNetworkIdentityInfo(NetworkIdentity identity)
|
||||
{
|
||||
List<NetworkIdentityInfo> infos = new List<NetworkIdentityInfo>
|
||||
{
|
||||
GetAssetId(identity),
|
||||
GetString("Scene ID", identity.sceneId.ToString("X"))
|
||||
};
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
infos.Add(GetString("Network ID", identity.netId.ToString()));
|
||||
infos.Add(GetBoolean("Is Client", identity.isClient));
|
||||
infos.Add(GetBoolean("Is Server", identity.isServer));
|
||||
infos.Add(GetBoolean("Is Owned", identity.isOwned));
|
||||
infos.Add(GetBoolean("Is Local Player", identity.isLocalPlayer));
|
||||
}
|
||||
return infos;
|
||||
}
|
||||
|
||||
IEnumerable<NetworkBehaviourInfo> GetNetworkBehaviorInfo(NetworkIdentity identity)
|
||||
{
|
||||
List<NetworkBehaviourInfo> behaviourInfos = new List<NetworkBehaviourInfo>();
|
||||
|
||||
NetworkBehaviour[] behaviours = identity.GetComponents<NetworkBehaviour>();
|
||||
foreach (NetworkBehaviour behaviour in behaviours)
|
||||
{
|
||||
behaviourInfos.Add(new NetworkBehaviourInfo
|
||||
{
|
||||
name = new GUIContent(behaviour.GetType().FullName),
|
||||
behaviour = behaviour
|
||||
});
|
||||
}
|
||||
return behaviourInfos;
|
||||
}
|
||||
|
||||
NetworkIdentityInfo GetAssetId(NetworkIdentity identity)
|
||||
{
|
||||
string assetId = identity.assetId.ToString();
|
||||
if (string.IsNullOrWhiteSpace(assetId))
|
||||
{
|
||||
assetId = "<object has no prefab>";
|
||||
}
|
||||
return GetString("Asset ID", assetId);
|
||||
}
|
||||
|
||||
static NetworkIdentityInfo GetString(string name, string value)
|
||||
{
|
||||
return new NetworkIdentityInfo
|
||||
{
|
||||
name = new GUIContent(name),
|
||||
value = new GUIContent(value)
|
||||
};
|
||||
}
|
||||
|
||||
static NetworkIdentityInfo GetBoolean(string name, bool value)
|
||||
{
|
||||
return new NetworkIdentityInfo
|
||||
{
|
||||
name = new GUIContent(name),
|
||||
value = new GUIContent((value ? "Yes" : "No"))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/NetworkInformationPreview.cs.meta
Normal file
11
Assets/Mirror/Editor/NetworkInformationPreview.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51a99294efe134232932c34606737356
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
181
Assets/Mirror/Editor/NetworkManagerEditor.cs
Normal file
181
Assets/Mirror/Editor/NetworkManagerEditor.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomEditor(typeof(NetworkManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkManagerEditor : Editor
|
||||
{
|
||||
SerializedProperty spawnListProperty;
|
||||
ReorderableList spawnList;
|
||||
protected NetworkManager networkManager;
|
||||
|
||||
protected void Init()
|
||||
{
|
||||
if (spawnList == null)
|
||||
{
|
||||
networkManager = target as NetworkManager;
|
||||
spawnListProperty = serializedObject.FindProperty("spawnPrefabs");
|
||||
spawnList = new ReorderableList(serializedObject, spawnListProperty)
|
||||
{
|
||||
drawHeaderCallback = DrawHeader,
|
||||
drawElementCallback = DrawChild,
|
||||
onReorderCallback = Changed,
|
||||
onRemoveCallback = RemoveButton,
|
||||
onChangedCallback = Changed,
|
||||
onAddCallback = AddButton,
|
||||
// this uses a 16x16 icon. other sizes make it stretch.
|
||||
elementHeight = 16
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Init();
|
||||
DrawDefaultInspector();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
spawnList.DoLayoutList();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Populate Spawnable Prefabs"))
|
||||
{
|
||||
ScanForNetworkIdentities();
|
||||
}
|
||||
}
|
||||
|
||||
void ScanForNetworkIdentities()
|
||||
{
|
||||
List<GameObject> identities = new List<GameObject>();
|
||||
bool cancelled = false;
|
||||
try
|
||||
{
|
||||
string[] paths = EditorHelper.IterateOverProject("t:prefab").ToArray();
|
||||
int count = 0;
|
||||
foreach (string path in paths)
|
||||
{
|
||||
// ignore test & example prefabs.
|
||||
// users sometimes keep the folders in their projects.
|
||||
if (path.Contains("Mirror/Tests/") ||
|
||||
path.Contains("Mirror/Examples/"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Searching for NetworkIdentities..",
|
||||
$"Scanned {count}/{paths.Length} prefabs. Found {identities.Count} new ones",
|
||||
count / (float)paths.Length))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
NetworkIdentity ni = AssetDatabase.LoadAssetAtPath<NetworkIdentity>(path);
|
||||
if (!ni)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!networkManager.spawnPrefabs.Contains(ni.gameObject))
|
||||
{
|
||||
identities.Add(ni.gameObject);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
if (!cancelled)
|
||||
{
|
||||
// RecordObject is needed for "*" to show up in Scene.
|
||||
// however, this only saves List.Count without the entries.
|
||||
Undo.RecordObject(networkManager, "NetworkManager: populated prefabs");
|
||||
|
||||
// add the entries
|
||||
networkManager.spawnPrefabs.AddRange(identities);
|
||||
|
||||
// sort alphabetically for better UX
|
||||
networkManager.spawnPrefabs = networkManager.spawnPrefabs.OrderBy(go => go.name).ToList();
|
||||
|
||||
// SetDirty is required to save the individual entries properly.
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
// Loading assets might use a lot of memory, so try to unload them after
|
||||
Resources.UnloadUnusedAssets();
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawHeader(Rect headerRect)
|
||||
{
|
||||
GUI.Label(headerRect, "Registered Spawnable Prefabs:");
|
||||
}
|
||||
|
||||
internal void DrawChild(Rect r, int index, bool isActive, bool isFocused)
|
||||
{
|
||||
SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index);
|
||||
GameObject go = (GameObject)prefab.objectReferenceValue;
|
||||
|
||||
GUIContent label;
|
||||
if (go == null)
|
||||
{
|
||||
label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here");
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkIdentity identity = go.GetComponent<NetworkIdentity>();
|
||||
label = new GUIContent(go.name, identity != null ? $"AssetId: [{identity.assetId}]" : "No Network Identity");
|
||||
}
|
||||
|
||||
GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false);
|
||||
|
||||
if (newGameObject != go)
|
||||
{
|
||||
if (newGameObject != null && !newGameObject.GetComponent<NetworkIdentity>())
|
||||
{
|
||||
Debug.LogError($"Prefab {newGameObject} cannot be added as spawnable as it doesn't have a NetworkIdentity.");
|
||||
return;
|
||||
}
|
||||
prefab.objectReferenceValue = newGameObject;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Changed(ReorderableList list)
|
||||
{
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
internal void AddButton(ReorderableList list)
|
||||
{
|
||||
spawnListProperty.arraySize += 1;
|
||||
list.index = spawnListProperty.arraySize - 1;
|
||||
|
||||
SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1);
|
||||
obj.objectReferenceValue = null;
|
||||
|
||||
spawnList.index = spawnList.count - 1;
|
||||
|
||||
Changed(list);
|
||||
}
|
||||
|
||||
internal void RemoveButton(ReorderableList list)
|
||||
{
|
||||
spawnListProperty.DeleteArrayElementAtIndex(spawnList.index);
|
||||
if (list.index >= spawnListProperty.arraySize)
|
||||
{
|
||||
list.index = spawnListProperty.arraySize - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/NetworkManagerEditor.cs.meta
Normal file
11
Assets/Mirror/Editor/NetworkManagerEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 519712eb07f7a44039df57664811c2c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
98
Assets/Mirror/Editor/NetworkScenePostProcess.cs
Normal file
98
Assets/Mirror/Editor/NetworkScenePostProcess.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkScenePostProcess : MonoBehaviour
|
||||
{
|
||||
[PostProcessScene]
|
||||
public static void OnPostProcessScene()
|
||||
{
|
||||
// find all NetworkIdentities in all scenes
|
||||
// => can't limit it to GetActiveScene() because that wouldn't work
|
||||
// for additive scene loads (the additively loaded scene is never
|
||||
// the active scene)
|
||||
// => ignore DontDestroyOnLoad scene! this avoids weird situations
|
||||
// like in NetworkZones when we destroy the local player and
|
||||
// load another scene afterwards, yet the local player is still
|
||||
// in the FindObjectsOfType result with scene=DontDestroyOnLoad
|
||||
// for some reason
|
||||
// => OfTypeAll so disabled objects are included too
|
||||
// => Unity 2019 returns prefabs here too, so filter them out.
|
||||
IEnumerable<NetworkIdentity> identities = Resources.FindObjectsOfTypeAll<NetworkIdentity>()
|
||||
.Where(identity => identity.gameObject.hideFlags != HideFlags.NotEditable &&
|
||||
identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
|
||||
identity.gameObject.scene.name != "DontDestroyOnLoad" &&
|
||||
!Utils.IsPrefab(identity.gameObject));
|
||||
|
||||
foreach (NetworkIdentity identity in identities)
|
||||
{
|
||||
// if we had a [ConflictComponent] attribute that would be better than this check.
|
||||
// also there is no context about which scene this is in.
|
||||
if (identity.GetComponent<NetworkManager>() != null)
|
||||
Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
|
||||
|
||||
// not spawned before?
|
||||
// OnPostProcessScene is called after additive scene loads too,
|
||||
// and we don't want to set main scene's objects inactive again
|
||||
if (!identity.isClient && !identity.isServer)
|
||||
{
|
||||
// valid scene object?
|
||||
// otherwise it might be an unopened scene that still has null
|
||||
// sceneIds. builds are interrupted if they contain 0 sceneIds,
|
||||
// but it's still possible that we call LoadScene in Editor
|
||||
// for a previously unopened scene.
|
||||
// (and only do SetActive if this was actually a scene object)
|
||||
if (identity.sceneId != 0)
|
||||
{
|
||||
PrepareSceneObject(identity);
|
||||
}
|
||||
// throwing an exception would only show it for one object
|
||||
// because this function would return afterwards.
|
||||
else
|
||||
{
|
||||
// there are two cases where sceneId == 0:
|
||||
// * if we have a prefab open in the prefab scene
|
||||
// * if an unopened scene needs resaving
|
||||
// show a proper error message in both cases so the user
|
||||
// knows what to do.
|
||||
string path = identity.gameObject.scene.path;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
Debug.LogError($"{identity.name} is currently open in Prefab Edit Mode. Please open the actual scene before launching Mirror.");
|
||||
else
|
||||
Debug.LogError($"Scene {path} needs to be opened and resaved, because the scene object {identity.name} has no valid sceneId yet.");
|
||||
|
||||
// either way we shouldn't continue. nothing good will
|
||||
// happen when trying to launch with invalid sceneIds.
|
||||
EditorApplication.isPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PrepareSceneObject(NetworkIdentity identity)
|
||||
{
|
||||
// set scene hash
|
||||
identity.SetSceneIdSceneHashPartInternal();
|
||||
|
||||
// spawnable scene objects are force disabled on scene load to
|
||||
// ensure Start/Update/etc. aren't called until actually spawned.
|
||||
//
|
||||
// note: NetworkIdentity.OnDisable adds itself to the
|
||||
// spawnableObjects dictionary (only if sceneId != 0)
|
||||
identity.gameObject.SetActive(false);
|
||||
|
||||
// safety check for prefabs with more than one NetworkIdentity
|
||||
GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject);
|
||||
if (prefabGO)
|
||||
{
|
||||
GameObject prefabRootGO = prefabGO.transform.root.gameObject;
|
||||
if (prefabRootGO != null && prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1)
|
||||
Debug.LogWarning($"Prefab {prefabRootGO.name} has several NetworkIdentity components attached to itself or its children, this is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/NetworkScenePostProcess.cs.meta
Normal file
11
Assets/Mirror/Editor/NetworkScenePostProcess.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3ec1c414d821444a9e77f18a2c130ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
19
Assets/Mirror/Editor/ReadOnlyDrawer.cs
Normal file
19
Assets/Mirror/Editor/ReadOnlyDrawer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Cache the current GUI enabled state
|
||||
bool prevGuiEnabledState = GUI.enabled;
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
GUI.enabled = prevGuiEnabledState;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/ReadOnlyDrawer.cs.meta
Normal file
11
Assets/Mirror/Editor/ReadOnlyDrawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22f17bdd21f104c41bc175937fefbdec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
47
Assets/Mirror/Editor/SceneDrawer.cs
Normal file
47
Assets/Mirror/Editor/SceneDrawer.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SceneAttribute))]
|
||||
public class SceneDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (property.propertyType == SerializedPropertyType.String)
|
||||
{
|
||||
SceneAsset sceneObject = AssetDatabase.LoadAssetAtPath<SceneAsset>(property.stringValue);
|
||||
|
||||
if (sceneObject == null && !string.IsNullOrWhiteSpace(property.stringValue))
|
||||
{
|
||||
// try to load it from the build settings for legacy compatibility
|
||||
sceneObject = GetBuildSettingsSceneObject(property.stringValue);
|
||||
}
|
||||
if (sceneObject == null && !string.IsNullOrWhiteSpace(property.stringValue))
|
||||
{
|
||||
Debug.LogError($"Could not find scene {property.stringValue} in {property.propertyPath}, assign the proper scenes in your NetworkManager");
|
||||
}
|
||||
SceneAsset scene = (SceneAsset)EditorGUI.ObjectField(position, label, sceneObject, typeof(SceneAsset), true);
|
||||
|
||||
property.stringValue = AssetDatabase.GetAssetPath(scene);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.LabelField(position, label.text, "Use [Scene] with strings.");
|
||||
}
|
||||
}
|
||||
|
||||
protected SceneAsset GetBuildSettingsSceneObject(string sceneName)
|
||||
{
|
||||
foreach (EditorBuildSettingsScene buildScene in EditorBuildSettings.scenes)
|
||||
{
|
||||
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(buildScene.path);
|
||||
if (sceneAsset!= null && sceneAsset.name == sceneName)
|
||||
{
|
||||
return sceneAsset;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/SceneDrawer.cs.meta
Normal file
11
Assets/Mirror/Editor/SceneDrawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b24704a46211b4ea294aba8f58715cea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
83
Assets/Mirror/Editor/SyncObjectCollectionsDrawer.cs
Normal file
83
Assets/Mirror/Editor/SyncObjectCollectionsDrawer.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// helper class for NetworkBehaviourInspector to draw all enumerable SyncObjects
|
||||
// (SyncList/Set/Dictionary)
|
||||
// 'SyncObjectCollectionsDrawer' is a nicer name than 'IEnumerableSyncObjectsDrawer'
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
class SyncObjectCollectionField
|
||||
{
|
||||
public bool visible;
|
||||
public readonly FieldInfo field;
|
||||
public readonly string label;
|
||||
|
||||
public SyncObjectCollectionField(FieldInfo field)
|
||||
{
|
||||
this.field = field;
|
||||
visible = false;
|
||||
label = $"{field.Name} [{field.FieldType.Name}]";
|
||||
}
|
||||
}
|
||||
|
||||
public class SyncObjectCollectionsDrawer
|
||||
{
|
||||
readonly UnityEngine.Object targetObject;
|
||||
readonly List<SyncObjectCollectionField> syncObjectCollectionFields;
|
||||
|
||||
public SyncObjectCollectionsDrawer(UnityEngine.Object targetObject)
|
||||
{
|
||||
this.targetObject = targetObject;
|
||||
syncObjectCollectionFields = new List<SyncObjectCollectionField>();
|
||||
foreach (FieldInfo field in InspectorHelper.GetAllFields(targetObject.GetType(), typeof(NetworkBehaviour)))
|
||||
{
|
||||
// only draw SyncObjects that are IEnumerable (SyncList/Set/Dictionary)
|
||||
if (field.IsVisibleSyncObject() &&
|
||||
field.ImplementsInterface<SyncObject>() &&
|
||||
field.ImplementsInterface<IEnumerable>())
|
||||
{
|
||||
syncObjectCollectionFields.Add(new SyncObjectCollectionField(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (syncObjectCollectionFields.Count == 0) { return; }
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Sync Collections", EditorStyles.boldLabel);
|
||||
|
||||
for (int i = 0; i < syncObjectCollectionFields.Count; i++)
|
||||
{
|
||||
DrawSyncObjectCollection(syncObjectCollectionFields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSyncObjectCollection(SyncObjectCollectionField syncObjectCollectionField)
|
||||
{
|
||||
syncObjectCollectionField.visible = EditorGUILayout.Foldout(syncObjectCollectionField.visible, syncObjectCollectionField.label);
|
||||
if (syncObjectCollectionField.visible)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
object fieldValue = syncObjectCollectionField.field.GetValue(targetObject);
|
||||
if (fieldValue is IEnumerable syncObject)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (object item in syncObject)
|
||||
{
|
||||
string itemValue = item != null ? item.ToString() : "NULL";
|
||||
string itemLabel = $"Element {index}";
|
||||
EditorGUILayout.LabelField(itemLabel, itemValue);
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Mirror/Editor/SyncObjectCollectionsDrawer.cs.meta
Normal file
3
Assets/Mirror/Editor/SyncObjectCollectionsDrawer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f90afab12e04f0e945d83e9d38308a3
|
||||
timeCreated: 1632556645
|
28
Assets/Mirror/Editor/SyncVarAttributeDrawer.cs
Normal file
28
Assets/Mirror/Editor/SyncVarAttributeDrawer.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SyncVarAttribute))]
|
||||
public class SyncVarAttributeDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
Vector2 syncVarIndicatorRect = EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent);
|
||||
float valueWidth = position.width - syncVarIndicatorRect.x;
|
||||
|
||||
Rect valueRect = new Rect(position.x, position.y, valueWidth, position.height);
|
||||
Rect labelRect = new Rect(position.x + valueWidth, position.y, syncVarIndicatorRect.x, position.height);
|
||||
|
||||
EditorGUI.PropertyField(valueRect, property, label, true);
|
||||
GUI.Label(labelRect, syncVarIndicatorContent, EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUI.GetPropertyHeight(property);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/SyncVarAttributeDrawer.cs.meta
Normal file
11
Assets/Mirror/Editor/SyncVarAttributeDrawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27821afc81c4d064d8348fbeb00c0ce8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Editor/Weaver.meta
Normal file
8
Assets/Mirror/Editor/Weaver.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9f8e6274119b4ce29e498cfb8aca8a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
Assets/Mirror/Editor/Weaver/AssemblyInfo.cs
Normal file
3
Assets/Mirror/Editor/Weaver/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Mirror.Tests")]
|
11
Assets/Mirror/Editor/Weaver/AssemblyInfo.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/AssemblyInfo.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 929924d95663264478d4238d4910d22e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
Assets/Mirror/Editor/Weaver/EntryPoint.meta
Normal file
3
Assets/Mirror/Editor/Weaver/EntryPoint.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 251338e67afb4cefa38da924f8c50a6e
|
||||
timeCreated: 1628851818
|
@ -0,0 +1,188 @@
|
||||
// for Unity 2020+ we use ILPostProcessor.
|
||||
// only automatically invoke it for older versions.
|
||||
#if !UNITY_2020_3_OR_NEWER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
using UnityAssembly = UnityEditor.Compilation.Assembly;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class CompilationFinishedHook
|
||||
{
|
||||
// needs to be the same as Weaver.MirrorAssemblyName!
|
||||
const string MirrorRuntimeAssemblyName = "Mirror";
|
||||
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
|
||||
|
||||
// global weaver define so that tests can use it
|
||||
internal static Weaver weaver;
|
||||
|
||||
// delegate for subscription to Weaver warning messages
|
||||
public static Action<string> OnWeaverWarning;
|
||||
// delete for subscription to Weaver error messages
|
||||
public static Action<string> OnWeaverError;
|
||||
|
||||
// controls whether Weaver errors are reported direct to the Unity console (tests enable this)
|
||||
public static bool UnityLogEnabled = true;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
public static void OnInitializeOnLoad()
|
||||
{
|
||||
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
|
||||
|
||||
// We only need to run this once per session
|
||||
// after that, all assemblies will be weaved by the event
|
||||
if (!SessionState.GetBool("MIRROR_WEAVED", false))
|
||||
{
|
||||
// reset session flag
|
||||
SessionState.SetBool("MIRROR_WEAVED", true);
|
||||
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
|
||||
|
||||
WeaveExistingAssemblies();
|
||||
}
|
||||
}
|
||||
|
||||
public static void WeaveExistingAssemblies()
|
||||
{
|
||||
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (File.Exists(assembly.outputPath))
|
||||
{
|
||||
OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]);
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
static Assembly FindCompilationPipelineAssembly(string assemblyName) =>
|
||||
CompilationPipeline.GetAssemblies().First(assembly => assembly.name == assemblyName);
|
||||
|
||||
static bool CompilerMessagesContainError(CompilerMessage[] messages) =>
|
||||
messages.Any(msg => msg.type == CompilerMessageType.Error);
|
||||
|
||||
public static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
|
||||
{
|
||||
// Do nothing if there were compile errors on the target
|
||||
if (CompilerMessagesContainError(messages))
|
||||
{
|
||||
Debug.Log("Weaver: stop because compile errors on target");
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not run on the editor only assemblies (test ones still need to be weaved)
|
||||
if (assemblyPath.Contains("-Editor") ||
|
||||
(assemblyPath.Contains(".Editor") && !assemblyPath.Contains(".Tests")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// skip Mirror.dll because CompilationFinishedHook can't weave itself.
|
||||
// this would cause a sharing violation.
|
||||
// skip Mirror.Weaver.dll too.
|
||||
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// find Mirror.dll
|
||||
Assembly mirrorAssembly = FindCompilationPipelineAssembly(MirrorRuntimeAssemblyName);
|
||||
if (mirrorAssembly == null)
|
||||
{
|
||||
Debug.LogError("Failed to find Mirror runtime assembly");
|
||||
return;
|
||||
}
|
||||
|
||||
string mirrorRuntimeDll = mirrorAssembly.outputPath;
|
||||
if (!File.Exists(mirrorRuntimeDll))
|
||||
{
|
||||
// this is normal, it happens with any assembly that is built before mirror
|
||||
// such as unity packages or your own assemblies
|
||||
// those don't need to be weaved
|
||||
// if any assembly depends on mirror, then it will be built after
|
||||
return;
|
||||
}
|
||||
|
||||
// find UnityEngine.CoreModule.dll
|
||||
string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
|
||||
if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
|
||||
{
|
||||
Debug.LogError("Failed to find UnityEngine assembly");
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<string> dependencyPaths = GetDependencyPaths(assemblyPath);
|
||||
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
|
||||
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
|
||||
|
||||
if (!WeaveFromFile(assemblyPath, dependencyPaths.ToArray()))
|
||||
{
|
||||
// Set false...will be checked in \Editor\EnterPlayModeSettingsCheck.CheckSuccessfulWeave()
|
||||
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", false);
|
||||
if (UnityLogEnabled) Debug.LogError($"Weaving failed for {assemblyPath}");
|
||||
}
|
||||
}
|
||||
|
||||
static HashSet<string> GetDependencyPaths(string assemblyPath)
|
||||
{
|
||||
// build directory list for later asm/symbol resolving using CompilationPipeline refs
|
||||
HashSet<string> dependencyPaths = new HashSet<string>
|
||||
{
|
||||
Path.GetDirectoryName(assemblyPath)
|
||||
};
|
||||
foreach (Assembly assembly in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (assembly.outputPath == assemblyPath)
|
||||
{
|
||||
foreach (string reference in assembly.compiledAssemblyReferences)
|
||||
{
|
||||
dependencyPaths.Add(Path.GetDirectoryName(reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencyPaths;
|
||||
}
|
||||
// helper function to invoke Weaver with an AssemblyDefinition from a
|
||||
// file path, with dependencies added.
|
||||
static bool WeaveFromFile(string assemblyPath, string[] dependencies)
|
||||
{
|
||||
// resolve assembly from stream
|
||||
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
|
||||
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
|
||||
{
|
||||
// add this assembly's path and unity's assembly path
|
||||
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
|
||||
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
|
||||
|
||||
// add dependencies
|
||||
if (dependencies != null)
|
||||
{
|
||||
foreach (string path in dependencies)
|
||||
{
|
||||
asmResolver.AddSearchDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
// create weaver with logger
|
||||
weaver = new Weaver(new CompilationFinishedLogger());
|
||||
if (weaver.Weave(assembly, asmResolver, out bool modified))
|
||||
{
|
||||
// write changes to file if modified
|
||||
if (modified)
|
||||
assembly.Write(new WriterParameters{WriteSymbols = true});
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de2aeb2e8068f421a9a1febe408f7051
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,31 @@
|
||||
// logger for compilation finished hook.
|
||||
// where we need a callback and Debug.Log.
|
||||
// for Unity 2020+ we use ILPostProcessor.
|
||||
#if !UNITY_2020_3_OR_NEWER
|
||||
using Mono.CecilX;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public class CompilationFinishedLogger : Logger
|
||||
{
|
||||
public void Warning(string message) => Warning(message, null);
|
||||
public void Warning(string message, MemberReference mr)
|
||||
{
|
||||
if (mr != null) message = $"{message} (at {mr})";
|
||||
|
||||
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogWarning(message);
|
||||
CompilationFinishedHook.OnWeaverWarning?.Invoke(message);
|
||||
}
|
||||
|
||||
public void Error(string message) => Error(message, null);
|
||||
public void Error(string message, MemberReference mr)
|
||||
{
|
||||
if (mr != null) message = $"{message} (at {mr})";
|
||||
|
||||
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogError(message);
|
||||
CompilationFinishedHook.OnWeaverError?.Invoke(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47026732f0fa475c94bd1dd41f1de559
|
||||
timeCreated: 1629379868
|
44
Assets/Mirror/Editor/Weaver/EntryPoint/EnterPlayModeHook.cs
Normal file
44
Assets/Mirror/Editor/Weaver/EntryPoint/EnterPlayModeHook.cs
Normal file
@ -0,0 +1,44 @@
|
||||
#if !UNITY_2020_3_OR_NEWER
|
||||
// make sure we weaved successfully when entering play mode.
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class EnterPlayModeSettingsCheck : MonoBehaviour
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnInitializeOnLoad()
|
||||
{
|
||||
// Hook this event to see if we have a good weave every time
|
||||
// user attempts to enter play mode or tries to do a build
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
static void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
// Per Unity docs, this fires "when exiting edit mode before the Editor is in play mode".
|
||||
// This doesn't fire when closing the editor.
|
||||
if (state == PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
// Check if last weave result was successful
|
||||
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
|
||||
{
|
||||
// Last weave result was a failure...try to weave again
|
||||
// Faults will show in the console that may have been cleared by "Clear on Play"
|
||||
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
|
||||
Weaver.CompilationFinishedHook.WeaveExistingAssemblies();
|
||||
|
||||
// Did that clear things up for us?
|
||||
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
|
||||
{
|
||||
// Nope, still failed, and console has the issues logged
|
||||
Debug.LogError("Can't enter play mode until weaver issues are resolved.");
|
||||
EditorApplication.isPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b73d0f106ba84aa983baa5142b08a0a9
|
||||
timeCreated: 1628851346
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09082db63d1d48d9ab91320165c1b684
|
||||
timeCreated: 1628859005
|
@ -0,0 +1,31 @@
|
||||
// tests use WeaveAssembler, which uses AssemblyBuilder to Build().
|
||||
// afterwards ILPostProcessor weaves the build.
|
||||
// this works on windows, but build() does not run ILPP on mac atm.
|
||||
// we need to manually invoke ILPP with an assembly from file.
|
||||
//
|
||||
// this is in Weaver folder becuase CompilationPipeline can only be accessed
|
||||
// from assemblies with the name "Unity.*.CodeGen"
|
||||
using System.IO;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public class CompiledAssemblyFromFile : ICompiledAssembly
|
||||
{
|
||||
readonly string assemblyPath;
|
||||
|
||||
public string Name => Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
public string[] References { get; set; }
|
||||
public string[] Defines { get; set; }
|
||||
public InMemoryAssembly InMemoryAssembly { get; }
|
||||
|
||||
public CompiledAssemblyFromFile(string assemblyPath)
|
||||
{
|
||||
this.assemblyPath = assemblyPath;
|
||||
byte[] peData = File.ReadAllBytes(assemblyPath);
|
||||
string pdbFileName = Path.GetFileNameWithoutExtension(assemblyPath) + ".pdb";
|
||||
byte[] pdbData = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(assemblyPath), pdbFileName));
|
||||
InMemoryAssembly = new InMemoryAssembly(peData, pdbData);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9009d1db4ed44f6694a92bf8ad7738e9
|
||||
timeCreated: 1630129423
|
@ -0,0 +1,199 @@
|
||||
// based on paul's resolver from
|
||||
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
|
||||
//
|
||||
// an assembly resolver's job is to open an assembly in case we want to resolve
|
||||
// a type from it.
|
||||
//
|
||||
// for example, while weaving MyGame.dll: if we want to resolve ArraySegment<T>,
|
||||
// then we need to open and resolve from another assembly (CoreLib).
|
||||
//
|
||||
// using DefaultAssemblyResolver with ILPostProcessor throws Exceptions in
|
||||
// WeaverTypes.cs when resolving anything, for example:
|
||||
// ArraySegment<T> in Mirror.Tests.Dll.
|
||||
//
|
||||
// we need a custom resolver for ILPostProcessor.
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Mono.CecilX;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
class ILPostProcessorAssemblyResolver : IAssemblyResolver
|
||||
{
|
||||
readonly string[] assemblyReferences;
|
||||
|
||||
// originally we used Dictionary + lock.
|
||||
// Resolve() is called thousands of times for large projects.
|
||||
// ILPostProcessor is multithreaded, so best to use ConcurrentDictionary without the lock here.
|
||||
readonly ConcurrentDictionary<string, AssemblyDefinition> assemblyCache =
|
||||
new ConcurrentDictionary<string, AssemblyDefinition>();
|
||||
|
||||
// Resolve() calls FindFile() every time.
|
||||
// thousands of times for String => mscorlib alone in large projects.
|
||||
// cache the results! ILPostProcessor is multithreaded, so use a ConcurrentDictionary here.
|
||||
readonly ConcurrentDictionary<string, string> fileNameCache =
|
||||
new ConcurrentDictionary<string, string>();
|
||||
|
||||
readonly ICompiledAssembly compiledAssembly;
|
||||
AssemblyDefinition selfAssembly;
|
||||
|
||||
readonly Logger Log;
|
||||
|
||||
public ILPostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly, Logger Log)
|
||||
{
|
||||
this.compiledAssembly = compiledAssembly;
|
||||
assemblyReferences = compiledAssembly.References;
|
||||
this.Log = Log;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Cleanup
|
||||
}
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name) =>
|
||||
Resolve(name, new ReaderParameters(ReadingMode.Deferred));
|
||||
|
||||
// here is an example on when this is called:
|
||||
// Player : NetworkBehaviour has a [SyncVar] of type String.
|
||||
// Weaver's SyncObjectInitializer checks if ImplementsSyncObject()
|
||||
// which needs to resolve the type 'String' from mscorlib.
|
||||
// Resolve() lives in CecilX.MetadataResolver.Resolve()
|
||||
// which calls assembly_resolver.Resolve().
|
||||
// which uses our ILPostProcessorAssemblyResolver here.
|
||||
//
|
||||
// for large projects, this is called thousands of times for mscorlib alone.
|
||||
// initially ILPostProcessorAssemblyResolver took 30x longer than with CompilationFinishedHook.
|
||||
// we need to cache and speed up everything we can here!
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
|
||||
{
|
||||
if (name.Name == compiledAssembly.Name)
|
||||
return selfAssembly;
|
||||
|
||||
// cache FindFile.
|
||||
// in large projects, this is called thousands(!) of times for String=>mscorlib alone.
|
||||
// reduces a single String=>mscorlib resolve from 0.771ms to 0.015ms.
|
||||
// => 50x improvement in TypeReference.Resolve() speed!
|
||||
// => 22x improvement in Weaver speed!
|
||||
if (!fileNameCache.TryGetValue(name.Name, out string fileName))
|
||||
{
|
||||
fileName = FindFile(name.Name);
|
||||
fileNameCache.TryAdd(name.Name, fileName);
|
||||
}
|
||||
|
||||
if (fileName == null)
|
||||
{
|
||||
// returning null will throw exceptions in our weaver where.
|
||||
// let's make it obvious why we returned null for easier debugging.
|
||||
// NOTE: if this fails for "System.Private.CoreLib":
|
||||
// ILPostProcessorReflectionImporter fixes it!
|
||||
Log.Warning($"ILPostProcessorAssemblyResolver.Resolve: Failed to find file for {name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to get cached assembly by filename + writetime
|
||||
DateTime lastWriteTime = File.GetLastWriteTime(fileName);
|
||||
string cacheKey = fileName + lastWriteTime;
|
||||
if (assemblyCache.TryGetValue(cacheKey, out AssemblyDefinition result))
|
||||
return result;
|
||||
|
||||
// otherwise resolve and cache a new assembly
|
||||
parameters.AssemblyResolver = this;
|
||||
MemoryStream ms = MemoryStreamFor(fileName);
|
||||
|
||||
string pdb = fileName + ".pdb";
|
||||
if (File.Exists(pdb))
|
||||
parameters.SymbolStream = MemoryStreamFor(pdb);
|
||||
|
||||
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
|
||||
assemblyCache.TryAdd(cacheKey, assemblyDefinition);
|
||||
return assemblyDefinition;
|
||||
}
|
||||
|
||||
// find assemblyname in assembly's references
|
||||
string FindFile(string name)
|
||||
{
|
||||
// perhaps the type comes from a .dll or .exe
|
||||
// check both in one call without Linq instead of iterating twice like originally
|
||||
foreach (string r in assemblyReferences)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(r) == name)
|
||||
return r;
|
||||
}
|
||||
|
||||
// this is called thousands(!) of times.
|
||||
// constructing strings only once saves ~0.1ms per call for mscorlib.
|
||||
string dllName = name + ".dll";
|
||||
|
||||
// Unfortunately the current ICompiledAssembly API only provides direct references.
|
||||
// It is very much possible that a postprocessor ends up investigating a type in a directly
|
||||
// referenced assembly, that contains a field that is not in a directly referenced assembly.
|
||||
// if we don't do anything special for that situation, it will fail to resolve. We should fix this
|
||||
// in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
|
||||
// are always located next to direct references, so we search in all directories of direct references we
|
||||
// got passed, and if we find the file in there, we resolve to it.
|
||||
foreach (string parentDir in assemblyReferences.Select(Path.GetDirectoryName).Distinct())
|
||||
{
|
||||
string candidate = Path.Combine(parentDir, dllName);
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// open file as MemoryStream.
|
||||
// ILPostProcessor is multithreaded.
|
||||
// retry a few times in case another thread is still accessing the file.
|
||||
static MemoryStream MemoryStreamFor(string fileName)
|
||||
{
|
||||
return Retry(10, TimeSpan.FromSeconds(1), () =>
|
||||
{
|
||||
byte[] byteArray;
|
||||
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
byteArray = new byte[fs.Length];
|
||||
int readLength = fs.Read(byteArray, 0, (int)fs.Length);
|
||||
if (readLength != fs.Length)
|
||||
throw new InvalidOperationException("File read length is not full length of file.");
|
||||
}
|
||||
|
||||
return new MemoryStream(byteArray);
|
||||
});
|
||||
}
|
||||
|
||||
static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
|
||||
{
|
||||
try
|
||||
{
|
||||
return func();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (retryCount == 0)
|
||||
throw;
|
||||
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
|
||||
Thread.Sleep(waitTime);
|
||||
return Retry(retryCount - 1, waitTime, func);
|
||||
}
|
||||
}
|
||||
|
||||
// if the CompiledAssembly's AssemblyDefinition is known, we can add it
|
||||
public void SetAssemblyDefinitionForCompiledAssembly(AssemblyDefinition assemblyDefinition)
|
||||
{
|
||||
selfAssembly = assemblyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b3e94696e22440ead0b3a42411bbe14
|
||||
timeCreated: 1629693784
|
@ -0,0 +1,53 @@
|
||||
// helper function to use ILPostProcessor for an assembly from file.
|
||||
// we keep this in Weaver folder because we can access CompilationPipleine here.
|
||||
// in tests folder we can't, unless we rename to "Unity.*.CodeGen",
|
||||
// but then tests wouldn't be weaved anymore.
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
using System;
|
||||
using System.IO;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ILPostProcessorFromFile
|
||||
{
|
||||
// read, weave, write file via ILPostProcessor
|
||||
public static void ILPostProcessFile(string assemblyPath, string[] references, Action<string> OnWarning, Action<string> OnError)
|
||||
{
|
||||
// we COULD Weave() with a test logger manually.
|
||||
// but for test result consistency on all platforms,
|
||||
// let's invoke the ILPostProcessor here too.
|
||||
CompiledAssemblyFromFile assembly = new CompiledAssemblyFromFile(assemblyPath);
|
||||
assembly.References = references;
|
||||
|
||||
// create ILPP and check WillProcess like Unity would.
|
||||
ILPostProcessorHook ilpp = new ILPostProcessorHook();
|
||||
if (ilpp.WillProcess(assembly))
|
||||
{
|
||||
//Debug.Log($"Will Process: {assembly.Name}");
|
||||
|
||||
// process it like Unity would
|
||||
ILPostProcessResult result = ilpp.Process(assembly);
|
||||
|
||||
// handle the error messages like Unity would
|
||||
foreach (DiagnosticMessage message in result.Diagnostics)
|
||||
{
|
||||
if (message.DiagnosticType == DiagnosticType.Warning)
|
||||
{
|
||||
OnWarning(message.MessageData);
|
||||
}
|
||||
else if (message.DiagnosticType == DiagnosticType.Error)
|
||||
{
|
||||
OnError(message.MessageData);
|
||||
}
|
||||
}
|
||||
|
||||
// save the weaved assembly to file.
|
||||
// some tests open it and check for certain IL code.
|
||||
File.WriteAllBytes(assemblyPath, result.InMemoryAssembly.PeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a4b115486b74d27a9540f3c39ae2d46
|
||||
timeCreated: 1630152191
|
@ -0,0 +1,143 @@
|
||||
// hook via ILPostProcessor from Unity 2020.3+
|
||||
// (2020.1 has errors https://github.com/vis2k/Mirror/issues/2912)
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
// Unity.CompilationPipeline reference is only resolved if assembly name is
|
||||
// Unity.*.CodeGen:
|
||||
// https://forum.unity.com/threads/how-does-unity-do-codegen-and-why-cant-i-do-it-myself.853867/#post-5646937
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
// to use Mono.CecilX here, we need to 'override references' in the
|
||||
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.
|
||||
// otherwise we get a reflection exception with 'file not found: CecilX'.
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
// IMPORTANT: 'using UnityEngine' does not work in here.
|
||||
// Unity gives "(0,0): error System.Security.SecurityException: ECall methods must be packaged into a system module."
|
||||
//using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public class ILPostProcessorHook : ILPostProcessor
|
||||
{
|
||||
// from CompilationFinishedHook
|
||||
const string MirrorRuntimeAssemblyName = "Mirror";
|
||||
|
||||
// ILPostProcessor is invoked by Unity.
|
||||
// we can not tell it to ignore certain assemblies before processing.
|
||||
// add a 'ignore' define for convenience.
|
||||
// => WeaverTests/WeaverAssembler need it to avoid Unity running it
|
||||
public const string IgnoreDefine = "ILPP_IGNORE";
|
||||
|
||||
// we can't use Debug.Log in ILPP, so we need a custom logger
|
||||
ILPostProcessorLogger Log = new ILPostProcessorLogger();
|
||||
|
||||
// ???
|
||||
public override ILPostProcessor GetInstance() => this;
|
||||
|
||||
// check if assembly has the 'ignore' define
|
||||
static bool HasDefine(ICompiledAssembly assembly, string define) =>
|
||||
assembly.Defines != null &&
|
||||
assembly.Defines.Contains(define);
|
||||
|
||||
// process Mirror, or anything that references Mirror
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
// compiledAssembly.References are file paths:
|
||||
// Library/Bee/artifacts/200b0aE.dag/Mirror.CompilerSymbols.dll
|
||||
// Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll
|
||||
// /Applications/Unity/Hub/Editor/2021.2.0b6_apple_silicon/Unity.app/Contents/NetStandard/ref/2.1.0/netstandard.dll
|
||||
//
|
||||
// log them to see:
|
||||
// foreach (string reference in compiledAssembly.References)
|
||||
// LogDiagnostics($"{compiledAssembly.Name} references {reference}");
|
||||
bool relevant = compiledAssembly.Name == MirrorRuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == MirrorRuntimeAssemblyName);
|
||||
bool ignore = HasDefine(compiledAssembly, IgnoreDefine);
|
||||
return relevant && !ignore;
|
||||
}
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
//Log.Warning($"Processing {compiledAssembly.Name}");
|
||||
|
||||
// load the InMemoryAssembly peData into a MemoryStream
|
||||
byte[] peData = compiledAssembly.InMemoryAssembly.PeData;
|
||||
//LogDiagnostics($" peData.Length={peData.Length} bytes");
|
||||
using (MemoryStream stream = new MemoryStream(peData))
|
||||
using (ILPostProcessorAssemblyResolver asmResolver = new ILPostProcessorAssemblyResolver(compiledAssembly, Log))
|
||||
{
|
||||
// we need to load symbols. otherwise we get:
|
||||
// "(0,0): error Mono.CecilX.Cil.SymbolsNotFoundException: No symbol found for file: "
|
||||
using (MemoryStream symbols = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData))
|
||||
{
|
||||
ReaderParameters readerParameters = new ReaderParameters{
|
||||
SymbolStream = symbols,
|
||||
ReadWrite = true,
|
||||
ReadSymbols = true,
|
||||
AssemblyResolver = asmResolver,
|
||||
// custom reflection importer to fix System.Private.CoreLib
|
||||
// not being found in custom assembly resolver above.
|
||||
ReflectionImporterProvider = new ILPostProcessorReflectionImporterProvider()
|
||||
};
|
||||
using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(stream, readerParameters))
|
||||
{
|
||||
// resolving a Mirror.dll type like NetworkServer while
|
||||
// weaving Mirror.dll does not work. it throws a
|
||||
// NullReferenceException in WeaverTypes.ctor
|
||||
// when Resolve() is called on the first Mirror type.
|
||||
// need to add the AssemblyDefinition itself to use.
|
||||
asmResolver.SetAssemblyDefinitionForCompiledAssembly(asmDef);
|
||||
|
||||
// weave this assembly.
|
||||
Weaver weaver = new Weaver(Log);
|
||||
if (weaver.Weave(asmDef, asmResolver, out bool modified))
|
||||
{
|
||||
//Log.Warning($"Weaving succeeded for: {compiledAssembly.Name}");
|
||||
|
||||
// write if modified
|
||||
if (modified)
|
||||
{
|
||||
// when weaving Mirror.dll with ILPostProcessor,
|
||||
// Weave() -> WeaverTypes -> resolving the first
|
||||
// type in Mirror.dll adds a reference to
|
||||
// Mirror.dll even though we are in Mirror.dll.
|
||||
// -> this would throw an exception:
|
||||
// "Mirror references itself" and not compile
|
||||
// -> need to detect and fix manually here
|
||||
if (asmDef.MainModule.AssemblyReferences.Any(r => r.Name == asmDef.Name.Name))
|
||||
{
|
||||
asmDef.MainModule.AssemblyReferences.Remove(asmDef.MainModule.AssemblyReferences.First(r => r.Name == asmDef.Name.Name));
|
||||
//Log.Warning($"fixed self referencing Assembly: {asmDef.Name.Name}");
|
||||
}
|
||||
|
||||
MemoryStream peOut = new MemoryStream();
|
||||
MemoryStream pdbOut = new MemoryStream();
|
||||
WriterParameters writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdbOut,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
asmDef.Write(peOut, writerParameters);
|
||||
|
||||
InMemoryAssembly inMemory = new InMemoryAssembly(peOut.ToArray(), pdbOut.ToArray());
|
||||
return new ILPostProcessResult(inMemory, Log.Logs);
|
||||
}
|
||||
}
|
||||
// if anything during Weave() fails, we log an error.
|
||||
// don't need to indicate 'weaving failed' again.
|
||||
// in fact, this would break tests only expecting certain errors.
|
||||
//else Log.Error($"Weaving failed for: {compiledAssembly.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always return an ILPostProcessResult with Logs.
|
||||
// otherwise we won't see Logs if weaving failed.
|
||||
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, Log.Logs);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f113eb695b348b5b28cd85358c8959a
|
||||
timeCreated: 1628859074
|
@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public class ILPostProcessorLogger : Logger
|
||||
{
|
||||
// can't Debug.Log in ILPostProcessor. need to add to this list.
|
||||
internal List<DiagnosticMessage> Logs = new List<DiagnosticMessage>();
|
||||
|
||||
void Add(string message, DiagnosticType logType)
|
||||
{
|
||||
Logs.Add(new DiagnosticMessage
|
||||
{
|
||||
// TODO add file etc. for double click opening later?
|
||||
DiagnosticType = logType, // doesn't have .Log
|
||||
File = null,
|
||||
Line = 0,
|
||||
Column = 0,
|
||||
MessageData = message
|
||||
});
|
||||
}
|
||||
|
||||
public void LogDiagnostics(string message, DiagnosticType logType = DiagnosticType.Warning)
|
||||
{
|
||||
// TODO IN-44868 FIX IS IN 2021.3.32f1, 2022.3.11f1, 2023.2.0b13 and 2023.3.0a8
|
||||
// DiagnosticMessage can't display \n for some reason.
|
||||
// it just cuts it off and we don't see any stack trace.
|
||||
// so let's replace all line breaks so we get the stack trace.
|
||||
// (Unity 2021.2.0b6 apple silicon)
|
||||
//message = message.Replace("\n", "/");
|
||||
|
||||
// lets break it into several messages instead so it's easier readable
|
||||
string[] lines = message.Split('\n');
|
||||
|
||||
// if it's just one line, simply log it
|
||||
if (lines.Length == 1)
|
||||
{
|
||||
// tests assume exact message log.
|
||||
// don't include 'Weaver: ...' or similar.
|
||||
Add($"{message}", logType);
|
||||
}
|
||||
// for multiple lines, log each line separately with start/end indicators
|
||||
else
|
||||
{
|
||||
// first line with Weaver: ... first
|
||||
Add("----------------------------------------------", logType);
|
||||
foreach (string line in lines) Add(line, logType);
|
||||
Add("----------------------------------------------", logType);
|
||||
}
|
||||
}
|
||||
|
||||
public void Warning(string message) => Warning(message, null);
|
||||
public void Warning(string message, MemberReference mr)
|
||||
{
|
||||
if (mr != null) message = $"{message} (at {mr})";
|
||||
LogDiagnostics(message, DiagnosticType.Warning);
|
||||
}
|
||||
|
||||
public void Error(string message) => Error(message, null);
|
||||
public void Error(string message, MemberReference mr)
|
||||
{
|
||||
if (mr != null) message = $"{message} (at {mr})";
|
||||
LogDiagnostics(message, DiagnosticType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7b56e7826664e34a415e4b70d958f2a
|
||||
timeCreated: 1629533154
|
@ -0,0 +1,36 @@
|
||||
// based on paul's resolver from
|
||||
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
|
||||
//
|
||||
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
|
||||
// "System.Private.CoreLib"
|
||||
// we need this custom reflection importer to fix that.
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
internal class ILPostProcessorReflectionImporter : DefaultReflectionImporter
|
||||
{
|
||||
const string SystemPrivateCoreLib = "System.Private.CoreLib";
|
||||
readonly AssemblyNameReference fixedCoreLib;
|
||||
|
||||
public ILPostProcessorReflectionImporter(ModuleDefinition module) : base(module)
|
||||
{
|
||||
// find the correct library for "System.Private.CoreLib".
|
||||
// either mscorlib or netstandard.
|
||||
// defaults to System.Private.CoreLib if not found.
|
||||
fixedCoreLib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == SystemPrivateCoreLib);
|
||||
}
|
||||
|
||||
public override AssemblyNameReference ImportReference(AssemblyName name)
|
||||
{
|
||||
// System.Private.CoreLib?
|
||||
if (name.Name == SystemPrivateCoreLib && fixedCoreLib != null)
|
||||
return fixedCoreLib;
|
||||
|
||||
// otherwise import as usual
|
||||
return base.ImportReference(name);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6403a7e3b3ae4e009ae282f111d266e0
|
||||
timeCreated: 1629709256
|
@ -0,0 +1,16 @@
|
||||
// based on paul's resolver from
|
||||
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
|
||||
//
|
||||
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
|
||||
// "System.Private.CoreLib"
|
||||
// we need this custom reflection importer to fix that.
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
internal class ILPostProcessorReflectionImporterProvider : IReflectionImporterProvider
|
||||
{
|
||||
public IReflectionImporter GetReflectionImporter(ModuleDefinition module) =>
|
||||
new ILPostProcessorReflectionImporter(module);
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1003b568bad4e69b961c4c81d5afd96
|
||||
timeCreated: 1629709223
|
359
Assets/Mirror/Editor/Weaver/Extensions.cs
Normal file
359
Assets/Mirror/Editor/Weaver/Extensions.cs
Normal file
@ -0,0 +1,359 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static bool Is(this TypeReference td, Type type) =>
|
||||
type.IsGenericType
|
||||
? td.GetElementType().FullName == type.FullName
|
||||
: td.FullName == type.FullName;
|
||||
|
||||
// check if 'td' is exactly of type T.
|
||||
// it does not check if any base type is of <T>, only the specific type.
|
||||
// for example:
|
||||
// NetworkConnection Is NetworkConnection: true
|
||||
// NetworkConnectionToClient Is NetworkConnection: false
|
||||
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
|
||||
|
||||
// check if 'tr' is derived from T.
|
||||
// it does not check if 'tr' is exactly T.
|
||||
// for example:
|
||||
// NetworkConnection IsDerivedFrom<NetworkConnection>: false
|
||||
// NetworkConnectionToClient IsDerivedFrom<NetworkConnection>: true
|
||||
public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T));
|
||||
|
||||
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
|
||||
{
|
||||
TypeDefinition td = tr.Resolve();
|
||||
if (!td.IsClass)
|
||||
return false;
|
||||
|
||||
// are ANY parent classes of baseClass?
|
||||
TypeReference parent = td.BaseType;
|
||||
|
||||
if (parent == null)
|
||||
return false;
|
||||
|
||||
if (parent.Is(baseClass))
|
||||
return true;
|
||||
|
||||
if (parent.CanBeResolved())
|
||||
return IsDerivedFrom(parent.Resolve(), baseClass);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
|
||||
{
|
||||
foreach (FieldDefinition field in td.Fields)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
return field.FieldType;
|
||||
}
|
||||
throw new ArgumentException($"Invalid enum {td.FullName}");
|
||||
}
|
||||
|
||||
public static bool ImplementsInterface<TInterface>(this TypeDefinition td)
|
||||
{
|
||||
TypeDefinition typedef = td;
|
||||
|
||||
while (typedef != null)
|
||||
{
|
||||
if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
TypeReference parent = typedef.BaseType;
|
||||
typedef = parent?.Resolve();
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// this can happen for plugins.
|
||||
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMultidimensionalArray(this TypeReference tr) =>
|
||||
tr is ArrayType arrayType && arrayType.Rank > 1;
|
||||
|
||||
// Does type use netId as backing field
|
||||
public static bool IsNetworkIdentityField(this TypeReference tr) =>
|
||||
tr.Is<UnityEngine.GameObject>() ||
|
||||
tr.Is<NetworkIdentity>() ||
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
tr.IsDerivedFrom<NetworkBehaviour>() ||
|
||||
tr.Is<NetworkBehaviour>();
|
||||
|
||||
public static bool CanBeResolved(this TypeReference parent)
|
||||
{
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent.Scope.Name == "Windows")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent.Scope.Name == "mscorlib")
|
||||
{
|
||||
TypeDefinition resolved = parent.Resolve();
|
||||
return resolved != null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parent = parent.Resolve().BaseType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Makes T => Variable and imports function
|
||||
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
|
||||
{
|
||||
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
|
||||
instance.GenericArguments.Add(variableReference);
|
||||
|
||||
MethodReference readFunc = module.ImportReference(instance);
|
||||
return readFunc;
|
||||
}
|
||||
|
||||
// Given a method of a generic class such as ArraySegment`T.get_Count,
|
||||
// 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
|
||||
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
||||
{
|
||||
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
|
||||
{
|
||||
CallingConvention = self.CallingConvention,
|
||||
HasThis = self.HasThis,
|
||||
ExplicitThis = self.ExplicitThis
|
||||
};
|
||||
|
||||
foreach (ParameterDefinition parameter in self.Parameters)
|
||||
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
||||
|
||||
foreach (GenericParameter generic_parameter in self.GenericParameters)
|
||||
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
|
||||
|
||||
return module.ImportReference(reference);
|
||||
}
|
||||
|
||||
// needed for NetworkBehaviour<T> support
|
||||
// https://github.com/vis2k/Mirror/pull/3073/
|
||||
public static FieldReference MakeHostInstanceGeneric(this FieldReference self)
|
||||
{
|
||||
var declaringType = new GenericInstanceType(self.DeclaringType);
|
||||
foreach (var parameter in self.DeclaringType.GenericParameters)
|
||||
{
|
||||
declaringType.GenericArguments.Add(parameter);
|
||||
}
|
||||
return new FieldReference(self.Name, self.FieldType, declaringType);
|
||||
}
|
||||
|
||||
// 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
|
||||
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
|
||||
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
||||
{
|
||||
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
|
||||
return module.ImportReference(reference);
|
||||
}
|
||||
|
||||
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
|
||||
{
|
||||
return method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>());
|
||||
}
|
||||
|
||||
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
|
||||
{
|
||||
return attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
|
||||
}
|
||||
|
||||
public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue)
|
||||
{
|
||||
foreach (CustomAttributeNamedArgument customField in ca.Fields)
|
||||
if (customField.Name == field)
|
||||
return (T)customField.Argument.Value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethod(this TypeDefinition td, string methodName)
|
||||
{
|
||||
return td.Methods.FirstOrDefault(method => method.Name == methodName);
|
||||
}
|
||||
|
||||
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
|
||||
{
|
||||
return td.Methods.Where(method => method.Name == methodName).ToList();
|
||||
}
|
||||
|
||||
public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName)
|
||||
{
|
||||
TypeDefinition typedef = td;
|
||||
while (typedef != null)
|
||||
{
|
||||
foreach (MethodDefinition md in typedef.Methods)
|
||||
{
|
||||
if (md.Name == methodName)
|
||||
return md;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TypeReference parent = typedef.BaseType;
|
||||
typedef = parent?.Resolve();
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// this can happen for plugins.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finds public fields in type and base type
|
||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
|
||||
{
|
||||
return FindAllPublicFields(variable.Resolve());
|
||||
}
|
||||
|
||||
// Finds public fields in type and base type
|
||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
|
||||
{
|
||||
while (typeDefinition != null)
|
||||
{
|
||||
foreach (FieldDefinition field in typeDefinition.Fields)
|
||||
{
|
||||
// ignore static, private, protected fields
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3485
|
||||
// credit: James Frowen
|
||||
if (field.IsStatic || field.IsPrivate || field.IsFamily)
|
||||
continue;
|
||||
|
||||
// also ignore internal fields
|
||||
// we dont want to create different writers for this type if they are in current dll or another dll
|
||||
// so we have to ignore internal in all cases
|
||||
if (field.IsAssembly)
|
||||
continue;
|
||||
|
||||
if (field.IsNotSerialized)
|
||||
continue;
|
||||
|
||||
yield return field;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
typeDefinition = typeDefinition.BaseType?.Resolve();
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
|
||||
module.GetTypes().Any(td => td.Namespace == nameSpace &&
|
||||
td.Name == className);
|
||||
|
||||
|
||||
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
|
||||
{
|
||||
foreach (AssemblyNameReference reference in module.AssemblyReferences)
|
||||
{
|
||||
if (reference.Name == referenceName)
|
||||
return reference;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Takes generic arguments from child class and applies them to parent reference, if possible
|
||||
// eg makes `Base<T>` in Child<int> : Base<int> have `int` instead of `T`
|
||||
// Originally by James-Frowen under MIT
|
||||
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
|
||||
public static TypeReference ApplyGenericParameters(this TypeReference parentReference,
|
||||
TypeReference childReference)
|
||||
{
|
||||
// If the parent is not generic, we got nothing to apply
|
||||
if (!parentReference.IsGenericInstance)
|
||||
return parentReference;
|
||||
|
||||
GenericInstanceType parentGeneric = (GenericInstanceType)parentReference;
|
||||
// make new type so we can replace the args on it
|
||||
// resolve it so we have non-generic instance (eg just instance with <T> instead of <int>)
|
||||
// if we don't cecil will make it double generic (eg INVALID IL)
|
||||
GenericInstanceType generic = new GenericInstanceType(parentReference.Resolve());
|
||||
foreach (TypeReference arg in parentGeneric.GenericArguments)
|
||||
generic.GenericArguments.Add(arg);
|
||||
|
||||
for (int i = 0; i < generic.GenericArguments.Count; i++)
|
||||
{
|
||||
// if arg is not generic
|
||||
// eg List<int> would be int so not generic.
|
||||
// But List<T> would be T so is generic
|
||||
if (!generic.GenericArguments[i].IsGenericParameter)
|
||||
continue;
|
||||
|
||||
// get the generic name, eg T
|
||||
string name = generic.GenericArguments[i].Name;
|
||||
// find what type T is, eg turn it into `int` if `List<int>`
|
||||
TypeReference arg = FindMatchingGenericArgument(childReference, name);
|
||||
|
||||
// import just to be safe
|
||||
TypeReference imported = parentReference.Module.ImportReference(arg);
|
||||
// set arg on generic, parent ref will be Base<int> instead of just Base<T>
|
||||
generic.GenericArguments[i] = imported;
|
||||
}
|
||||
|
||||
return generic;
|
||||
}
|
||||
|
||||
// Finds the type reference for a generic parameter with the provided name in the child reference
|
||||
// Originally by James-Frowen under MIT
|
||||
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
|
||||
static TypeReference FindMatchingGenericArgument(TypeReference childReference, string paramName)
|
||||
{
|
||||
TypeDefinition def = childReference.Resolve();
|
||||
// child class must be generic if we are in this part of the code
|
||||
// eg Child<T> : Base<T> <--- child must have generic if Base has T
|
||||
// vs Child : Base<int> <--- wont be here if Base has int (we check if T exists before calling this)
|
||||
if (!def.HasGenericParameters)
|
||||
throw new InvalidOperationException(
|
||||
"Base class had generic parameters, but could not find them in child class");
|
||||
|
||||
// go through parameters in child class, and find the generic that matches the name
|
||||
for (int i = 0; i < def.GenericParameters.Count; i++)
|
||||
{
|
||||
GenericParameter param = def.GenericParameters[i];
|
||||
if (param.Name == paramName)
|
||||
{
|
||||
GenericInstanceType generic = (GenericInstanceType)childReference;
|
||||
// return generic arg with same index
|
||||
return generic.GenericArguments[i];
|
||||
}
|
||||
}
|
||||
|
||||
// this should never happen, if it does it means that this code is bugged
|
||||
throw new InvalidOperationException("Did not find matching generic");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Extensions.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Extensions.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 562a5cf0254cc45738e9aa549a7100b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
26
Assets/Mirror/Editor/Weaver/Helpers.cs
Normal file
26
Assets/Mirror/Editor/Weaver/Helpers.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class Helpers
|
||||
{
|
||||
// This code is taken from SerializationWeaver
|
||||
public static string UnityEngineDllDirectoryName()
|
||||
{
|
||||
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
|
||||
return directoryName?.Replace(@"file:\", "");
|
||||
}
|
||||
|
||||
public static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
// we want to add the [InitializeOnLoad] attribute if it's available
|
||||
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
|
||||
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
||||
assemblyReference.Name.StartsWith(nameof(UnityEditor))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Helpers.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Helpers.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c4ed76daf48547c5abb7c58f8d20886
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/Mirror/Editor/Weaver/Logger.cs
Normal file
13
Assets/Mirror/Editor/Weaver/Logger.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public interface Logger
|
||||
{
|
||||
void Warning(string message);
|
||||
void Warning(string message, MemberReference mr);
|
||||
void Error(string message);
|
||||
void Error(string message, MemberReference mr);
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Logger.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Logger.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a21c60c40a4c4d679c2b71a7c40882e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Mirror/Editor/Weaver/Processors.meta
Normal file
8
Assets/Mirror/Editor/Weaver/Processors.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e538d627280d2471b8c72fdea822ca49
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
130
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
130
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [Command] methods in NetworkBehaviour
|
||||
public static class CommandProcessor
|
||||
{
|
||||
/*
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriterPooled writer = NetworkWriterPool.Get();
|
||||
writer.Write(thrusting);
|
||||
writer.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, cmdHash, writer, channel);
|
||||
NetworkWriterPool.Return(writer);
|
||||
}
|
||||
|
||||
public void CallCmdThrust(float thrusting, int spin)
|
||||
{
|
||||
// whatever the user was doing before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to CmdTrust with CallCmdTrust
|
||||
|
||||
This method moves all the user's code into the "CallCmd" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
// NetworkWriter writer = new NetworkWriter();
|
||||
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the Cmd call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
int channel = commandAttr.GetField("channel", 0);
|
||||
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
|
||||
|
||||
// invoke internal send and return
|
||||
// load 'base.' to call the SendCommand function with
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||
// pass the function hash so we don't have to compute it at runtime
|
||||
// otherwise each GetStableHash call requires O(N) complexity.
|
||||
// noticeable for long function names:
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3375
|
||||
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// requiresAuthority ? 1 : 0
|
||||
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
||||
{
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
||||
}
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
string cmdName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, method);
|
||||
|
||||
MethodDefinition cmd = new MethodDefinition(cmdName,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
AddSenderConnection(method, worker);
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
||||
{
|
||||
foreach (ParameterDefinition param in method.Parameters)
|
||||
{
|
||||
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
||||
{
|
||||
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
||||
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||
worker.Emit(OpCodes.Ldarg_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
139
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
139
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class MethodProcessor
|
||||
{
|
||||
const string RpcPrefix = "UserCode_";
|
||||
|
||||
// For a function like
|
||||
// [ClientRpc] void RpcTest(int value),
|
||||
// Weaver substitutes the method and moves the code to a new method:
|
||||
// UserCode_RpcTest(int value) <- contains original code
|
||||
// RpcTest(int value) <- serializes parameters, sends the message
|
||||
//
|
||||
// Note that all the calls to the method remain untouched.
|
||||
// FixRemoteCallToBaseMethod replaces them afterwards.
|
||||
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
|
||||
{
|
||||
string newName = Weaver.GenerateMethodName(RpcPrefix, md);
|
||||
|
||||
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||
|
||||
// force the substitute method to be protected.
|
||||
// -> public would show in the Inspector for UnityEvents as
|
||||
// User_CmdUsePotion() etc. but the user shouldn't use those.
|
||||
// -> private would not allow inheriting classes to call it, see
|
||||
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
|
||||
// -> IL has no concept of 'protected', it's called IsFamily there.
|
||||
cmd.IsPublic = false;
|
||||
cmd.IsFamily = true;
|
||||
|
||||
// add parameters
|
||||
foreach (ParameterDefinition pd in md.Parameters)
|
||||
{
|
||||
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
||||
}
|
||||
|
||||
// swap bodies
|
||||
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
||||
|
||||
// Move over all the debugging information
|
||||
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
|
||||
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
||||
md.DebugInformation.SequencePoints.Clear();
|
||||
|
||||
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
|
||||
cmd.CustomDebugInformations.Add(customInfo);
|
||||
md.CustomDebugInformations.Clear();
|
||||
|
||||
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
|
||||
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// For a function like
|
||||
// [ClientRpc] void RpcTest(int value),
|
||||
// Weaver substitutes the method and moves the code to a new method:
|
||||
// UserCode_RpcTest(int value) <- contains original code
|
||||
// RpcTest(int value) <- serializes parameters, sends the message
|
||||
//
|
||||
// FixRemoteCallToBaseMethod replaces all calls to
|
||||
// RpcTest(value)
|
||||
// with
|
||||
// UserCode_RpcTest(value)
|
||||
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
|
||||
{
|
||||
string callName = method.Name;
|
||||
|
||||
// Cmd/rpc start with Weaver.RpcPrefix
|
||||
// e.g. CallCmdDoSomething
|
||||
if (!callName.StartsWith(RpcPrefix))
|
||||
return;
|
||||
|
||||
// e.g. CmdDoSomething
|
||||
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||
|
||||
foreach (Instruction instruction in method.Body.Instructions)
|
||||
{
|
||||
// is this instruction a Call to a method?
|
||||
// if yes, output the method so we can check it.
|
||||
if (IsCallToMethod(instruction, out MethodDefinition calledMethod))
|
||||
{
|
||||
// when considering if 'calledMethod' is a call to 'method',
|
||||
// we originally compared .Name.
|
||||
//
|
||||
// to fix IL2CPP build bugs with overloaded Rpcs, we need to
|
||||
// generated rpc names like
|
||||
// RpcTest(string value) => RpcTestString(strig value)
|
||||
// RpcTest(int value) => RpcTestInt(int value)
|
||||
// to make them unique.
|
||||
//
|
||||
// calledMethod.Name is still "RpcTest", so we need to
|
||||
// convert this to the generated name as well before comparing.
|
||||
string calledMethodName_Generated = Weaver.GenerateMethodName("", calledMethod);
|
||||
if (calledMethodName_Generated == baseRemoteCallName)
|
||||
{
|
||||
TypeDefinition baseType = type.BaseType.Resolve();
|
||||
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||
|
||||
if (baseMethod == null)
|
||||
{
|
||||
Log.Error($"Could not find base method for {callName}", method);
|
||||
WeavingFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseMethod.IsVirtual)
|
||||
{
|
||||
Log.Error($"Could not find base method that was virtual {callName}", method);
|
||||
WeavingFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
instruction.Operand = baseMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
||||
{
|
||||
if (instruction.OpCode == OpCodes.Call &&
|
||||
instruction.Operand is MethodDefinition method)
|
||||
{
|
||||
calledMethod = method;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
calledMethod = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661e1af528e3441f79e1552fb5ec4e0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,56 @@
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||
static class MonoBehaviourProcessor
|
||||
{
|
||||
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
ProcessSyncVars(Log, td, ref WeavingFailed);
|
||||
ProcessMethods(Log, td, ref WeavingFailed);
|
||||
}
|
||||
|
||||
static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
// find command and RPC functions
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
if (md.HasCustomAttribute<CommandAttribute>())
|
||||
{
|
||||
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||
{
|
||||
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||
{
|
||||
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1055
Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
Normal file
1055
Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8118d606be3214e5d99943ec39530dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
216
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
216
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
@ -0,0 +1,216 @@
|
||||
// finds all readers and writers and register them
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ReaderWriterProcessor
|
||||
{
|
||||
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
// find NetworkReader/Writer extensions from Mirror.dll first.
|
||||
// and NetworkMessage custom writer/reader extensions.
|
||||
// NOTE: do not include this result in our 'modified' return value,
|
||||
// otherwise Unity crashes when running tests
|
||||
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
||||
|
||||
// find readers/writers in the assembly we are in right now.
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
|
||||
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
// find Mirror.dll in assembly's references.
|
||||
// those are guaranteed to be resolvable and correct.
|
||||
// after all, it references them :)
|
||||
AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
|
||||
if (mirrorAssemblyReference != null)
|
||||
{
|
||||
// resolve the assembly to load the AssemblyDefinition.
|
||||
// we need to search all types in it.
|
||||
// if we only were to resolve one known type like in WeaverTypes,
|
||||
// then we wouldn't need it.
|
||||
AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
|
||||
if (mirrorAssembly != null)
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
|
||||
}
|
||||
else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
|
||||
}
|
||||
|
||||
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// extension methods only live in static classes
|
||||
// static classes are represented as sealed and abstract
|
||||
if (klass.IsAbstract && klass.IsSealed)
|
||||
{
|
||||
// if assembly has any declared writers then it is "modified"
|
||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
|
||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// if assembly has any network message then it is modified
|
||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||
{
|
||||
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (TypeDefinition td in klass.NestedTypes)
|
||||
{
|
||||
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
|
||||
{
|
||||
// register all the writers in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 2)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
||||
continue;
|
||||
|
||||
if (!method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
TypeReference dataType = method.Parameters[1].ParameterType;
|
||||
writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
|
||||
{
|
||||
// register all the reader in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 1)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
||||
continue;
|
||||
|
||||
if (method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
// helper function to add [RuntimeInitializeOnLoad] attribute to method
|
||||
static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||
{
|
||||
// NOTE: previously we used reflection because according paul,
|
||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||
// order, which breaks rewired'
|
||||
// it's not obvious why importing an attribute via reflection instead
|
||||
// of cecil would break anything. let's use cecil.
|
||||
|
||||
// to add a CustomAttribute, we need the attribute's constructor.
|
||||
// in this case, there are two: empty, and RuntimeInitializeOnLoadType.
|
||||
// we want the last one, with the type parameter.
|
||||
MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
|
||||
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
|
||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||
// we need to import it first.
|
||||
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
|
||||
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||
method.CustomAttributes.Add(attribute);
|
||||
}
|
||||
|
||||
// helper function to add [InitializeOnLoad] attribute to method
|
||||
// (only works in Editor assemblies. check IsEditorAssembly first.)
|
||||
static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||
{
|
||||
// NOTE: previously we used reflection because according paul,
|
||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||
// order, which breaks rewired'
|
||||
// it's not obvious why importing an attribute via reflection instead
|
||||
// of cecil would break anything. let's use cecil.
|
||||
|
||||
// to add a CustomAttribute, we need the attribute's constructor.
|
||||
// in this case, there's only one - and it's an empty constructor.
|
||||
MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
|
||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||
// we need to import it first.
|
||||
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||
method.CustomAttributes.Add(attribute);
|
||||
}
|
||||
|
||||
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
|
||||
// registers all generated writers into Mirror.Writer<T> static class.
|
||||
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
|
||||
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
|
||||
// in Editor and in tests too
|
||||
//
|
||||
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
|
||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
|
||||
{
|
||||
MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||
MethodAttributes.Static,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
// add [RuntimeInitializeOnLoad] in any case
|
||||
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||
|
||||
// add [InitializeOnLoad] if UnityEditor is referenced
|
||||
if (Helpers.IsEditorAssembly(currentAssembly))
|
||||
{
|
||||
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||
}
|
||||
|
||||
// fill function body with reader/writer initializers
|
||||
ILProcessor worker = initReadWriters.Body.GetILProcessor();
|
||||
// for debugging: add a log to see if initialized on load
|
||||
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
|
||||
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
|
||||
writers.InitializeWriters(worker);
|
||||
readers.InitializeReaders(worker);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
GeneratedCodeClass.Methods.Add(initReadWriters);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
104
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
104
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [Rpc] methods in NetworkBehaviour
|
||||
public static class RpcProcessor
|
||||
{
|
||||
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
string rpcName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
|
||||
|
||||
MethodDefinition rpc = new MethodDefinition(rpcName, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/*
|
||||
* generates code like:
|
||||
|
||||
public void RpcTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32((uint)param);
|
||||
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
|
||||
}
|
||||
public void CallRpcTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to RpcTest with CallRpcTest
|
||||
|
||||
This method moves all the user's code into the "CallRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
// add a log message if needed for debugging
|
||||
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
|
||||
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
|
||||
|
||||
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the Rpc call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
int channel = clientRpcAttr.GetField("channel", 0);
|
||||
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||
// pass the function hash so we don't have to compute it at runtime
|
||||
// otherwise each GetStableHash call requires O(N) complexity.
|
||||
// noticeable for long function names:
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3375
|
||||
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||
// includeOwner ? 1 : 0
|
||||
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3cb7051ff41947e59bba58bdd2b73fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,163 @@
|
||||
// Injects server/client active checks for [Server/Client] attributes
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class ServerClientAttributeProcessor
|
||||
{
|
||||
public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
|
||||
{
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix))
|
||||
return false;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
if (HasServerClientAttribute(md))
|
||||
{
|
||||
Log.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
return ProcessMethodAttributes(weaverTypes, md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasServerClientAttribute(MethodDefinition md)
|
||||
{
|
||||
foreach (CustomAttribute attr in md.CustomAttributes)
|
||||
{
|
||||
switch (attr.Constructor.DeclaringType.ToString())
|
||||
{
|
||||
case "Mirror.ServerAttribute":
|
||||
case "Mirror.ServerCallbackAttribute":
|
||||
case "Mirror.ClientAttribute":
|
||||
case "Mirror.ClientCallbackAttribute":
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md)
|
||||
{
|
||||
if (md.HasCustomAttribute<ServerAttribute>())
|
||||
InjectServerGuard(weaverTypes, md, true);
|
||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||
InjectServerGuard(weaverTypes, md, false);
|
||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||
InjectClientGuard(weaverTypes, md, true);
|
||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||
InjectClientGuard(weaverTypes, md, false);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||
}
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||
}
|
||||
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with "ref" or "out" parameters
|
||||
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
int offset = md.Resolve().IsStatic ? 0 : 1;
|
||||
for (int index = 0; index < md.Parameters.Count; index++)
|
||||
{
|
||||
ParameterDefinition param = md.Parameters[index];
|
||||
if (param.IsOut)
|
||||
{
|
||||
// this causes IL2CPP build issues with generic out parameters:
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3482
|
||||
// TypeReference elementType = param.ParameterType.GetElementType();
|
||||
//
|
||||
// instead we need to use ElementType not GetElementType()
|
||||
// GetElementType() will get the element type of the inner elementType
|
||||
// which will return wrong type for arrays and generic
|
||||
// credit: JamesFrowen
|
||||
ByReferenceType byRefType = (ByReferenceType)param.ParameterType;
|
||||
TypeReference elementType = byRefType.ElementType;
|
||||
|
||||
md.Body.Variables.Add(new VariableDefinition(elementType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with a return value.
|
||||
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
if (!md.ReturnType.Is(typeof(void)))
|
||||
{
|
||||
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 024f251bf693bb345b90b9177892d534
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,39 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectInitializer
|
||||
{
|
||||
// generates code like:
|
||||
// this.InitSyncObject(m_sizes);
|
||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
|
||||
{
|
||||
// register syncobject in network behaviour
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fd);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
|
||||
}
|
||||
|
||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
// value types cant inherit from SyncObject
|
||||
if (typeRef.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeRef.Resolve().IsDerivedFrom<SyncObject>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02219b00b3674e59a2151f41e791688
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncObjectsLimit = 64;
|
||||
|
||||
// Finds SyncObjects fields in a type
|
||||
// Type should be a NetworkBehaviour
|
||||
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.FieldType.IsGenericParameter || fd.ContainsGenericParameter)
|
||||
{
|
||||
// can't call .Resolve on generic ones
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
|
||||
{
|
||||
if (fd.IsStatic)
|
||||
{
|
||||
Log.Error($"{fd.Name} cannot be static", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// SyncObjects always needs to be readonly to guarantee.
|
||||
// Weaver calls InitSyncObject on them for dirty bits etc.
|
||||
// Reassigning at runtime would cause undefined behaviour.
|
||||
// (C# 'readonly' is called 'initonly' in IL code.)
|
||||
//
|
||||
// NOTE: instead of forcing readonly, we could also scan all
|
||||
// instructions for SyncObject assignments. this would
|
||||
// make unit tests very difficult though.
|
||||
if (!fd.IsInitOnly)
|
||||
{
|
||||
// just a warning for now.
|
||||
// many people might still use non-readonly SyncObjects.
|
||||
Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd);
|
||||
|
||||
// only log, but keep weaving. no need to break projects.
|
||||
//WeavingFailed = true;
|
||||
}
|
||||
|
||||
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
|
||||
|
||||
syncObjects.Add(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// SyncObjects dirty mask is 64 bit. can't sync more than 64.
|
||||
if (syncObjects.Count > 64)
|
||||
{
|
||||
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
|
||||
return syncObjects;
|
||||
}
|
||||
|
||||
// Generates serialization methods for synclists
|
||||
static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
|
||||
{
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||
{
|
||||
if (!argument.IsGenericParameter)
|
||||
{
|
||||
readers.GetReadFunc(argument, ref WeavingFailed);
|
||||
writers.GetWriteFunc(argument, ref WeavingFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr != null)
|
||||
{
|
||||
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,206 @@
|
||||
// [SyncVar] int health;
|
||||
// is replaced with:
|
||||
// public int Networkhealth { get; set; } properties.
|
||||
// this class processes all access to 'health' and replaces it with 'Networkhealth'
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncVarAttributeAccessReplacer
|
||||
{
|
||||
// process the module
|
||||
public static void Process(Logger Log, ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
// process all classes in this module
|
||||
foreach (TypeDefinition td in moduleDef.Types)
|
||||
{
|
||||
if (td.IsClass)
|
||||
{
|
||||
ProcessClass(Log, syncVarAccessLists, td);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
|
||||
}
|
||||
|
||||
static void ProcessClass(Logger Log, SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
|
||||
{
|
||||
//Console.WriteLine($" ProcessClass {td}");
|
||||
|
||||
// process all methods in this class
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
ProcessMethod(Log, syncVarAccessLists, md);
|
||||
}
|
||||
|
||||
// processes all nested classes in this class recursively
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
ProcessClass(Log, syncVarAccessLists, nested);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethod(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
|
||||
{
|
||||
// process all references to replaced members with properties
|
||||
//Log.Warning($" ProcessSiteMethod {md}");
|
||||
|
||||
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix))
|
||||
return;
|
||||
|
||||
// skip abstract
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// go through all instructions of this method
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
for (int i = 0; i < md.Body.Instructions.Count;)
|
||||
{
|
||||
Instruction instr = md.Body.Instructions[i];
|
||||
i += ProcessInstruction(Log, syncVarAccessLists, md, instr, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessInstruction(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
|
||||
{
|
||||
// stfld (sets value of a field)?
|
||||
if (instr.OpCode == OpCodes.Stfld)
|
||||
{
|
||||
// operand is a FieldDefinition in the same assembly?
|
||||
if (instr.Operand is FieldDefinition opFieldst)
|
||||
{
|
||||
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
|
||||
}
|
||||
// operand is a FieldReference in another assembly?
|
||||
// this is not supported just yet.
|
||||
// compilation error is better than silently failing SyncVar serialization at runtime.
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3525
|
||||
else if (instr.Operand is FieldReference opFieldstRef)
|
||||
{
|
||||
// resolve it from the other assembly
|
||||
FieldDefinition field = opFieldstRef.Resolve();
|
||||
|
||||
// [SyncVar]?
|
||||
if (field.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
// ILPostProcessor would need to Process() the assembly's
|
||||
// references before processing this one.
|
||||
// we can not control the order.
|
||||
// instead, Log an error to suggest adding a SetSyncVar(value) function.
|
||||
// this is a very easy solution for a very rare edge case.
|
||||
Log.Error($"'[SyncVar] {opFieldstRef.DeclaringType.Name}.{opFieldstRef.Name}' in '{field.Module.Name}' is modified by '{md.FullName}' in '{md.Module.Name}'. Modifying a [SyncVar] from another assembly is not supported. Please add a: 'public void Set{opFieldstRef.Name}(value) {{ this.{opFieldstRef.Name} = value; }}' method in '{opFieldstRef.DeclaringType.Name}' and call this function from '{md.FullName}' instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ldfld (load value of a field)?
|
||||
if (instr.OpCode == OpCodes.Ldfld)
|
||||
{
|
||||
// operand is a FieldDefinition in the same assembly?
|
||||
if (instr.Operand is FieldDefinition opFieldld)
|
||||
{
|
||||
// this instruction gets the value of a field. cache the field reference.
|
||||
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
|
||||
}
|
||||
}
|
||||
|
||||
// ldflda (load field address aka reference)
|
||||
if (instr.OpCode == OpCodes.Ldflda)
|
||||
{
|
||||
// operand is a FieldDefinition in the same assembly?
|
||||
if (instr.Operand is FieldDefinition opFieldlda)
|
||||
{
|
||||
// watch out for initobj instruction
|
||||
// see https://github.com/vis2k/Mirror/issues/696
|
||||
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
|
||||
}
|
||||
}
|
||||
|
||||
// we processed one instruction (instr)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// replaces syncvar write access with the NetworkXYZ.set property calls
|
||||
static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//Log.Warning($" replacing {md.Name}:{i}", opField);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//Log.Warning($" replaced {md.Name}:{i}", opField);
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||
static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//Log.Warning($" replacing {md.Name}:{i}");
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//Log.Warning($" replaced {md.Name}:{i}");
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||
{
|
||||
// don't replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return 1;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
// we have a replacement for this property
|
||||
// is the next instruction a initobj?
|
||||
Instruction nextInstr = md.Body.Instructions[iCount + 1];
|
||||
|
||||
if (nextInstr.OpCode == OpCodes.Initobj)
|
||||
{
|
||||
// we need to replace this code with:
|
||||
// var tmp = new MyStruct();
|
||||
// this.set_Networkxxxx(tmp);
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
|
||||
md.Body.Variables.Add(tmpVariable);
|
||||
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
||||
|
||||
worker.Remove(instr);
|
||||
worker.Remove(nextInstr);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d48f1ab125e9940a995603796bccc59e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,515 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [SyncVar] attribute fields in NetworkBehaviour
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public class SyncVarAttributeProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncVarLimit = 64;
|
||||
|
||||
AssemblyDefinition assembly;
|
||||
WeaverTypes weaverTypes;
|
||||
SyncVarAccessLists syncVarAccessLists;
|
||||
Logger Log;
|
||||
|
||||
string HookParameterMessage(string hookName, TypeReference ValueType) =>
|
||||
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
|
||||
|
||||
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.weaverTypes = weaverTypes;
|
||||
this.syncVarAccessLists = syncVarAccessLists;
|
||||
this.Log = Log;
|
||||
}
|
||||
|
||||
// Get hook method if any
|
||||
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
|
||||
{
|
||||
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
|
||||
|
||||
if (syncVarAttr == null)
|
||||
return null;
|
||||
|
||||
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
|
||||
|
||||
if (hookFunctionName == null)
|
||||
return null;
|
||||
|
||||
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
|
||||
}
|
||||
|
||||
// Create a field definition for a field that will store the Action<T, T> delegate instance for the syncvar hook method (only instantiate delegate once)
|
||||
public FieldDefinition CreateNewActionFieldDefinitionFromHookMethod(FieldDefinition syncVarField)
|
||||
{
|
||||
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
|
||||
GenericInstanceType syncVarHookActionDelegateType = actionRef.MakeGenericInstanceType(syncVarField.FieldType, syncVarField.FieldType);
|
||||
string syncVarHookDelegateFieldName = $"_Mirror_SyncVarHookDelegate_{syncVarField.Name}";
|
||||
return new FieldDefinition(syncVarHookDelegateFieldName, FieldAttributes.Public, syncVarHookActionDelegateType);
|
||||
}
|
||||
|
||||
// push hook from GetHookMethod() onto the stack as a new Action<T,T>.
|
||||
// allows for reuse without handling static/virtual cases every time.
|
||||
// perf warning: it is recommended to use this method only when generating IL to create a new Action<T, T>() in order to store it into a field
|
||||
// avoid using this to emit IL to instantiate a new action instance every single time one is needed for the same method
|
||||
public void GenerateNewActionFromHookMethod(FieldDefinition syncVar, ILProcessor worker, MethodDefinition hookMethod)
|
||||
{
|
||||
// IL_000a: ldarg.0
|
||||
// IL_000b: ldftn instance void Mirror.Examples.Tanks.Tank::ExampleHook(int32, int32)
|
||||
// IL_0011: newobj instance void class [netstandard]System.Action`2<int32, int32>::.ctor(object, native int)
|
||||
|
||||
// we support static hooks and instance hooks.
|
||||
if (hookMethod.IsStatic)
|
||||
{
|
||||
// for static hooks, we need to push 'null' first.
|
||||
// we can't just push nothing.
|
||||
// stack would get out of balance because we already pushed
|
||||
// other stuff above.
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for instance hooks, we need to push 'this.' first.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
}
|
||||
|
||||
MethodReference hookMethodReference;
|
||||
// if the network behaviour class is generic, we need to make the method reference generic for correct IL
|
||||
if (hookMethod.DeclaringType.HasGenericParameters)
|
||||
{
|
||||
hookMethodReference = hookMethod.MakeHostInstanceGeneric(hookMethod.Module, hookMethod.DeclaringType.MakeGenericInstanceType(hookMethod.DeclaringType.GenericParameters.ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
hookMethodReference = hookMethod;
|
||||
}
|
||||
|
||||
// we support regular and virtual hook functions.
|
||||
if (hookMethod.IsVirtual)
|
||||
{
|
||||
// for virtual / overwritten hooks, we need different IL.
|
||||
// this is from simply testing Action = VirtualHook; in C#.
|
||||
worker.Emit(OpCodes.Dup);
|
||||
worker.Emit(OpCodes.Ldvirtftn, hookMethodReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldftn, hookMethodReference);
|
||||
}
|
||||
|
||||
// call 'new Action<T,T>()' constructor to convert the function to an action
|
||||
// we need to make an instance of the generic Action<T,T>.
|
||||
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
|
||||
GenericInstanceType genericInstance = actionRef.MakeGenericInstanceType(syncVar.FieldType, syncVar.FieldType);
|
||||
worker.Emit(OpCodes.Newobj, weaverTypes.ActionT_T.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
|
||||
}
|
||||
|
||||
// generates CIL to set an Action<T,T> instance field to a new Action<T,T>(hookMethod)
|
||||
// this.hookDelegate = new Action<T, T>(HookMethod);
|
||||
public void GenerateSyncVarHookDelegateInitializer(ILProcessor worker, FieldDefinition syncVar, FieldDefinition hookDelegate, MethodDefinition hookMethod)
|
||||
{
|
||||
// push this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
// push new Action<T, T>(hookMethod)
|
||||
GenerateNewActionFromHookMethod(syncVar, worker, hookMethod);
|
||||
// set field
|
||||
worker.Emit(OpCodes.Stfld, hookDelegate);
|
||||
}
|
||||
|
||||
MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
|
||||
{
|
||||
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
|
||||
|
||||
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
||||
|
||||
if (methodsWith2Param.Count == 0)
|
||||
{
|
||||
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
WeavingFailed = true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (MethodDefinition method in methodsWith2Param)
|
||||
{
|
||||
if (MatchesParameters(syncVar, method))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
WeavingFailed = true;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
|
||||
{
|
||||
// matches void onValueChange(T oldValue, T newValue)
|
||||
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
|
||||
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
|
||||
}
|
||||
|
||||
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
|
||||
{
|
||||
//Create the get method
|
||||
MethodDefinition get = new MethodDefinition(
|
||||
$"get_Network{originalName}", MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
fd.FieldType);
|
||||
|
||||
ILProcessor worker = get.Body.GetILProcessor();
|
||||
|
||||
FieldReference fr;
|
||||
if (fd.DeclaringType.HasGenericParameters)
|
||||
{
|
||||
fr = fd.MakeHostInstanceGeneric();
|
||||
}
|
||||
else
|
||||
{
|
||||
fr = fd;
|
||||
}
|
||||
|
||||
FieldReference netIdFieldReference = null;
|
||||
if (netFieldId != null)
|
||||
{
|
||||
if (netFieldId.DeclaringType.HasGenericParameters)
|
||||
{
|
||||
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
|
||||
}
|
||||
else
|
||||
{
|
||||
netIdFieldReference = netFieldId;
|
||||
}
|
||||
}
|
||||
|
||||
// [SyncVar] GameObject?
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// return this.GetSyncVarGameObject(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fr);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] NetworkIdentity?
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fr);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
||||
// this.
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fr);
|
||||
MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, fr);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
||||
get.Body.InitLocals = true;
|
||||
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
||||
|
||||
return get;
|
||||
}
|
||||
|
||||
// for [SyncVar] health, weaver generates
|
||||
//
|
||||
// NetworkHealth
|
||||
// {
|
||||
// get => health;
|
||||
// set => GeneratedSyncVarSetter(...)
|
||||
// }
|
||||
//
|
||||
// the setter used to be manually IL generated, but we moved it to C# :)
|
||||
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, ref bool WeavingFailed)
|
||||
{
|
||||
//Create the set method
|
||||
MethodDefinition set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = set.Body.GetILProcessor();
|
||||
FieldReference fr;
|
||||
if (fd.DeclaringType.HasGenericParameters)
|
||||
{
|
||||
fr = fd.MakeHostInstanceGeneric();
|
||||
}
|
||||
else
|
||||
{
|
||||
fr = fd;
|
||||
}
|
||||
|
||||
FieldReference netIdFieldReference = null;
|
||||
if (netFieldId != null)
|
||||
{
|
||||
if (netFieldId.DeclaringType.HasGenericParameters)
|
||||
{
|
||||
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
|
||||
}
|
||||
else
|
||||
{
|
||||
netIdFieldReference = netFieldId;
|
||||
}
|
||||
}
|
||||
|
||||
// if (!SyncVarEqual(value, ref playerData))
|
||||
Instruction endOfMethod = worker.Create(OpCodes.Nop);
|
||||
|
||||
// NOTE: SyncVar...Equal functions are static.
|
||||
// don't Emit Ldarg_0 aka 'this'.
|
||||
|
||||
// call WeaverSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged = null)
|
||||
// IL_0000: ldarg.0
|
||||
// IL_0001: ldarg.1
|
||||
// IL_0002: ldarg.0
|
||||
// IL_0003: ldflda int32 Mirror.Examples.Tanks.Tank::health
|
||||
// IL_0008: ldc.i4.1
|
||||
// IL_0009: conv.i8
|
||||
// IL_000a: ldnull
|
||||
// IL_000b: call instance void [Mirror]Mirror.NetworkBehaviour::GeneratedSyncVarSetter<int32>(!!0, !!0&, uint64, class [netstandard]System.Action`2<!!0, !!0>)
|
||||
// IL_0010: ret
|
||||
|
||||
// 'this.' for the call
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
// first push 'value'
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
// push 'ref T this.field'
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, fr);
|
||||
|
||||
// push the dirty bit for this SyncVar
|
||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||
|
||||
// hook? then push 'this.HookDelegate' onto stack
|
||||
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
|
||||
if (hookMethod != null)
|
||||
{
|
||||
// Create the field that will store a single instance of the hook as a delegate (field will be set in constructor)
|
||||
FieldDefinition hookActionDelegateField = CreateNewActionFieldDefinitionFromHookMethod(fd);
|
||||
syncVarHookDelegates[fd] = (hookActionDelegateField, hookMethod);
|
||||
|
||||
// push this.hookActionDelegateField
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldfld, hookActionDelegateField);
|
||||
}
|
||||
// otherwise push 'null' as hook
|
||||
else
|
||||
{
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
// call GeneratedSyncVarSetter<T>.
|
||||
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
|
||||
// passing netId too for persistence.
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// GameObject setter needs one more parameter: netId field ref
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject);
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// NetworkIdentity setter needs one more parameter: netId field ref
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
|
||||
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
|
||||
}
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
|
||||
{
|
||||
// NetworkIdentity setter needs one more parameter: netId field ref
|
||||
// (actually its a NetworkBehaviourSyncVar type)
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
|
||||
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
|
||||
MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, getFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of GeneratedSyncVarSetter<T>
|
||||
MethodReference generic = weaverTypes.generatedSyncVarSetter.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||
worker.Emit(OpCodes.Call, generic);
|
||||
}
|
||||
|
||||
worker.Append(endOfMethod);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
||||
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, long dirtyBit, ref bool WeavingFailed)
|
||||
{
|
||||
string originalName = fd.Name;
|
||||
|
||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||
FieldDefinition netIdField = null;
|
||||
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
|
||||
{
|
||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
|
||||
weaverTypes.Import<NetworkBehaviourSyncVar>());
|
||||
netIdField.DeclaringType = td;
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
else if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
|
||||
weaverTypes.Import<uint>());
|
||||
netIdField.DeclaringType = td;
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
|
||||
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
||||
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, syncVarHookDelegates, ref WeavingFailed);
|
||||
|
||||
//NOTE: is property even needed? Could just use a setter function?
|
||||
//create the property
|
||||
PropertyDefinition propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
|
||||
{
|
||||
GetMethod = get,
|
||||
SetMethod = set
|
||||
};
|
||||
|
||||
//add the methods and property to the type.
|
||||
td.Methods.Add(get);
|
||||
td.Methods.Add(set);
|
||||
td.Properties.Add(propertyDefinition);
|
||||
syncVarAccessLists.replacementSetterProperties[fd] = set;
|
||||
|
||||
// replace getter field if GameObject/NetworkIdentity so it uses
|
||||
// netId instead
|
||||
// -> only for GameObjects, otherwise an int syncvar's getter would
|
||||
// end up in recursion.
|
||||
if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
syncVarAccessLists.replacementGetterProperties[fd] = get;
|
||||
}
|
||||
}
|
||||
|
||||
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
List<FieldDefinition> syncVars = new List<FieldDefinition>();
|
||||
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
||||
Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates = new Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)>();
|
||||
|
||||
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
|
||||
// start assigning syncvars at the place the base class stopped, if any
|
||||
int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
|
||||
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
||||
{
|
||||
Log.Error($"{fd.Name} cannot be static", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd.FieldType.IsGenericParameter)
|
||||
{
|
||||
Log.Error($"{fd.Name} has generic type. Generic SyncVars are not supported", fd);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncVars.Add(fd);
|
||||
|
||||
ProcessSyncVar(td, fd, syncVarNetIds, syncVarHookDelegates, 1L << dirtyBitCounter, ref WeavingFailed);
|
||||
dirtyBitCounter += 1;
|
||||
|
||||
if (dirtyBitCounter > SyncVarLimit)
|
||||
{
|
||||
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
|
||||
WeavingFailed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all the new SyncVar __netId fields
|
||||
foreach (FieldDefinition fd in syncVarNetIds.Values)
|
||||
{
|
||||
td.Fields.Add(fd);
|
||||
}
|
||||
|
||||
// add all of the new SyncVar Action<T,T> fields
|
||||
foreach((FieldDefinition hookDelegateInstanceField, MethodDefinition) entry in syncVarHookDelegates.Values)
|
||||
{
|
||||
td.Fields.Add(entry.hookDelegateInstanceField);
|
||||
}
|
||||
|
||||
// include parent class syncvars
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3457
|
||||
int parentSyncVarCount = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
|
||||
syncVarAccessLists.SetNumSyncVars(td.FullName, parentSyncVarCount + syncVars.Count);
|
||||
|
||||
return (syncVars, syncVarNetIds, syncVarHookDelegates);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f52c39bddd95d42b88f9cd554dfd9198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
159
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
159
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
@ -0,0 +1,159 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Processes [TargetRpc] methods in NetworkBehaviour
|
||||
public static class TargetRpcProcessor
|
||||
{
|
||||
// helper functions to check if the method has a NetworkConnection parameter
|
||||
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||
{
|
||||
if (md.Parameters.Count > 0)
|
||||
{
|
||||
// we need to allow both NetworkConnection, and inheriting types.
|
||||
// NetworkBehaviour.SendTargetRpc takes a NetworkConnection parameter.
|
||||
// fixes https://github.com/vis2k/Mirror/issues/3290
|
||||
TypeReference type = md.Parameters[0].ParameterType;
|
||||
return type.Is<NetworkConnection>() ||
|
||||
type.IsDerivedFrom<NetworkConnection>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||
{
|
||||
string trgName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
|
||||
|
||||
MethodDefinition rpc = new MethodDefinition(trgName, MethodAttributes.Family |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Castclass, td);
|
||||
|
||||
// NetworkConnection parameter is optional
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// TargetRpcs are sent from server to client.
|
||||
// on server, we currently support two types:
|
||||
// TargetRpc(NetworkConnection)
|
||||
// TargetRpc(NetworkConnectionToClient)
|
||||
// however, it's always a connection to client.
|
||||
// in the future, only NetworkConnectionToClient will be supported.
|
||||
// explicit typing helps catch issues at compile time.
|
||||
//
|
||||
// on client, InvokeTargetRpc calls the original code.
|
||||
// we need to fill in the NetworkConnection parameter.
|
||||
// NetworkClient.connection is always a connection to server.
|
||||
//
|
||||
// we used to pass NetworkClient.connection as the TargetRpc parameter.
|
||||
// which caused: https://github.com/MirrorNetworking/Mirror/issues/3455
|
||||
// when the parameter is defined as a NetworkConnectionToClient.
|
||||
//
|
||||
// a client's connection never fits into a NetworkConnectionToClient.
|
||||
// we need to always pass null here.
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
// process reader parameters and skip first one if first one is NetworkConnection
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/* generates code like:
|
||||
public void TargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
or if optional:
|
||||
public void TargetTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to TargetTest with CallTargetTest
|
||||
|
||||
This method moves all the user's code into the "CallTargetRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
|
||||
*/
|
||||
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||
|
||||
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
|
||||
|
||||
// write all the arguments that the user passed to the TargetRpc call
|
||||
// (skip first one if first one is NetworkConnection)
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// connection
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// null
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||
// pass the function hash so we don't have to compute it at runtime
|
||||
// otherwise each GetStableHash call requires O(N) complexity.
|
||||
// noticeable for long function names:
|
||||
// https://github.com/MirrorNetworking/Mirror/issues/3375
|
||||
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
|
||||
// writer
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
|
||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
|
||||
|
||||
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb3ce6c6f3f2942ae88178b86f5a8282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
385
Assets/Mirror/Editor/Weaver/Readers.cs
Normal file
385
Assets/Mirror/Editor/Weaver/Readers.cs
Normal file
@ -0,0 +1,385 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
// to use Mono.CecilX.Rocks here, we need to 'override references' in the
|
||||
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
||||
// otherwise we get an unknown import exception.
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public class Readers
|
||||
{
|
||||
// Readers are only for this assembly.
|
||||
// can't be used from another assembly, otherwise we will get:
|
||||
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
||||
AssemblyDefinition assembly;
|
||||
WeaverTypes weaverTypes;
|
||||
TypeDefinition GeneratedCodeClass;
|
||||
Logger Log;
|
||||
|
||||
Dictionary<TypeReference, MethodReference> readFuncs =
|
||||
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
||||
|
||||
public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.weaverTypes = weaverTypes;
|
||||
this.GeneratedCodeClass = GeneratedCodeClass;
|
||||
this.Log = Log;
|
||||
}
|
||||
|
||||
internal void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
if (readFuncs.ContainsKey(dataType))
|
||||
{
|
||||
// TODO enable this again later.
|
||||
// Reader has some obsolete functions that were renamed.
|
||||
// Don't want weaver warnings for all of them.
|
||||
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
|
||||
}
|
||||
|
||||
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
|
||||
TypeReference imported = assembly.MainModule.ImportReference(dataType);
|
||||
readFuncs[imported] = methodReference;
|
||||
}
|
||||
|
||||
void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
|
||||
{
|
||||
Register(typeReference, newReaderFunc);
|
||||
GeneratedCodeClass.Methods.Add(newReaderFunc);
|
||||
}
|
||||
|
||||
// Finds existing reader for type, if non exists trys to create one
|
||||
public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
|
||||
return foundFunc;
|
||||
|
||||
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
|
||||
return GenerateReader(importedVariable, ref WeavingFailed);
|
||||
}
|
||||
|
||||
MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed)
|
||||
{
|
||||
// Arrays are special, if we resolve them, we get the element type,
|
||||
// so the following ifs might choke on it for scriptable objects
|
||||
// or other objects that require a custom serializer
|
||||
// thus check if it is an array and skip all the checks.
|
||||
if (variableReference.IsArray)
|
||||
{
|
||||
if (variableReference.IsMultidimensionalArray())
|
||||
{
|
||||
Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
|
||||
}
|
||||
|
||||
TypeDefinition variableDefinition = variableReference.Resolve();
|
||||
|
||||
// check if the type is completely invalid
|
||||
if (variableDefinition == null)
|
||||
{
|
||||
Log.Error($"{variableReference.Name} is not a supported type", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
else if (variableReference.IsByReference)
|
||||
{
|
||||
// error??
|
||||
Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
// use existing func for known types
|
||||
if (variableDefinition.IsEnum)
|
||||
{
|
||||
return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
|
||||
}
|
||||
else if (variableDefinition.Is(typeof(ArraySegment<>)))
|
||||
{
|
||||
return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed);
|
||||
}
|
||||
else if (variableDefinition.Is(typeof(List<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
|
||||
}
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
else if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
|
||||
{
|
||||
return GetNetworkBehaviourReader(variableReference);
|
||||
}
|
||||
|
||||
// check if reader generation is applicable on this type
|
||||
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
|
||||
{
|
||||
Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.Object>())
|
||||
{
|
||||
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.ScriptableObject>())
|
||||
{
|
||||
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.HasGenericParameters)
|
||||
{
|
||||
Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.IsInterface)
|
||||
{
|
||||
Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.IsAbstract)
|
||||
{
|
||||
Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
|
||||
}
|
||||
|
||||
MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
|
||||
{
|
||||
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
|
||||
MethodReference generic = weaverTypes.readNetworkBehaviourGeneric;
|
||||
|
||||
MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
|
||||
|
||||
// register function so it is added to Reader<T>
|
||||
// use Register instead of RegisterWriteFunc because this is not a generated function
|
||||
Register(variableReference, readFunc);
|
||||
|
||||
return readFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
|
||||
MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
|
||||
|
||||
worker.Emit(OpCodes.Call, underlyingFunc);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variable;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
|
||||
// $array = reader.Read<[T]>()
|
||||
ArrayType arrayType = elementType.MakeArrayType();
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
|
||||
|
||||
// return new ArraySegment<T>($array);
|
||||
worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateReaderFunction(TypeReference variable)
|
||||
{
|
||||
string functionName = $"_Read_{variable.FullName}";
|
||||
|
||||
// create new reader for this type
|
||||
MethodDefinition readerFunc = new MethodDefinition(functionName,
|
||||
MethodAttributes.Public |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
variable);
|
||||
|
||||
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
|
||||
readerFunc.Body.InitLocals = true;
|
||||
RegisterReadFunc(variable, readerFunc);
|
||||
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
// generate readers for the element
|
||||
GetReadFunc(elementType, ref WeavingFailed);
|
||||
|
||||
ModuleDefinition module = assembly.MainModule;
|
||||
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
|
||||
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
|
||||
|
||||
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
|
||||
methodRef.GenericArguments.Add(elementType);
|
||||
|
||||
// generates
|
||||
// return reader.ReadList<T>();
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
worker.Emit(OpCodes.Ldarg_0); // reader
|
||||
worker.Emit(OpCodes.Call, methodRef); // Read
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
|
||||
// create local for return value
|
||||
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
|
||||
TypeDefinition td = variable.Resolve();
|
||||
|
||||
if (!td.IsValueType)
|
||||
GenerateNullCheck(worker, ref WeavingFailed);
|
||||
|
||||
CreateNew(variable, worker, td, ref WeavingFailed);
|
||||
ReadAllFields(variable, worker, ref WeavingFailed);
|
||||
|
||||
worker.Emit(OpCodes.Ldloc_0);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
||||
{
|
||||
// if (!reader.ReadBoolean()) {
|
||||
// return null;
|
||||
// }
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
||||
|
||||
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
|
||||
worker.Emit(OpCodes.Brtrue, labelEmptyArray);
|
||||
// return null
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
worker.Emit(OpCodes.Ret);
|
||||
worker.Append(labelEmptyArray);
|
||||
}
|
||||
|
||||
// Initialize the local variable with a new instance
|
||||
void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed)
|
||||
{
|
||||
if (variable.IsValueType)
|
||||
{
|
||||
// structs are created with Initobj
|
||||
worker.Emit(OpCodes.Ldloca, 0);
|
||||
worker.Emit(OpCodes.Initobj, variable);
|
||||
}
|
||||
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
|
||||
{
|
||||
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod);
|
||||
genericInstanceMethod.GenericArguments.Add(variable);
|
||||
worker.Emit(OpCodes.Call, genericInstanceMethod);
|
||||
worker.Emit(OpCodes.Stloc_0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// classes are created with their constructor
|
||||
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
|
||||
if (ctor == null)
|
||||
{
|
||||
Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable);
|
||||
WeavingFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
MethodReference ctorRef = assembly.MainModule.ImportReference(ctor);
|
||||
|
||||
worker.Emit(OpCodes.Newobj, ctorRef);
|
||||
worker.Emit(OpCodes.Stloc_0);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
||||
{
|
||||
foreach (FieldDefinition field in variable.FindAllPublicFields())
|
||||
{
|
||||
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
|
||||
OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
|
||||
worker.Emit(opcode, 0);
|
||||
MethodReference readFunc = GetReadFunc(field.FieldType, ref WeavingFailed);
|
||||
if (readFunc != null)
|
||||
{
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Call, readFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"{field.Name} has an unsupported type", field);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
|
||||
|
||||
worker.Emit(OpCodes.Stfld, fieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Save a delegate for each one of the readers into Reader<T>.read
|
||||
internal void InitializeReaders(ILProcessor worker)
|
||||
{
|
||||
ModuleDefinition module = assembly.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 (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
|
||||
{
|
||||
TypeReference targetType = kvp.Key;
|
||||
MethodReference readFunc = kvp.Value;
|
||||
|
||||
// create a Func<NetworkReader, T> delegate
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
worker.Emit(OpCodes.Ldftn, readFunc);
|
||||
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
|
||||
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
|
||||
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
|
||||
|
||||
// save it in Reader<T>.read
|
||||
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
|
||||
worker.Emit(OpCodes.Stsfld, specializedField);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Readers.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Readers.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be40277098a024539bf63d0205cae824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
126
Assets/Mirror/Editor/Weaver/Resolvers.cs
Normal file
126
Assets/Mirror/Editor/Weaver/Resolvers.cs
Normal file
@ -0,0 +1,126 @@
|
||||
// all the resolve functions for the weaver
|
||||
// NOTE: these functions should be made extensions, but right now they still
|
||||
// make heavy use of Weaver.fail and we'd have to check each one's return
|
||||
// value for null otherwise.
|
||||
// (original FieldType.Resolve returns null if not found too, so
|
||||
// exceptions would be a bit inconsistent here)
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class Resolvers
|
||||
{
|
||||
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
|
||||
{
|
||||
if (tr == null)
|
||||
{
|
||||
Log.Error($"Cannot resolve method {name} without a class");
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
MethodReference method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
|
||||
if (method == null)
|
||||
{
|
||||
Log.Error($"Method not found with name {name} in type {tr.Name}", tr);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed)
|
||||
{
|
||||
foreach (MethodDefinition methodRef in t.Resolve().Methods)
|
||||
{
|
||||
if (predicate(methodRef))
|
||||
{
|
||||
return assembly.MainModule.ImportReference(methodRef);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error($"Method not found in type {t.Name}", t);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FieldReference ResolveField(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
|
||||
{
|
||||
if (tr == null)
|
||||
{
|
||||
Log.Error($"Cannot resolve Field {name} without a class");
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
FieldReference field = ResolveField(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
|
||||
if (field == null)
|
||||
{
|
||||
Log.Error($"Field not found with name {name} in type {tr.Name}", tr);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
public static FieldReference ResolveField(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<FieldDefinition, bool> predicate, ref bool WeavingFailed)
|
||||
{
|
||||
foreach (FieldDefinition fieldRef in t.Resolve().Fields)
|
||||
{
|
||||
if (predicate(fieldRef))
|
||||
{
|
||||
return assembly.MainModule.ImportReference(fieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error($"Field not found in type {t.Name}", t);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name)
|
||||
{
|
||||
if (tr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
foreach (MethodDefinition methodDef in tr.Resolve().Methods)
|
||||
{
|
||||
if (methodDef.Name == name)
|
||||
{
|
||||
MethodReference methodRef = methodDef;
|
||||
if (tr.IsGenericInstance)
|
||||
{
|
||||
methodRef = methodRef.MakeHostInstanceGeneric(tr.Module, (GenericInstanceType)tr);
|
||||
}
|
||||
return assembly.MainModule.ImportReference(methodRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find the method in this class, try the parent
|
||||
return TryResolveMethodInParents(tr.Resolve().BaseType.ApplyGenericParameters(tr), assembly, name);
|
||||
}
|
||||
|
||||
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
|
||||
{
|
||||
foreach (MethodDefinition methodRef in variable.Resolve().Methods)
|
||||
{
|
||||
if (methodRef.Name == ".ctor" &&
|
||||
methodRef.Resolve().IsPublic &&
|
||||
methodRef.Parameters.Count == 0)
|
||||
{
|
||||
return methodRef;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name)
|
||||
{
|
||||
foreach (PropertyDefinition pd in tr.Resolve().Properties)
|
||||
{
|
||||
if (pd.Name == name)
|
||||
{
|
||||
return assembly.MainModule.ImportReference(pd.GetMethod);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Resolvers.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Resolvers.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3039a59c76aec43c797ad66930430367
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
32
Assets/Mirror/Editor/Weaver/SyncVarAccessLists.cs
Normal file
32
Assets/Mirror/Editor/Weaver/SyncVarAccessLists.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// tracks SyncVar read/write access when processing NetworkBehaviour,
|
||||
// to later be replaced by SyncVarAccessReplacer.
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// This data is flushed each time - if we are run multiple times in the same process/domain
|
||||
public class SyncVarAccessLists
|
||||
{
|
||||
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
||||
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties =
|
||||
new Dictionary<FieldDefinition, MethodDefinition>();
|
||||
|
||||
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
||||
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties =
|
||||
new Dictionary<FieldDefinition, MethodDefinition>();
|
||||
|
||||
// amount of SyncVars per class. dict<className, amount>
|
||||
// necessary for SyncVar dirty bits, where inheriting classes start
|
||||
// their dirty bits at base class SyncVar amount.
|
||||
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
|
||||
|
||||
public int GetSyncVarStart(string className) =>
|
||||
numSyncVars.TryGetValue(className, out int value) ? value : 0;
|
||||
|
||||
public void SetNumSyncVars(string className, int num)
|
||||
{
|
||||
numSyncVars[className] = num;
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Mirror/Editor/Weaver/SyncVarAccessLists.cs.meta
Normal file
3
Assets/Mirror/Editor/Weaver/SyncVarAccessLists.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6905230c3c4c4e158760065a93380e83
|
||||
timeCreated: 1629348618
|
15
Assets/Mirror/Editor/Weaver/TypeReferenceComparer.cs
Normal file
15
Assets/Mirror/Editor/Weaver/TypeReferenceComparer.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// Compares TypeReference using FullName
|
||||
public class TypeReferenceComparer : IEqualityComparer<TypeReference>
|
||||
{
|
||||
public bool Equals(TypeReference x, TypeReference y) =>
|
||||
x.FullName == y.FullName;
|
||||
|
||||
public int GetHashCode(TypeReference obj) =>
|
||||
obj.FullName.GetHashCode();
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/TypeReferenceComparer.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/TypeReferenceComparer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55eb9eb8794946f4da7ad39788c9920b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
21
Assets/Mirror/Editor/Weaver/Unity.Mirror.CodeGen.asmdef
Normal file
21
Assets/Mirror/Editor/Weaver/Unity.Mirror.CodeGen.asmdef
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Unity.Mirror.CodeGen",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:30817c1a0e6d646d99c048fc403f5979"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Mono.CecilX.dll",
|
||||
"Mono.CecilX.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d0b9d21c3ff546a4aa32399dfd33474
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
267
Assets/Mirror/Editor/Weaver/Weaver.cs
Normal file
267
Assets/Mirror/Editor/Weaver/Weaver.cs
Normal file
@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
internal class Weaver
|
||||
{
|
||||
// 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;
|
||||
AssemblyDefinition CurrentAssembly;
|
||||
Writers writers;
|
||||
Readers readers;
|
||||
|
||||
// in case of weaver errors, we don't stop immediately.
|
||||
// we log all errors and then eventually return false if
|
||||
// weaving has failed.
|
||||
// this way the user can fix multiple errors at once, instead of having
|
||||
// to fix -> recompile -> fix -> recompile for one error at a time.
|
||||
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;
|
||||
|
||||
// remote actions now support overloads,
|
||||
// -> but IL2CPP doesnt like it when two generated methods
|
||||
// -> have the same signature,
|
||||
// -> so, append the signature to the generated method name,
|
||||
// -> to create a unique name
|
||||
// Example:
|
||||
// RpcTeleport(Vector3 position) -> InvokeUserCode_RpcTeleport__Vector3()
|
||||
// RpcTeleport(Vector3 position, Quaternion rotation) -> InvokeUserCode_RpcTeleport__Vector3Quaternion()
|
||||
// fixes https://github.com/vis2k/Mirror/issues/3060
|
||||
public static string GenerateMethodName(string initialPrefix, MethodDefinition md)
|
||||
{
|
||||
initialPrefix += md.Name;
|
||||
|
||||
for (int i = 0; i < md.Parameters.Count; ++i)
|
||||
{
|
||||
// with __ so it's more obvious that this is the parameter suffix.
|
||||
// otherwise RpcTest(int) => RpcTestInt(int) which is not obvious.
|
||||
initialPrefix += $"__{md.Parameters[i].ParameterType.Name}";
|
||||
}
|
||||
|
||||
return initialPrefix;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// ModuleDefinition.Types only finds top level types.
|
||||
// GetAllTypes recursively finds all nested types as well.
|
||||
// fixes nested types not being weaved, for example:
|
||||
// class Parent { // ModuleDefinition.Types finds this
|
||||
// class Child { // .Types.NestedTypes finds this
|
||||
// class GrandChild {} // only GetAllTypes finds this too
|
||||
// }
|
||||
// }
|
||||
// note this is not about inheritance, only about type definitions.
|
||||
// see test: NetworkBehaviourTests.DeeplyNested()
|
||||
foreach (TypeDefinition td in moduleDefinition.GetAllTypes())
|
||||
{
|
||||
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>());
|
||||
}
|
||||
|
||||
void ToggleWeaverFuse()
|
||||
{
|
||||
// // find Weaved() function
|
||||
MethodDefinition func = weaverTypes.weaverFuseMethod.Resolve();
|
||||
// // change return 0 to return 1
|
||||
|
||||
ILProcessor worker = func.Body.GetILProcessor();
|
||||
func.Body.Instructions[0] = worker.Create(OpCodes.Ldc_I4_1);
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
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(Log, 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 });
|
||||
}
|
||||
|
||||
// if weaving succeeded, switch on the Weaver Fuse in Mirror.dll
|
||||
if (CurrentAssembly.Name.Name == MirrorAssemblyName)
|
||||
{
|
||||
ToggleWeaverFuse();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception :{e}");
|
||||
WeavingFailed = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/Weaver.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Weaver.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de160f52931054064852f2afd7e7a86f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
26
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs
Normal file
26
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class WeaverException : Exception
|
||||
{
|
||||
public MemberReference MemberReference { get; }
|
||||
|
||||
protected WeaverException(string message, MemberReference member) : base(message)
|
||||
{
|
||||
MemberReference = member;
|
||||
}
|
||||
|
||||
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GenerateWriterException : WeaverException
|
||||
{
|
||||
public GenerateWriterException(string message, MemberReference member) : base(message, member) {}
|
||||
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8aaaf6193bad7424492677f8e81f1b30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
171
Assets/Mirror/Editor/Weaver/WeaverTypes.cs
Normal file
171
Assets/Mirror/Editor/Weaver/WeaverTypes.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public class WeaverTypes
|
||||
{
|
||||
public MethodReference ScriptableObjectCreateInstanceMethod;
|
||||
|
||||
public FieldReference NetworkBehaviourDirtyBitsReference;
|
||||
public MethodReference GetWriterReference;
|
||||
public MethodReference ReturnWriterReference;
|
||||
|
||||
public MethodReference NetworkClientConnectionReference;
|
||||
|
||||
public MethodReference RemoteCallDelegateConstructor;
|
||||
|
||||
public MethodReference NetworkServerGetActive;
|
||||
public MethodReference NetworkClientGetActive;
|
||||
|
||||
// custom attribute types
|
||||
public MethodReference InitSyncObjectReference;
|
||||
|
||||
// array segment
|
||||
public MethodReference ArraySegmentConstructorReference;
|
||||
|
||||
// Action<T,T> for SyncVar Hooks
|
||||
public MethodReference ActionT_T;
|
||||
|
||||
// syncvar
|
||||
public MethodReference generatedSyncVarSetter;
|
||||
public MethodReference generatedSyncVarSetter_GameObject;
|
||||
public MethodReference generatedSyncVarSetter_NetworkIdentity;
|
||||
public MethodReference generatedSyncVarSetter_NetworkBehaviour_T;
|
||||
public MethodReference generatedSyncVarDeserialize;
|
||||
public MethodReference generatedSyncVarDeserialize_GameObject;
|
||||
public MethodReference generatedSyncVarDeserialize_NetworkIdentity;
|
||||
public MethodReference generatedSyncVarDeserialize_NetworkBehaviour_T;
|
||||
public MethodReference getSyncVarGameObjectReference;
|
||||
public MethodReference getSyncVarNetworkIdentityReference;
|
||||
public MethodReference getSyncVarNetworkBehaviourReference;
|
||||
public MethodReference registerCommandReference;
|
||||
public MethodReference registerRpcReference;
|
||||
public MethodReference getTypeFromHandleReference;
|
||||
public MethodReference logErrorReference;
|
||||
public MethodReference logWarningReference;
|
||||
public MethodReference sendCommandInternal;
|
||||
public MethodReference sendRpcInternal;
|
||||
public MethodReference sendTargetRpcInternal;
|
||||
|
||||
public MethodReference readNetworkBehaviourGeneric;
|
||||
|
||||
public TypeReference weaverFuseType;
|
||||
public MethodReference weaverFuseMethod;
|
||||
|
||||
// attributes
|
||||
public TypeDefinition initializeOnLoadMethodAttribute;
|
||||
public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
|
||||
|
||||
AssemblyDefinition assembly;
|
||||
|
||||
public TypeReference Import<T>() => Import(typeof(T));
|
||||
|
||||
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
|
||||
|
||||
// constructor resolves the types and stores them in fields
|
||||
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
|
||||
{
|
||||
// system types
|
||||
this.assembly = assembly;
|
||||
|
||||
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
|
||||
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
|
||||
|
||||
TypeReference ActionType = Import(typeof(Action<,>));
|
||||
ActionT_T = Resolvers.ResolveMethod(ActionType, assembly, Log, ".ctor", ref WeavingFailed);
|
||||
|
||||
weaverFuseType = Import(typeof(WeaverFuse));
|
||||
weaverFuseMethod = Resolvers.ResolveMethod(weaverFuseType, assembly, Log, "Weaved", ref WeavingFailed);
|
||||
|
||||
TypeReference NetworkServerType = Import(typeof(NetworkServer));
|
||||
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
|
||||
|
||||
TypeReference NetworkClientType = Import(typeof(NetworkClient));
|
||||
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
|
||||
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
|
||||
|
||||
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
|
||||
|
||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveField(NetworkBehaviourType, assembly, Log, "syncVarDirtyBits", ref WeavingFailed);
|
||||
|
||||
generatedSyncVarSetter = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter", ref WeavingFailed);
|
||||
generatedSyncVarSetter_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_GameObject", ref WeavingFailed);
|
||||
generatedSyncVarSetter_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkIdentity", ref WeavingFailed);
|
||||
generatedSyncVarSetter_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkBehaviour", ref WeavingFailed);
|
||||
|
||||
generatedSyncVarDeserialize_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_GameObject", ref WeavingFailed);
|
||||
generatedSyncVarDeserialize = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize", ref WeavingFailed);
|
||||
generatedSyncVarDeserialize_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkIdentity", ref WeavingFailed);
|
||||
generatedSyncVarDeserialize_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkBehaviour", ref WeavingFailed);
|
||||
|
||||
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
|
||||
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
|
||||
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
|
||||
|
||||
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
|
||||
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
|
||||
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
|
||||
|
||||
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
|
||||
|
||||
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
|
||||
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
|
||||
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
|
||||
|
||||
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
|
||||
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
|
||||
|
||||
TypeReference ScriptableObjectType = Import<ScriptableObject>();
|
||||
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
|
||||
ScriptableObjectType, assembly, Log,
|
||||
md => md.Name == "CreateInstance" && md.HasGenericParameters,
|
||||
ref WeavingFailed);
|
||||
|
||||
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
|
||||
// these have multiple methods with same name, so need to check parameters too
|
||||
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
||||
md.Name == "LogError" &&
|
||||
md.Parameters.Count == 1 &&
|
||||
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
||||
ref WeavingFailed);
|
||||
|
||||
logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
||||
md.Name == "LogWarning" &&
|
||||
md.Parameters.Count == 1 &&
|
||||
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
||||
ref WeavingFailed);
|
||||
|
||||
TypeReference typeType = Import(typeof(Type));
|
||||
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
|
||||
|
||||
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
|
||||
GetWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Get", ref WeavingFailed);
|
||||
ReturnWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Return", ref WeavingFailed);
|
||||
|
||||
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
|
||||
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
|
||||
{
|
||||
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
|
||||
md.HasGenericParameters;
|
||||
}),
|
||||
ref WeavingFailed);
|
||||
|
||||
// [InitializeOnLoadMethod]
|
||||
// 'UnityEditor' is not available in builds.
|
||||
// we can only import this attribute if we are in an Editor assembly.
|
||||
if (Helpers.IsEditorAssembly(assembly))
|
||||
{
|
||||
TypeReference initializeOnLoadMethodAttributeRef = Import(typeof(InitializeOnLoadMethodAttribute));
|
||||
initializeOnLoadMethodAttribute = initializeOnLoadMethodAttributeRef.Resolve();
|
||||
}
|
||||
|
||||
// [RuntimeInitializeOnLoadMethod]
|
||||
TypeReference runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
|
||||
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Mirror/Editor/Weaver/WeaverTypes.cs.meta
Normal file
3
Assets/Mirror/Editor/Weaver/WeaverTypes.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2585961bf7fe4c10a9143f4087efdf6f
|
||||
timeCreated: 1596486854
|
341
Assets/Mirror/Editor/Weaver/Writers.cs
Normal file
341
Assets/Mirror/Editor/Weaver/Writers.cs
Normal file
@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
// to use Mono.CecilX.Rocks here, we need to 'override references' in the
|
||||
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
||||
// otherwise we get an unknown import exception.
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// not static, because ILPostProcessor is multithreaded
|
||||
public class Writers
|
||||
{
|
||||
// Writers are only for this assembly.
|
||||
// can't be used from another assembly, otherwise we will get:
|
||||
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
||||
AssemblyDefinition assembly;
|
||||
WeaverTypes weaverTypes;
|
||||
TypeDefinition GeneratedCodeClass;
|
||||
Logger Log;
|
||||
|
||||
Dictionary<TypeReference, MethodReference> writeFuncs =
|
||||
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
||||
|
||||
public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.weaverTypes = weaverTypes;
|
||||
this.GeneratedCodeClass = GeneratedCodeClass;
|
||||
this.Log = Log;
|
||||
}
|
||||
|
||||
public void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
if (writeFuncs.ContainsKey(dataType))
|
||||
{
|
||||
// TODO enable this again later.
|
||||
// Writer has some obsolete functions that were renamed.
|
||||
// Don't want weaver warnings for all of them.
|
||||
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
|
||||
}
|
||||
|
||||
// we need to import type when we Initialize Writers so import here in case it is used anywhere else
|
||||
TypeReference imported = assembly.MainModule.ImportReference(dataType);
|
||||
writeFuncs[imported] = methodReference;
|
||||
}
|
||||
|
||||
void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
|
||||
{
|
||||
Register(typeReference, newWriterFunc);
|
||||
GeneratedCodeClass.Methods.Add(newWriterFunc);
|
||||
}
|
||||
|
||||
// Finds existing writer for type, if non exists trys to create one
|
||||
public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
|
||||
return foundFunc;
|
||||
|
||||
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
|
||||
try
|
||||
{
|
||||
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
|
||||
return GenerateWriter(importedVariable, ref WeavingFailed);
|
||||
}
|
||||
catch (GenerateWriterException e)
|
||||
{
|
||||
Log.Error(e.Message, e.MemberReference);
|
||||
WeavingFailed = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Throws GenerateWriterException when writer could not be generated for type
|
||||
MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
|
||||
{
|
||||
if (variableReference.IsByReference)
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
|
||||
}
|
||||
|
||||
// Arrays are special, if we resolve them, we get the element type,
|
||||
// e.g. int[] resolves to int
|
||||
// therefore process this before checks below
|
||||
if (variableReference.IsArray)
|
||||
{
|
||||
if (variableReference.IsMultidimensionalArray())
|
||||
{
|
||||
throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
||||
}
|
||||
TypeReference elementType = variableReference.GetElementType();
|
||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
|
||||
}
|
||||
|
||||
if (variableReference.Resolve()?.IsEnum ?? false)
|
||||
{
|
||||
// serialize enum as their base type
|
||||
return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
|
||||
}
|
||||
|
||||
// check for collections
|
||||
if (variableReference.Is(typeof(ArraySegment<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
|
||||
}
|
||||
if (variableReference.Is(typeof(List<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
|
||||
}
|
||||
|
||||
// handle both NetworkBehaviour and inheritors.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
|
||||
if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
|
||||
{
|
||||
return GetNetworkBehaviourWriter(variableReference);
|
||||
}
|
||||
|
||||
// check for invalid types
|
||||
TypeDefinition variableDefinition = variableReference.Resolve();
|
||||
if (variableDefinition == null)
|
||||
{
|
||||
throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.Object>())
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.ScriptableObject>())
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableDefinition.HasGenericParameters)
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableDefinition.IsInterface)
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
if (variableDefinition.IsAbstract)
|
||||
{
|
||||
throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
|
||||
}
|
||||
|
||||
// generate writer for class/struct
|
||||
return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
|
||||
}
|
||||
|
||||
MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
|
||||
{
|
||||
// all NetworkBehaviours can use the same write function
|
||||
if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out MethodReference func))
|
||||
{
|
||||
// register function so it is added to writer<T>
|
||||
// use Register instead of RegisterWriteFunc because this is not a generated function
|
||||
Register(variableReference, func);
|
||||
|
||||
return func;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
|
||||
throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
|
||||
}
|
||||
}
|
||||
|
||||
MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
|
||||
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
|
||||
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
worker.Emit(OpCodes.Call, underlyingWriter);
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateWriterFunc(TypeReference variable)
|
||||
{
|
||||
string functionName = $"_Write_{variable.FullName}";
|
||||
// create new writer for this type
|
||||
MethodDefinition writerFunc = new MethodDefinition(functionName,
|
||||
MethodAttributes.Public |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
weaverTypes.Import(typeof(void)));
|
||||
|
||||
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
|
||||
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
|
||||
writerFunc.Body.InitLocals = true;
|
||||
|
||||
RegisterWriteFunc(variable, writerFunc);
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
|
||||
{
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
|
||||
if (!variable.Resolve().IsValueType)
|
||||
WriteNullCheck(worker, ref WeavingFailed);
|
||||
|
||||
if (!WriteAllFields(variable, worker, ref WeavingFailed))
|
||||
return null;
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
||||
{
|
||||
// if (value == null)
|
||||
// {
|
||||
// writer.WriteBoolean(false);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
|
||||
Instruction labelNotNull = worker.Create(OpCodes.Nop);
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
worker.Emit(OpCodes.Brtrue, labelNotNull);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I4_0);
|
||||
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
||||
worker.Emit(OpCodes.Ret);
|
||||
worker.Append(labelNotNull);
|
||||
|
||||
// write.WriteBoolean(true);
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldc_I4_1);
|
||||
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
||||
}
|
||||
|
||||
// Find all fields in type and write them
|
||||
bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
||||
{
|
||||
foreach (FieldDefinition field in variable.FindAllPublicFields())
|
||||
{
|
||||
MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
|
||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
||||
if (writeFunc == null) { return false; }
|
||||
|
||||
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
|
||||
|
||||
worker.Emit(OpCodes.Ldarg_0);
|
||||
worker.Emit(OpCodes.Ldarg_1);
|
||||
worker.Emit(OpCodes.Ldfld, fieldRef);
|
||||
worker.Emit(OpCodes.Call, writeFunc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
|
||||
{
|
||||
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
|
||||
MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
|
||||
|
||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
||||
if (elementWriteFunc == null)
|
||||
{
|
||||
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
|
||||
WeavingFailed = true;
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
ModuleDefinition module = assembly.MainModule;
|
||||
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
|
||||
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
|
||||
|
||||
GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
|
||||
methodRef.GenericArguments.Add(elementType);
|
||||
|
||||
// generates
|
||||
// reader.WriteArray<T>(array);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
worker.Emit(OpCodes.Ldarg_0); // writer
|
||||
worker.Emit(OpCodes.Ldarg_1); // collection
|
||||
|
||||
worker.Emit(OpCodes.Call, methodRef); // WriteArray
|
||||
|
||||
worker.Emit(OpCodes.Ret);
|
||||
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
// Save a delegate for each one of the writers into Writer{T}.write
|
||||
internal void InitializeWriters(ILProcessor worker)
|
||||
{
|
||||
ModuleDefinition module = assembly.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 (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
|
||||
{
|
||||
TypeReference targetType = kvp.Key;
|
||||
MethodReference writeFunc = kvp.Value;
|
||||
|
||||
// create a Action<NetworkWriter, T> delegate
|
||||
worker.Emit(OpCodes.Ldnull);
|
||||
worker.Emit(OpCodes.Ldftn, writeFunc);
|
||||
GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
|
||||
MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
|
||||
worker.Emit(OpCodes.Newobj, actionRefInstance);
|
||||
|
||||
// save it in Writer<T>.write
|
||||
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
|
||||
worker.Emit(OpCodes.Stsfld, specializedField);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user