mirror of
https://github.com/smyalygames/monopoly.git
synced 2026-01-04 02:38:42 +01:00
Added multiplayer plugin
This commit is contained in:
74
Assets/Mirror/Editor/EnterPlayModeSettingsCheck.cs
Normal file
74
Assets/Mirror/Editor/EnterPlayModeSettingsCheck.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Unity 2019.3 has an experimental 'disable domain reload on play'
|
||||
// feature. keeping any global state between sessions will break
|
||||
// Mirror and most of our user's projects. don't allow it for now.
|
||||
// https://blogs.unity3d.com/2019/11/05/enter-play-mode-faster-in-unity-2019-3/
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class EnterPlayModeSettingsCheck : MonoBehaviour
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnInitializeOnLoad()
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
// We can't support experimental "Enter Play Mode Options" mode
|
||||
// Check immediately on load, and before entering play mode, and warn the user
|
||||
CheckPlayModeOptions();
|
||||
#endif
|
||||
|
||||
// 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)
|
||||
{
|
||||
CheckSuccessfulWeave();
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
// We can't support experimental "Enter Play Mode Options" mode
|
||||
// Check and prevent entering play mode if enabled
|
||||
CheckPlayModeOptions();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckSuccessfulWeave()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
static void CheckPlayModeOptions()
|
||||
{
|
||||
// enabling the checkbox is enough. it controls all the other settings.
|
||||
if (EditorSettings.enterPlayModeOptionsEnabled)
|
||||
{
|
||||
Debug.LogError("Enter Play Mode Options are not supported by Mirror. Please disable 'ProjectSettings -> Editor -> Enter Play Mode Settings (Experimental)'.");
|
||||
EditorApplication.isPlaying = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/EnterPlayModeSettingsCheck.cs.meta
Normal file
11
Assets/Mirror/Editor/EnterPlayModeSettingsCheck.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b15a0d2ca0909400eb53dd6fe894cddd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
76
Assets/Mirror/Editor/InspectorHelper.cs
Normal file
76
Assets/Mirror/Editor/InspectorHelper.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
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>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="deepestBaseType">Stops at this base type (exclusive)</param>
|
||||
/// <returns></returns>
|
||||
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 IsSyncObject(this FieldInfo field)
|
||||
{
|
||||
return typeof(SyncObject).IsAssignableFrom(field.FieldType);
|
||||
}
|
||||
public static bool HasShowInInspector(this FieldInfo field)
|
||||
{
|
||||
object[] fieldMarkers = field.GetCustomAttributes(typeof(ShowInInspectorAttribute), true);
|
||||
return fieldMarkers.Length > 0;
|
||||
}
|
||||
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:
|
||||
1
Assets/Mirror/Editor/LogLevelWindow.cs
Normal file
1
Assets/Mirror/Editor/LogLevelWindow.cs
Normal file
@@ -0,0 +1 @@
|
||||
// File moved to Mirror/Editor/Logging/LogLevelWindow.cs
|
||||
11
Assets/Mirror/Editor/LogLevelWindow.cs.meta
Normal file
11
Assets/Mirror/Editor/LogLevelWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f28def2148ed5194abe70af012a4e3e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Mirror/Editor/Logging.meta
Normal file
8
Assets/Mirror/Editor/Logging.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d97731cd74ac8b4b8aad808548ef9cd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
70
Assets/Mirror/Editor/Logging/LogLevelWindow.cs
Normal file
70
Assets/Mirror/Editor/Logging/LogLevelWindow.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Mirror.Logging;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.EditorScripts.Logging
|
||||
{
|
||||
public class LogLevelWindow : EditorWindow
|
||||
{
|
||||
[Header("Log Settings Asset")]
|
||||
[SerializeField] LogSettings settings = null;
|
||||
|
||||
SerializedObject serializedObject;
|
||||
SerializedProperty settingsProp;
|
||||
Vector2 dictionaryScrollPosition;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
serializedObject = new SerializedObject(this);
|
||||
settingsProp = serializedObject.FindProperty(nameof(settings));
|
||||
|
||||
LogSettings existingSettings = EditorLogSettingsLoader.FindLogSettings();
|
||||
if (existingSettings != null)
|
||||
{
|
||||
settingsProp.objectReferenceValue = existingSettings;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
using (EditorGUILayout.ScrollViewScope scrollScope = new EditorGUILayout.ScrollViewScope(dictionaryScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar))
|
||||
{
|
||||
dictionaryScrollPosition = scrollScope.scrollPosition;
|
||||
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(settingsProp);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
LogSettings newSettings = LogLevelsGUI.DrawCreateNewButton();
|
||||
if (newSettings != null)
|
||||
{
|
||||
settingsProp.objectReferenceValue = newSettings;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogLevelsGUI.DrawLogFactoryDictionary(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Window/Analysis/Mirror Log Levels", priority = 20002)]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
LogLevelWindow window = GetWindow<LogLevelWindow>();
|
||||
window.minSize = new Vector2(200, 100);
|
||||
window.titleContent = new GUIContent("Mirror Log Levels");
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Logging/LogLevelWindow.cs.meta
Normal file
11
Assets/Mirror/Editor/Logging/LogLevelWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3dbf48190d77d243b87962a82c3b164
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
61
Assets/Mirror/Editor/Logging/LogLevelsGUI.cs
Normal file
61
Assets/Mirror/Editor/Logging/LogLevelsGUI.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using Mirror.Logging;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.EditorScripts.Logging
|
||||
{
|
||||
public static class LogLevelsGUI
|
||||
{
|
||||
public static LogSettings DrawCreateNewButton()
|
||||
{
|
||||
if (GUILayout.Button("Create New"))
|
||||
{
|
||||
return ScriptableObjectUtility.CreateAsset<LogSettings>(nameof(LogSettings));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DrawLogFactoryDictionary(LogSettings settings)
|
||||
{
|
||||
using (EditorGUI.ChangeCheckScope scope = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
if (LogFactory.loggers.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("No Keys found in LogFactory.loggers\nPlay the game for default log values to be added to LogFactory", EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Logging Components", EditorStyles.boldLabel);
|
||||
|
||||
foreach (KeyValuePair<string, ILogger> item in LogFactory.loggers)
|
||||
{
|
||||
DrawLoggerField(item);
|
||||
}
|
||||
|
||||
if (scope.changed)
|
||||
{
|
||||
settings.SaveFromDictionary(LogFactory.loggers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLoggerField(KeyValuePair<string, ILogger> item)
|
||||
{
|
||||
ILogger logger = item.Value;
|
||||
string name = item.Key;
|
||||
|
||||
const float fieldWidth = 100f;
|
||||
const float inspectorMargin = 25f;
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUILayout.LabelField(new GUIContent(ObjectNames.NicifyVariableName(name)), GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth - fieldWidth - inspectorMargin));
|
||||
logger.filterLogType = (LogType)EditorGUILayout.EnumPopup(logger.filterLogType, GUILayout.Width(fieldWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Logging/LogLevelsGUI.cs.meta
Normal file
11
Assets/Mirror/Editor/Logging/LogLevelsGUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d6ce9d62a2d2ec4d8cef8a0d22b8dd2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Mirror/Editor/Logging/LogSettingsEditor.cs
Normal file
24
Assets/Mirror/Editor/Logging/LogSettingsEditor.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Mirror.Logging;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.EditorScripts.Logging
|
||||
{
|
||||
[CustomEditor(typeof(LogSettings))]
|
||||
public class LogSettingsEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
CurrentScriptField();
|
||||
|
||||
LogLevelsGUI.DrawLogFactoryDictionary(target as LogSettings);
|
||||
}
|
||||
|
||||
public void CurrentScriptField()
|
||||
{
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Logging/LogSettingsEditor.cs.meta
Normal file
11
Assets/Mirror/Editor/Logging/LogSettingsEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f4ecb3d81ce9ff44b91f311ee46d4ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Assets/Mirror/Editor/Logging/NetworkLogSettingsEditor.cs
Normal file
31
Assets/Mirror/Editor/Logging/NetworkLogSettingsEditor.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Mirror.Logging;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Mirror.EditorScripts.Logging
|
||||
{
|
||||
[CustomEditor(typeof(NetworkLogSettings))]
|
||||
public class NetworkLogSettingsEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
NetworkLogSettings target = this.target as NetworkLogSettings;
|
||||
|
||||
if (target.settings == null)
|
||||
{
|
||||
LogSettings newSettings = LogLevelsGUI.DrawCreateNewButton();
|
||||
if (newSettings != null)
|
||||
{
|
||||
SerializedProperty settingsProp = serializedObject.FindProperty("settings");
|
||||
settingsProp.objectReferenceValue = newSettings;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogLevelsGUI.DrawLogFactoryDictionary(target.settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37fb96d5bbf965d47acfc5c8589a1b71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Mirror/Editor/Mirror.Editor.asmdef
Normal file
17
Assets/Mirror/Editor/Mirror.Editor.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Mirror.Editor",
|
||||
"references": [
|
||||
"Mirror",
|
||||
"Mirror.Weaver"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
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:
|
||||
186
Assets/Mirror/Editor/NetworkBehaviourInspector.cs
Normal file
186
Assets/Mirror/Editor/NetworkBehaviourInspector.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[CustomEditor(typeof(NetworkBehaviour), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkBehaviourInspector : Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all visible syncVars in target class
|
||||
/// </summary>
|
||||
protected List<string> syncVarNames = new List<string>();
|
||||
bool syncsAnything;
|
||||
SyncListDrawer syncListDrawer;
|
||||
|
||||
// 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 cheeck length of list so see if sync objects exists
|
||||
FieldInfo syncObjectsField = scriptClass.GetField("syncObjects", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
List<SyncObject> syncObjects = (List<SyncObject>)syncObjectsField.GetValue(serializedObject.targetObject);
|
||||
|
||||
return syncObjects.Count > 0;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (target == null) { Debug.LogWarning("NetworkBehaviourInspector had no target object"); 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();
|
||||
|
||||
syncVarNames = new List<string>();
|
||||
foreach (FieldInfo field in InspectorHelper.GetAllFields(scriptClass, typeof(NetworkBehaviour)))
|
||||
{
|
||||
if (field.IsSyncVar() && field.IsVisibleField())
|
||||
{
|
||||
syncVarNames.Add(field.Name);
|
||||
}
|
||||
}
|
||||
|
||||
syncListDrawer = new SyncListDrawer(serializedObject.targetObject);
|
||||
|
||||
syncsAnything = SyncsAnything(scriptClass);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
DrawDefaultSyncLists();
|
||||
DrawDefaultSyncSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws Sync Objects that are IEnumerable
|
||||
/// </summary>
|
||||
protected void DrawDefaultSyncLists()
|
||||
{
|
||||
// Need this check incase OnEnable returns early
|
||||
if (syncListDrawer == null) { return; }
|
||||
|
||||
syncListDrawer.Draw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws SyncSettings if the NetworkBehaviour has anything to sync
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
|
||||
|
||||
// apply
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
public class SyncListDrawer
|
||||
{
|
||||
readonly UnityEngine.Object targetObject;
|
||||
readonly List<SyncListField> syncListFields;
|
||||
|
||||
public SyncListDrawer(UnityEngine.Object targetObject)
|
||||
{
|
||||
this.targetObject = targetObject;
|
||||
syncListFields = new List<SyncListField>();
|
||||
foreach (FieldInfo field in InspectorHelper.GetAllFields(targetObject.GetType(), typeof(NetworkBehaviour)))
|
||||
{
|
||||
if (field.IsSyncObject() && field.IsVisibleSyncObject())
|
||||
{
|
||||
syncListFields.Add(new SyncListField(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (syncListFields.Count == 0) { return; }
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Sync Lists", EditorStyles.boldLabel);
|
||||
|
||||
for (int i = 0; i < syncListFields.Count; i++)
|
||||
{
|
||||
DrawSyncList(syncListFields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSyncList(SyncListField syncListField)
|
||||
{
|
||||
syncListField.visible = EditorGUILayout.Foldout(syncListField.visible, syncListField.label);
|
||||
if (syncListField.visible)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
object fieldValue = syncListField.field.GetValue(targetObject);
|
||||
if (fieldValue is IEnumerable synclist)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (object item in synclist)
|
||||
{
|
||||
string itemValue = item != null ? item.ToString() : "NULL";
|
||||
string itemLabel = "Element " + index;
|
||||
EditorGUILayout.LabelField(itemLabel, itemValue);
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SyncListField
|
||||
{
|
||||
public bool visible;
|
||||
public readonly FieldInfo field;
|
||||
public readonly string label;
|
||||
|
||||
public SyncListField(FieldInfo field)
|
||||
{
|
||||
this.field = field;
|
||||
visible = false;
|
||||
label = field.Name + " [" + field.FieldType.Name + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
} //namespace
|
||||
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 != null && 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, NetworkConnection> 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("Has Authority", identity.hasAuthority));
|
||||
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.IsNullOrEmpty(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:
|
||||
113
Assets/Mirror/Editor/NetworkManagerEditor.cs
Normal file
113
Assets/Mirror/Editor/NetworkManagerEditor.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
92
Assets/Mirror/Editor/NetworkScenePostProcess.cs
Normal file
92
Assets/Mirror/Editor/NetworkScenePostProcess.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
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" &&
|
||||
!PrefabUtility.IsPartOfPrefabAsset(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 Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void PrepareSceneObject(NetworkIdentity identity)
|
||||
{
|
||||
// set scene hash
|
||||
identity.SetSceneIdSceneHashPartInternal();
|
||||
|
||||
// disable it
|
||||
// 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
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject);
|
||||
#else
|
||||
GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject);
|
||||
#endif
|
||||
if (prefabGO)
|
||||
{
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
GameObject prefabRootGO = prefabGO.transform.root.gameObject;
|
||||
#else
|
||||
GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO);
|
||||
#endif
|
||||
if (prefabRootGO != null && prefabRootGO.GetComponentsInChildren<NetworkIdentity>().Length > 1)
|
||||
{
|
||||
Debug.LogWarningFormat("Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported.", prefabRootGO.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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:
|
||||
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.IsNullOrEmpty(property.stringValue))
|
||||
{
|
||||
// try to load it from the build settings for legacy compatibility
|
||||
sceneObject = GetBuildSettingsSceneObject(property.stringValue);
|
||||
}
|
||||
if (sceneObject == null && !string.IsNullOrEmpty(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.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:
|
||||
54
Assets/Mirror/Editor/ScriptableObjectUtility.cs
Normal file
54
Assets/Mirror/Editor/ScriptableObjectUtility.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.EditorScripts
|
||||
{
|
||||
public static class ScriptableObjectUtility
|
||||
{
|
||||
/// <summary>
|
||||
// This makes it easy to create, name and place unique new ScriptableObject asset files.
|
||||
/// </summary>
|
||||
public static T CreateAsset<T>(string defaultName) where T : ScriptableObject
|
||||
{
|
||||
string path = SavePanel(defaultName);
|
||||
// user click cancel
|
||||
if (string.IsNullOrEmpty(path)) { return null; }
|
||||
|
||||
T asset = ScriptableObject.CreateInstance<T>();
|
||||
|
||||
SaveAsset(path, asset);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
static string SavePanel(string name)
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel(
|
||||
"Save ScriptableObject",
|
||||
"Assets/Mirror/",
|
||||
name + ".asset",
|
||||
"asset");
|
||||
|
||||
// user click cancel, return early
|
||||
if (string.IsNullOrEmpty(path)) { return path; }
|
||||
|
||||
// Unity only wants path from Assets
|
||||
if (path.StartsWith(Application.dataPath))
|
||||
{
|
||||
path = "Assets" + path.Substring(Application.dataPath.Length);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static void SaveAsset(string path, ScriptableObject asset)
|
||||
{
|
||||
string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path);
|
||||
|
||||
AssetDatabase.CreateAsset(asset, assetPathAndName);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/ScriptableObjectUtility.cs.meta
Normal file
11
Assets/Mirror/Editor/ScriptableObjectUtility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d54a29ddd5b52b4eaa07ed39c0e3e83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
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, true);
|
||||
GUI.Label(labelRect, syncVarIndicatorContent, EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUI.GetPropertyHeight(property);
|
||||
}
|
||||
}
|
||||
} //namespace
|
||||
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:
|
||||
175
Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs
Normal file
175
Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
using UnityAssembly = UnityEditor.Compilation.Assembly;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class CompilationFinishedHook
|
||||
{
|
||||
const string MirrorRuntimeAssemblyName = "Mirror";
|
||||
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
|
||||
|
||||
// delegate for subscription to Weaver debug messages
|
||||
public static Action<string> OnWeaverMessage;
|
||||
// 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 we weave any assemblies when CompilationPipeline delegates are invoked
|
||||
public static bool WeaverEnabled { get; set; }
|
||||
// controls weather Weaver errors are reported direct to the Unity console (tests enable this)
|
||||
public static bool UnityLogEnabled = true;
|
||||
|
||||
// warning message handler that also calls OnWarningMethod delegate
|
||||
static void HandleWarning(string msg)
|
||||
{
|
||||
if (UnityLogEnabled) Debug.LogWarning(msg);
|
||||
if (OnWeaverWarning != null) OnWeaverWarning.Invoke(msg);
|
||||
}
|
||||
|
||||
// error message handler that also calls OnErrorMethod delegate
|
||||
static void HandleError(string msg)
|
||||
{
|
||||
if (UnityLogEnabled) Debug.LogError(msg);
|
||||
if (OnWeaverError != null) OnWeaverError.Invoke(msg);
|
||||
}
|
||||
|
||||
[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]);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
EditorUtility.RequestScriptReload();
|
||||
#else
|
||||
UnityEditorInternal.InternalEditorUtility.RequestScriptReload();
|
||||
#endif
|
||||
}
|
||||
|
||||
static string FindMirrorRuntime()
|
||||
{
|
||||
foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (assembly.name == MirrorRuntimeAssemblyName)
|
||||
{
|
||||
return assembly.outputPath;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool CompilerMessagesContainError(CompilerMessage[] messages)
|
||||
{
|
||||
return messages.Any(msg => msg.type == CompilerMessageType.Error);
|
||||
}
|
||||
|
||||
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
|
||||
if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// don't weave mirror files
|
||||
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// find Mirror.dll
|
||||
string mirrorRuntimeDll = FindMirrorRuntime();
|
||||
if (string.IsNullOrEmpty(mirrorRuntimeDll))
|
||||
{
|
||||
Debug.LogError("Failed to find Mirror runtime assembly");
|
||||
return;
|
||||
}
|
||||
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 = GetDependecyPaths(assemblyPath);
|
||||
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
|
||||
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
|
||||
Log.WarningMethod = HandleWarning;
|
||||
Log.ErrorMethod = HandleError;
|
||||
|
||||
if (!Weaver.WeaveAssembly(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> GetDependecyPaths(string assemblyPath)
|
||||
{
|
||||
// build directory list for later asm/symbol resolving using CompilationPipeline refs
|
||||
HashSet<string> dependencyPaths = new HashSet<string>
|
||||
{
|
||||
Path.GetDirectoryName(assemblyPath)
|
||||
};
|
||||
foreach (UnityAssembly unityAsm in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (unityAsm.outputPath != assemblyPath)
|
||||
continue;
|
||||
|
||||
foreach (string unityAsmRef in unityAsm.compiledAssemblyReferences)
|
||||
{
|
||||
dependencyPaths.Add(Path.GetDirectoryName(unityAsmRef));
|
||||
}
|
||||
}
|
||||
|
||||
return dependencyPaths;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de2aeb2e8068f421a9a1febe408f7051
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
266
Assets/Mirror/Editor/Weaver/Extensions.cs
Normal file
266
Assets/Mirror/Editor/Weaver/Extensions.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
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 t)
|
||||
{
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
return td.GetElementType().FullName == t.FullName;
|
||||
}
|
||||
return td.FullName == t.FullName;
|
||||
}
|
||||
|
||||
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
|
||||
|
||||
public static bool IsDerivedFrom<T>(this TypeDefinition td) => IsDerivedFrom(td, typeof(T));
|
||||
|
||||
public static bool IsDerivedFrom(this TypeDefinition td, Type baseClass)
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
foreach (InterfaceImplementation iface in typedef.Interfaces)
|
||||
{
|
||||
if (iface.InterfaceType.Is<TInterface>())
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TypeReference parent = typedef.BaseType;
|
||||
typedef = parent?.Resolve();
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// this can happen for pluins.
|
||||
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMultidimensionalArray(this TypeReference tr)
|
||||
{
|
||||
return tr is ArrayType arrayType && arrayType.Rank > 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="instanceType"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, 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 Weaver.CurrentAssembly.MainModule.ImportReference(reference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a field of a generic class such as Writer<T>.write,
|
||||
/// and a generic instance such as ArraySegment`int
|
||||
/// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
||||
/// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="instanceType">Generic Instance eg Writer<int></param>
|
||||
/// <returns></returns>
|
||||
public static FieldReference SpecializeField(this FieldReference self, GenericInstanceType instanceType)
|
||||
{
|
||||
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
|
||||
|
||||
return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
|
||||
}
|
||||
|
||||
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
|
||||
{
|
||||
foreach (CustomAttribute ca in method.CustomAttributes)
|
||||
{
|
||||
if (ca.AttributeType.Is<TAttribute>())
|
||||
return ca;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
|
||||
{
|
||||
// Linq allocations don't matter in weaver
|
||||
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)
|
||||
{
|
||||
// Linq allocations don't matter in weaver
|
||||
return td.Methods.FirstOrDefault(method => method.Name == methodName);
|
||||
}
|
||||
|
||||
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
|
||||
{
|
||||
// Linq allocations don't matter in weaver
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds public fields in type and base type
|
||||
/// </summary>
|
||||
/// <param name="variable"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
|
||||
{
|
||||
return FindAllPublicFields(variable.Resolve());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds public fields in type and base type
|
||||
/// </summary>
|
||||
/// <param name="variable"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
|
||||
{
|
||||
while (typeDefinition != null)
|
||||
{
|
||||
foreach (FieldDefinition field in typeDefinition.Fields)
|
||||
{
|
||||
if (field.IsStatic || field.IsPrivate)
|
||||
continue;
|
||||
|
||||
if (field.IsNotSerialized)
|
||||
continue;
|
||||
|
||||
yield return field;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
typeDefinition = typeDefinition.BaseType?.Resolve();
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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:
|
||||
25
Assets/Mirror/Editor/Weaver/Helpers.cs
Normal file
25
Assets/Mirror/Editor/Weaver/Helpers.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
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 string DestinationFileFor(string outputDir, string assemblyPath)
|
||||
{
|
||||
string fileName = Path.GetFileName(assemblyPath);
|
||||
Debug.Assert(fileName != null, "fileName != null");
|
||||
|
||||
return Path.Combine(outputDir, fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
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:
|
||||
20
Assets/Mirror/Editor/Weaver/Log.cs
Normal file
20
Assets/Mirror/Editor/Weaver/Log.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
public static Action<string> WarningMethod;
|
||||
public static Action<string> ErrorMethod;
|
||||
|
||||
public static void Warning(string msg)
|
||||
{
|
||||
WarningMethod(msg);
|
||||
}
|
||||
|
||||
public static void Error(string msg)
|
||||
{
|
||||
ErrorMethod(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Weaver/Log.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Log.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:
|
||||
16
Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
Normal file
16
Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Mirror.Weaver",
|
||||
"references": [
|
||||
"Mirror"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
7
Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta
Normal file
7
Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d0b9d21c3ff546a4aa32399dfd33474
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
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:
|
||||
127
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
127
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Command] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class CommandProcessor
|
||||
{
|
||||
/*
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriter networkWriter = new NetworkWriter();
|
||||
networkWriter.Write(thrusting);
|
||||
networkWriter.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, networkWriter, cmdName);
|
||||
}
|
||||
|
||||
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(TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr)
|
||||
{
|
||||
MethodDefinition cmd = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
// NetworkWriter writer = new NetworkWriter();
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Cmd call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
string cmdName = md.Name;
|
||||
int channel = commandAttr.GetField("channel", 0);
|
||||
bool ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);
|
||||
|
||||
// invoke internal send and return
|
||||
// load 'base.' to call the SendCommand function with
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, cmdName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
|
||||
worker.Append(worker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.sendCommandInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(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(TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc)
|
||||
{
|
||||
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, method.Name, label, "Command");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
AddSenderConnection(method, worker);
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, cmdCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(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)
|
||||
// exmaple: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd67b3f7c2d66074a9bc7a23787e2ffb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// removed Oct 5 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e25c00c88fc134f6ea7ab00ae4db8083
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
125
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
125
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class MethodProcessor
|
||||
{
|
||||
private const string RpcPrefix = "UserCode_";
|
||||
|
||||
// creates a method substitute
|
||||
// For example, if we have this:
|
||||
// public void CmdThrust(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// it will substitute the method and move the code to a new method with a provided name
|
||||
// for example:
|
||||
//
|
||||
// public void CmdTrust(float thrusting, int spin)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public void <newName>(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// Note that all the calls to the method remain untouched
|
||||
//
|
||||
// the original method definition loses all code
|
||||
// this returns the newly created method with all the user provided code
|
||||
public static MethodDefinition SubstituteMethod(TypeDefinition td, MethodDefinition md)
|
||||
{
|
||||
string newName = RpcPrefix + md.Name;
|
||||
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||
|
||||
// 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(td, cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and fixes call to base methods within remote calls
|
||||
/// <para>For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`</para>
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="method"></param>
|
||||
public static void FixRemoteCallToBaseMethod(TypeDefinition type, MethodDefinition method)
|
||||
{
|
||||
string callName = method.Name;
|
||||
|
||||
// Cmd/rpc start with Weaver.RpcPrefix
|
||||
// eg CallCmdDoSomething
|
||||
if (!callName.StartsWith(RpcPrefix))
|
||||
return;
|
||||
|
||||
// eg CmdDoSomething
|
||||
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||
|
||||
foreach (Instruction instruction in method.Body.Instructions)
|
||||
{
|
||||
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
||||
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
|
||||
calledMethod.Name == baseRemoteCallName)
|
||||
{
|
||||
TypeDefinition baseType = type.BaseType.Resolve();
|
||||
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||
|
||||
if (baseMethod == null)
|
||||
{
|
||||
Weaver.Error($"Could not find base method for {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseMethod.IsVirtual)
|
||||
{
|
||||
Weaver.Error($"Could not find base method that was virutal {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
instruction.Operand = baseMethod;
|
||||
|
||||
Weaver.DLog(type, "Replacing call to '{0}' with '{1}' inside '{2}'", calledMethod.FullName, baseMethod.FullName, method.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,45 @@
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||
/// </summary>
|
||||
static class MonoBehaviourProcessor
|
||||
{
|
||||
public static void Process(TypeDefinition td)
|
||||
{
|
||||
ProcessSyncVars(td);
|
||||
ProcessMethods(td);
|
||||
}
|
||||
|
||||
static void ProcessSyncVars(TypeDefinition td)
|
||||
{
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
Weaver.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Weaver.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethods(TypeDefinition td)
|
||||
{
|
||||
// find command and RPC functions
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
if (md.HasCustomAttribute<CommandAttribute>())
|
||||
Weaver.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||
Weaver.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||
Weaver.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1099
Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
Normal file
1099
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:
|
||||
160
Assets/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
Normal file
160
Assets/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class PropertySiteProcessor
|
||||
{
|
||||
public static void Process(ModuleDefinition moduleDef)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
//Search through the types
|
||||
foreach (TypeDefinition td in moduleDef.Types)
|
||||
{
|
||||
if (td.IsClass)
|
||||
{
|
||||
ProcessSiteClass(td);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
|
||||
}
|
||||
|
||||
static void ProcessSiteClass(TypeDefinition td)
|
||||
{
|
||||
//Console.WriteLine(" ProcessSiteClass " + td);
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
ProcessSiteClass(nested);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
// process all references to replaced members with properties
|
||||
//Weaver.DLog(td, " ProcessSiteMethod " + md);
|
||||
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
for (int iCount = 0; iCount < md.Body.Instructions.Count;)
|
||||
{
|
||||
Instruction instr = md.Body.Instructions[iCount];
|
||||
iCount += ProcessInstruction(md, instr, iCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar write access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
|
||||
{
|
||||
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
||||
{
|
||||
// this instruction sets the value of a field. cache the field reference.
|
||||
ProcessInstructionSetterField(md, instr, opFieldst);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
||||
{
|
||||
// this instruction gets the value of a field. cache the field reference.
|
||||
ProcessInstructionGetterField(md, instr, opFieldld);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
||||
{
|
||||
// loading a field by reference, watch out for initobj instruction
|
||||
// see https://github.com/vis2k/Mirror/issues/696
|
||||
return ProcessInstructionLoadAddress(md, instr, opFieldlda, iCount);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return 1;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.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:
|
||||
175
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
175
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
// finds all readers and writers and register them
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ReaderWriterProcessor
|
||||
{
|
||||
public static bool Process(AssemblyDefinition CurrentAssembly)
|
||||
{
|
||||
Readers.Init();
|
||||
Writers.Init();
|
||||
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (unityAsm.name == "Mirror")
|
||||
{
|
||||
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
|
||||
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
|
||||
}
|
||||
|
||||
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
|
||||
{
|
||||
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 asmebly has any declared writers then it is "modified"
|
||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
|
||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// if asmebly has any network message then it is modified
|
||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
|
||||
{
|
||||
bool modified = false;
|
||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||
{
|
||||
Readers.GetReadFunc(module.ImportReference(klass));
|
||||
Writers.GetWriteFunc(module.ImportReference(klass));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (TypeDefinition td in klass.NestedTypes)
|
||||
{
|
||||
modified |= LoadMessageReadWriter(module, td);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
||||
assemblyReference.Name == nameof(UnityEditor)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a method that will store all the readers and writers into
|
||||
/// <see cref="Writer{T}.write"/> and <see cref="Reader{T}.read"/>
|
||||
///
|
||||
/// The method will be marked InitializeOnLoadMethodAttribute so it gets
|
||||
/// executed before mirror runtime code
|
||||
/// </summary>
|
||||
/// <param name="currentAssembly"></param>
|
||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||
MethodAttributes.Static,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
|
||||
|
||||
CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
|
||||
customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||
rwInitializer.CustomAttributes.Add(customAttributeRef);
|
||||
|
||||
if (IsEditorAssembly(currentAssembly))
|
||||
{
|
||||
// editor assembly, add InitializeOnLoadMethod too. Useful for the editor tests
|
||||
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
|
||||
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
|
||||
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
|
||||
}
|
||||
|
||||
ILProcessor worker = rwInitializer.Body.GetILProcessor();
|
||||
|
||||
Writers.InitializeWriters(worker);
|
||||
Readers.InitializeReaders(worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
TypeDefinition generateClass = Weaver.WeaveLists.generateContainerClass;
|
||||
|
||||
generateClass.Methods.Add(rwInitializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
105
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
105
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Rpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class RpcProcessor
|
||||
{
|
||||
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(
|
||||
Weaver.InvokeRpcPrefix + md.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(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(TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
if (Weaver.GenerateLogErrors)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, "Call ClientRpc function " + md.Name));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.logErrorReference));
|
||||
}
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Rpc call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
int channel = clientRpcAttr.GetField("channel", 0);
|
||||
bool excludeOwner = clientRpcAttr.GetField("excludeOwner", false);
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
|
||||
worker.Append(worker.Create(excludeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendRpcInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(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,153 @@
|
||||
// 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(TypeDefinition td)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
modified |= ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
modified |= Process(nested);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return false;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
if (HasServerClientAttribute(md))
|
||||
{
|
||||
Weaver.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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
return ProcessMethodAttributes(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(MethodDefinition md)
|
||||
{
|
||||
if (md.HasCustomAttribute<ServerAttribute>())
|
||||
InjectServerGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||
InjectServerGuard(md, false);
|
||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||
InjectClientGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||
InjectClientGuard(md, false);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectServerGuard(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(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)
|
||||
{
|
||||
TypeReference elementType = param.ParameterType.GetElementType();
|
||||
|
||||
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 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29e4a45f69822462ab0b15adda962a29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// removed 2020-09
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5d8b25543a624384944b599e5a832a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f3445268e45d437fac325837aff3246
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectInitializer
|
||||
{
|
||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
// register syncobject in network behaviour
|
||||
GenerateSyncObjectRegistration(worker, fd);
|
||||
}
|
||||
|
||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
// value types cant inherit from SyncObject
|
||||
if (typeRef.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeRef.Resolve().ImplementsInterface<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;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
this.InitSyncObject(m_sizes);
|
||||
*/
|
||||
static void GenerateSyncObjectRegistration(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.InitSyncObjectReference));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02219b00b3674e59a2151f41e791688
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds SyncObjects fields in a type
|
||||
/// <para>Type should be a NetworkBehaviour</para>
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
|
||||
{
|
||||
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.FieldType.Resolve().ImplementsInterface<SyncObject>())
|
||||
{
|
||||
if (fd.IsStatic)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} cannot be static", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateReadersAndWriters(fd.FieldType);
|
||||
|
||||
syncObjects.Add(fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return syncObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates serialization methods for synclists
|
||||
/// </summary>
|
||||
/// <param name="td">The synclist class</param>
|
||||
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
|
||||
static void GenerateReadersAndWriters(TypeReference tr)
|
||||
{
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||
{
|
||||
if (!argument.IsGenericParameter)
|
||||
{
|
||||
Readers.GetReadFunc(argument);
|
||||
Writers.GetWriteFunc(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr != null)
|
||||
{
|
||||
GenerateReadersAndWriters(tr.Resolve().BaseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
434
Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
Normal file
434
Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
Normal file
@@ -0,0 +1,434 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [SyncVar] in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class SyncVarProcessor
|
||||
{
|
||||
// ulong = 64 bytes
|
||||
const int SyncVarLimit = 64;
|
||||
|
||||
|
||||
static string HookParameterMessage(string hookName, TypeReference ValueType)
|
||||
=> string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType);
|
||||
|
||||
// Get hook method if any
|
||||
public static MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName)
|
||||
{
|
||||
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
|
||||
|
||||
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
||||
|
||||
if (methodsWith2Param.Count == 0)
|
||||
{
|
||||
Weaver.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (MethodDefinition method in methodsWith2Param)
|
||||
{
|
||||
if (MatchesParameters(syncVar, method))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
Weaver.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||
syncVar);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static 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 static 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();
|
||||
|
||||
// [SyncVar] GameObject?
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// return this.GetSyncVarGameObject(ref field, uint netId);
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
// [SyncVar] NetworkIdentity?
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
||||
get.Body.InitLocals = true;
|
||||
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
||||
|
||||
return get;
|
||||
}
|
||||
|
||||
public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
|
||||
{
|
||||
//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();
|
||||
|
||||
// if (!SyncVarEqual(value, ref playerData))
|
||||
Instruction endOfMethod = worker.Create(OpCodes.Nop);
|
||||
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// new value to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
// reference to field to set
|
||||
// make generic version of SetSyncVar with field type
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference));
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference));
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
|
||||
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
|
||||
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm));
|
||||
}
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, endOfMethod));
|
||||
|
||||
// T oldValue = value;
|
||||
// TODO for GO/NI we need to backup the netId don't we?
|
||||
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
|
||||
set.Body.Variables.Add(oldValue);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
worker.Append(worker.Create(OpCodes.Stloc, oldValue));
|
||||
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
|
||||
// new value to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
|
||||
// reference to field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
|
||||
// dirty bit
|
||||
// 8 byte integer aka long
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference));
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference));
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of SetSyncVar with field type
|
||||
GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
|
||||
gm.GenericArguments.Add(fd.FieldType);
|
||||
|
||||
// invoke SetSyncVar
|
||||
worker.Append(worker.Create(OpCodes.Call, gm));
|
||||
}
|
||||
|
||||
MethodDefinition hookMethod = GetHookMethod(td, fd);
|
||||
|
||||
if (hookMethod != null)
|
||||
{
|
||||
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive));
|
||||
worker.Append(worker.Create(OpCodes.Brfalse, label));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarHookGuard));
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, label));
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, true);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
|
||||
|
||||
// call hook (oldValue, newValue)
|
||||
// Generates: OnValueChanged(oldValue, value);
|
||||
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, false);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
|
||||
|
||||
worker.Append(label);
|
||||
}
|
||||
|
||||
worker.Append(endOfMethod);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
||||
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit)
|
||||
{
|
||||
string originalName = fd.Name;
|
||||
Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType);
|
||||
|
||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||
FieldDefinition netIdField = null;
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>() ||
|
||||
fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
|
||||
FieldAttributes.Private,
|
||||
WeaverTypes.Import<uint>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
|
||||
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
||||
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField);
|
||||
|
||||
//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);
|
||||
Weaver.WeaveLists.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.Is<UnityEngine.GameObject>() ||
|
||||
fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
Weaver.WeaveLists.replacementGetterProperties[fd] = get;
|
||||
}
|
||||
}
|
||||
|
||||
public static (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td)
|
||||
{
|
||||
List<FieldDefinition> syncVars = new List<FieldDefinition>();
|
||||
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
||||
|
||||
// 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 = Weaver.WeaveLists.GetSyncVarStart(td.BaseType.FullName);
|
||||
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
{
|
||||
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} cannot be static", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd.FieldType.IsArray)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncVars.Add(fd);
|
||||
|
||||
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
|
||||
dirtyBitCounter += 1;
|
||||
|
||||
if (dirtyBitCounter == SyncVarLimit)
|
||||
{
|
||||
Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all the new SyncVar __netId fields
|
||||
foreach (FieldDefinition fd in syncVarNetIds.Values)
|
||||
{
|
||||
td.Fields.Add(fd);
|
||||
}
|
||||
Weaver.WeaveLists.SetNumSyncVars(td.FullName, syncVars.Count);
|
||||
|
||||
return (syncVars, syncVarNetIds);
|
||||
}
|
||||
|
||||
public static void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
|
||||
{
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, null);
|
||||
}
|
||||
|
||||
public static void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||
{
|
||||
if (newValue == null)
|
||||
{
|
||||
Weaver.Error("NewValue field was null when writing SyncVar hook");
|
||||
}
|
||||
|
||||
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
|
||||
}
|
||||
|
||||
static void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||
{
|
||||
WriteStartFunctionCall();
|
||||
|
||||
// write args
|
||||
WriteOldValue();
|
||||
WriteNewValue();
|
||||
|
||||
WriteEndFunctionCall();
|
||||
|
||||
|
||||
// *** Local functions used to write OpCodes ***
|
||||
// Local functions have access to function variables, no need to pass in args
|
||||
|
||||
void WriteOldValue()
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldloc, oldValue));
|
||||
}
|
||||
|
||||
void WriteNewValue()
|
||||
{
|
||||
// write arg1 or this.field
|
||||
if (newValue == null)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// syncvar.get
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, newValue));
|
||||
}
|
||||
}
|
||||
|
||||
// Writes this before method if it is not static
|
||||
void WriteStartFunctionCall()
|
||||
{
|
||||
// dont add this (Ldarg_0) if method is static
|
||||
if (!hookMethod.IsStatic)
|
||||
{
|
||||
// this before method call
|
||||
// eg this.onValueChanged
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
}
|
||||
}
|
||||
|
||||
// Calls method
|
||||
void WriteEndFunctionCall()
|
||||
{
|
||||
// only use Callvirt when not static
|
||||
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
||||
worker.Append(worker.Create(opcode, hookMethod));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f52c39bddd95d42b88f9cd554dfd9198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
134
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
134
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [TargetRpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class TargetRpcProcessor
|
||||
{
|
||||
// helper functions to check if the method has a NetworkConnection parameter
|
||||
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||
{
|
||||
return md.Parameters.Count > 0 &&
|
||||
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
||||
}
|
||||
|
||||
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
// NetworkConnection parameter is optional
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// if call has NetworkConnection write clients connection as first arg
|
||||
//ClientScene.readyconnection
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.ReadyConnectionReference));
|
||||
}
|
||||
|
||||
// process reader parameters and skip first one if first one is NetworkConnection
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(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(TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the TargetRpc call
|
||||
// (skip first one if first one is NetworkConnection)
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// connection
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// null
|
||||
worker.Append(worker.Create(OpCodes.Ldnull));
|
||||
}
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)));
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(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:
|
||||
1
Assets/Mirror/Editor/Weaver/Program.cs
Normal file
1
Assets/Mirror/Editor/Weaver/Program.cs
Normal file
@@ -0,0 +1 @@
|
||||
// Removed 05/09/20
|
||||
11
Assets/Mirror/Editor/Weaver/Program.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Program.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0152994c9591626408fcfec96fcc7933
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
325
Assets/Mirror/Editor/Weaver/Readers.cs
Normal file
325
Assets/Mirror/Editor/Weaver/Readers.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class Readers
|
||||
{
|
||||
static Dictionary<string, MethodReference> readFuncs;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
readFuncs = new Dictionary<string, MethodReference>();
|
||||
}
|
||||
|
||||
internal static void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
readFuncs[dataType.FullName] = methodReference;
|
||||
}
|
||||
|
||||
public static MethodReference GetReadFunc(TypeReference variableReference)
|
||||
{
|
||||
if (readFuncs.TryGetValue(variableReference.FullName, out MethodReference foundFunc))
|
||||
{
|
||||
return foundFunc;
|
||||
}
|
||||
|
||||
// Arrays are special, if we resolve them, we get teh 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())
|
||||
{
|
||||
Weaver.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
||||
return null;
|
||||
}
|
||||
|
||||
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray));
|
||||
}
|
||||
|
||||
TypeDefinition variableDefinition = variableReference.Resolve();
|
||||
if (variableDefinition == null)
|
||||
{
|
||||
Weaver.Error($"{variableReference.Name} is not a supported type", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.Object>())
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableReference.Is<UnityEngine.ScriptableObject>())
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableReference.IsByReference)
|
||||
{
|
||||
// error??
|
||||
Weaver.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.HasGenericParameters && !variableDefinition.Is(typeof(ArraySegment<>)) && !variableDefinition.Is(typeof(List<>)))
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.IsInterface)
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
if (variableDefinition.IsAbstract)
|
||||
{
|
||||
Weaver.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (variableDefinition.IsEnum)
|
||||
{
|
||||
return GenerateEnumReadFunc(variableReference);
|
||||
}
|
||||
else if (variableDefinition.Is(typeof(ArraySegment<>)))
|
||||
{
|
||||
return GenerateArraySegmentReadFunc(variableReference);
|
||||
}
|
||||
else if (variableDefinition.Is(typeof(List<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList));
|
||||
}
|
||||
|
||||
return GenerateClassOrStructReadFunction(variableReference);
|
||||
}
|
||||
|
||||
static void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
|
||||
{
|
||||
readFuncs[typeReference.FullName] = newReaderFunc;
|
||||
|
||||
Weaver.WeaveLists.generateContainerClass.Methods.Add(newReaderFunc);
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateEnumReadFunc(TypeReference variable)
|
||||
{
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
|
||||
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
|
||||
MethodReference underlyingFunc = GetReadFunc(underlyingType);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, underlyingFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable)
|
||||
{
|
||||
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.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(arrayType)));
|
||||
|
||||
// return new ArraySegment<T>($array);
|
||||
worker.Append(worker.Create(OpCodes.Newobj, WeaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
private static 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,
|
||||
Weaver.CurrentAssembly.MainModule.ImportReference(variable));
|
||||
|
||||
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, WeaverTypes.Import<NetworkReader>()));
|
||||
readerFunc.Body.InitLocals = true;
|
||||
RegisterReadFunc(variable, readerFunc);
|
||||
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction)
|
||||
{
|
||||
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||
// generate readers for the element
|
||||
GetReadFunc(elementType);
|
||||
|
||||
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
|
||||
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
|
||||
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, readerFunction);
|
||||
|
||||
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
|
||||
methodRef.GenericArguments.Add(elementType);
|
||||
|
||||
// generates
|
||||
// return reader.ReadList<T>();
|
||||
|
||||
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0)); // reader
|
||||
worker.Append(worker.Create(OpCodes.Call, methodRef)); // Read
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable)
|
||||
{
|
||||
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);
|
||||
|
||||
CreateNew(variable, worker, td);
|
||||
ReadAllFields(variable, worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
return readerFunc;
|
||||
}
|
||||
|
||||
private static void GenerateNullCheck(ILProcessor worker)
|
||||
{
|
||||
// if (!reader.ReadBoolean()) {
|
||||
// return null;
|
||||
// }
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(WeaverTypes.Import<bool>())));
|
||||
|
||||
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, labelEmptyArray));
|
||||
// return null
|
||||
worker.Append(worker.Create(OpCodes.Ldnull));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
worker.Append(labelEmptyArray);
|
||||
}
|
||||
|
||||
// Initialize the local variable with a new instance
|
||||
static void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td)
|
||||
{
|
||||
if (variable.IsValueType)
|
||||
{
|
||||
// structs are created with Initobj
|
||||
worker.Append(worker.Create(OpCodes.Ldloca, 0));
|
||||
worker.Append(worker.Create(OpCodes.Initobj, variable));
|
||||
}
|
||||
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
|
||||
{
|
||||
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(WeaverTypes.ScriptableObjectCreateInstanceMethod);
|
||||
genericInstanceMethod.GenericArguments.Add(variable);
|
||||
worker.Append(worker.Create(OpCodes.Call, genericInstanceMethod));
|
||||
worker.Append(worker.Create(OpCodes.Stloc_0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// classes are created with their constructor
|
||||
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
|
||||
if (ctor == null)
|
||||
{
|
||||
Weaver.Error($"{variable.Name} can't be deserialized because it has no default constructor", variable);
|
||||
return;
|
||||
}
|
||||
|
||||
MethodReference ctorRef = Weaver.CurrentAssembly.MainModule.ImportReference(ctor);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Newobj, ctorRef));
|
||||
worker.Append(worker.Create(OpCodes.Stloc_0));
|
||||
}
|
||||
}
|
||||
|
||||
static void ReadAllFields(TypeReference variable, ILProcessor worker)
|
||||
{
|
||||
uint fields = 0;
|
||||
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.Append(worker.Create(opcode, 0));
|
||||
|
||||
MethodReference readFunc = GetReadFunc(field.FieldType);
|
||||
if (readFunc != null)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, readFunc));
|
||||
}
|
||||
else
|
||||
{
|
||||
Weaver.Error($"{field.Name} has an unsupported type", field);
|
||||
}
|
||||
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Stfld, fieldRef));
|
||||
fields++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a delegate for each one of the readers into <see cref="Reader{T}.read"/>
|
||||
/// </summary>
|
||||
/// <param name="worker"></param>
|
||||
internal static void InitializeReaders(ILProcessor worker)
|
||||
{
|
||||
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
|
||||
|
||||
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
|
||||
|
||||
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
|
||||
FieldReference fieldRef = module.ImportReference(fieldInfo);
|
||||
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
|
||||
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
|
||||
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
|
||||
|
||||
foreach (MethodReference readFunc in readFuncs.Values)
|
||||
{
|
||||
TypeReference dataType = readFunc.ReturnType;
|
||||
|
||||
// create a Func<NetworkReader, T> delegate
|
||||
worker.Append(worker.Create(OpCodes.Ldnull));
|
||||
worker.Append(worker.Create(OpCodes.Ldftn, readFunc));
|
||||
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, dataType);
|
||||
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(funcGenericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Newobj, funcConstructorInstance));
|
||||
|
||||
// save it in Writer<T>.write
|
||||
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(dataType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
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:
|
||||
86
Assets/Mirror/Editor/Weaver/Resolvers.cs
Normal file
86
Assets/Mirror/Editor/Weaver/Resolvers.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 scriptDef, string name)
|
||||
{
|
||||
if (tr == null)
|
||||
{
|
||||
Weaver.Error($"Cannot resolve method {name} without a class");
|
||||
return null;
|
||||
}
|
||||
MethodReference method = ResolveMethod(tr, scriptDef, m => m.Name == name);
|
||||
if (method == null)
|
||||
{
|
||||
Weaver.Error($"Method not found with name {name} in type {tr.Name}", tr);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition scriptDef, System.Func<MethodDefinition, bool> predicate)
|
||||
{
|
||||
foreach (MethodDefinition methodRef in t.Resolve().Methods)
|
||||
{
|
||||
if (predicate(methodRef))
|
||||
{
|
||||
return scriptDef.MainModule.ImportReference(methodRef);
|
||||
}
|
||||
}
|
||||
|
||||
Weaver.Error($"Method not found in type {t.Name}", t);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition scriptDef, string name)
|
||||
{
|
||||
if (tr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
|
||||
{
|
||||
if (methodRef.Name == name)
|
||||
{
|
||||
return scriptDef.MainModule.ImportReference(methodRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find the method in this class, try the parent
|
||||
return TryResolveMethodInParents(tr.Resolve().BaseType, scriptDef, 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 scriptDef, string name)
|
||||
{
|
||||
foreach (PropertyDefinition pd in tr.Resolve().Properties)
|
||||
{
|
||||
if (pd.Name == name)
|
||||
{
|
||||
return scriptDef.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:
|
||||
232
Assets/Mirror/Editor/Weaver/Weaver.cs
Normal file
232
Assets/Mirror/Editor/Weaver/Weaver.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
// This data is flushed each time - if we are run multiple times in the same process/domain
|
||||
class WeaverLists
|
||||
{
|
||||
// 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>();
|
||||
|
||||
public TypeDefinition generateContainerClass;
|
||||
|
||||
// amount of SyncVars per class. dict<className, amount>
|
||||
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
|
||||
|
||||
public int GetSyncVarStart(string className)
|
||||
{
|
||||
return numSyncVars.ContainsKey(className)
|
||||
? numSyncVars[className]
|
||||
: 0;
|
||||
}
|
||||
|
||||
public void SetNumSyncVars(string className, int num)
|
||||
{
|
||||
numSyncVars[className] = num;
|
||||
}
|
||||
|
||||
public WeaverLists()
|
||||
{
|
||||
generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode",
|
||||
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
|
||||
WeaverTypes.Import<object>());
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Weaver
|
||||
{
|
||||
public static string InvokeRpcPrefix => "InvokeUserCode_";
|
||||
|
||||
public static WeaverLists WeaveLists { get; private set; }
|
||||
public static AssemblyDefinition CurrentAssembly { get; private set; }
|
||||
public static bool WeavingFailed { get; private set; }
|
||||
public static bool GenerateLogErrors { get; set; }
|
||||
|
||||
// private properties
|
||||
static readonly bool DebugLogEnabled = true;
|
||||
|
||||
public static void DLog(TypeDefinition td, string fmt, params object[] args)
|
||||
{
|
||||
if (!DebugLogEnabled)
|
||||
return;
|
||||
|
||||
Console.WriteLine("[" + td.Name + "] " + string.Format(fmt, args));
|
||||
}
|
||||
|
||||
// display weaver error
|
||||
// and mark process as failed
|
||||
public static void Error(string message)
|
||||
{
|
||||
Log.Error(message);
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
public static void Error(string message, MemberReference mr)
|
||||
{
|
||||
Log.Error($"{message} (at {mr})");
|
||||
WeavingFailed = true;
|
||||
}
|
||||
|
||||
public static void Warning(string message, MemberReference mr)
|
||||
{
|
||||
Log.Warning($"{message} (at {mr})");
|
||||
}
|
||||
|
||||
|
||||
static void CheckMonoBehaviour(TypeDefinition td)
|
||||
{
|
||||
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
|
||||
{
|
||||
MonoBehaviourProcessor.Process(td);
|
||||
}
|
||||
}
|
||||
|
||||
static bool WeaveNetworkBehavior(TypeDefinition td)
|
||||
{
|
||||
if (!td.IsClass)
|
||||
return false;
|
||||
|
||||
if (!td.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
CheckMonoBehaviour(td);
|
||||
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(behaviour).Process();
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool WeaveModule(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
watch.Start();
|
||||
foreach (TypeDefinition td in moduleDefinition.Types)
|
||||
{
|
||||
if (td.IsClass && td.BaseType.CanBeResolved())
|
||||
{
|
||||
modified |= WeaveNetworkBehavior(td);
|
||||
modified |= ServerClientAttributeProcessor.Process(td);
|
||||
}
|
||||
}
|
||||
watch.Stop();
|
||||
Console.WriteLine("Weave behaviours and messages took" + watch.ElapsedMilliseconds + " milliseconds");
|
||||
|
||||
return modified;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error(ex.ToString());
|
||||
throw new Exception(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
static bool Weave(string assName, IEnumerable<string> dependencies)
|
||||
{
|
||||
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
|
||||
using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
|
||||
{
|
||||
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName));
|
||||
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
|
||||
if (dependencies != null)
|
||||
{
|
||||
foreach (string path in dependencies)
|
||||
{
|
||||
asmResolver.AddSearchDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
WeaverTypes.SetupTargetTypes(CurrentAssembly);
|
||||
// WeaverList depends on WeaverTypes setup because it uses Import
|
||||
WeaveLists = new WeaverLists();
|
||||
|
||||
|
||||
System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
|
||||
bool modified = ReaderWriterProcessor.Process(CurrentAssembly);
|
||||
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)
|
||||
{
|
||||
PropertySiteProcessor.Process(moduleDefinition);
|
||||
|
||||
// add class that holds read/write functions
|
||||
moduleDefinition.Types.Add(WeaveLists.generateContainerClass);
|
||||
|
||||
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly);
|
||||
|
||||
// write to outputDir if specified, otherwise perform in-place write
|
||||
WriterParameters writeParams = new WriterParameters { WriteSymbols = true };
|
||||
CurrentAssembly.Write(writeParams);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool WeaveAssembly(string assembly, IEnumerable<string> dependencies)
|
||||
{
|
||||
WeavingFailed = false;
|
||||
|
||||
try
|
||||
{
|
||||
return Weave(assembly, dependencies);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Exception :" + e);
|
||||
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:
|
||||
25
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs
Normal file
25
Assets/Mirror/Editor/Weaver/WeaverExceptions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
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(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GenerateWriterException : WeaverException
|
||||
{
|
||||
public GenerateWriterException(string message, MemberReference member) : base(message, member) { }
|
||||
protected GenerateWriterException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.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:
|
||||
132
Assets/Mirror/Editor/Weaver/WeaverTypes.cs
Normal file
132
Assets/Mirror/Editor/Weaver/WeaverTypes.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class WeaverTypes
|
||||
{
|
||||
public static MethodReference ScriptableObjectCreateInstanceMethod;
|
||||
|
||||
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
||||
public static MethodReference GetPooledWriterReference;
|
||||
public static MethodReference RecycleWriterReference;
|
||||
|
||||
public static MethodReference ReadyConnectionReference;
|
||||
|
||||
public static MethodReference CmdDelegateConstructor;
|
||||
|
||||
public static MethodReference NetworkServerGetActive;
|
||||
public static MethodReference NetworkServerGetLocalClientActive;
|
||||
public static MethodReference NetworkClientGetActive;
|
||||
|
||||
// custom attribute types
|
||||
public static MethodReference InitSyncObjectReference;
|
||||
|
||||
// array segment
|
||||
public static MethodReference ArraySegmentConstructorReference;
|
||||
public static MethodReference ArraySegmentArrayReference;
|
||||
public static MethodReference ArraySegmentOffsetReference;
|
||||
public static MethodReference ArraySegmentCountReference;
|
||||
|
||||
// list
|
||||
public static MethodReference ListConstructorReference;
|
||||
public static MethodReference ListCountReference;
|
||||
public static MethodReference ListGetItemReference;
|
||||
public static MethodReference ListAddReference;
|
||||
|
||||
public static MethodReference syncVarEqualReference;
|
||||
public static MethodReference syncVarNetworkIdentityEqualReference;
|
||||
public static MethodReference syncVarGameObjectEqualReference;
|
||||
public static MethodReference setSyncVarReference;
|
||||
public static MethodReference setSyncVarHookGuard;
|
||||
public static MethodReference getSyncVarHookGuard;
|
||||
public static MethodReference setSyncVarGameObjectReference;
|
||||
public static MethodReference getSyncVarGameObjectReference;
|
||||
public static MethodReference setSyncVarNetworkIdentityReference;
|
||||
public static MethodReference getSyncVarNetworkIdentityReference;
|
||||
public static MethodReference registerCommandDelegateReference;
|
||||
public static MethodReference registerRpcDelegateReference;
|
||||
public static MethodReference getTypeReference;
|
||||
public static MethodReference getTypeFromHandleReference;
|
||||
public static MethodReference logErrorReference;
|
||||
public static MethodReference logWarningReference;
|
||||
public static MethodReference sendCommandInternal;
|
||||
public static MethodReference sendRpcInternal;
|
||||
public static MethodReference sendTargetRpcInternal;
|
||||
|
||||
private static AssemblyDefinition currentAssembly;
|
||||
|
||||
public static TypeReference Import<T>() => Import(typeof(T));
|
||||
|
||||
public static TypeReference Import(Type t) => currentAssembly.MainModule.ImportReference(t);
|
||||
|
||||
public static void SetupTargetTypes(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
// system types
|
||||
WeaverTypes.currentAssembly = currentAssembly;
|
||||
|
||||
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
|
||||
ArraySegmentArrayReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Array");
|
||||
ArraySegmentCountReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Count");
|
||||
ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, currentAssembly, "Offset");
|
||||
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, currentAssembly, ".ctor");
|
||||
|
||||
TypeReference ListType = Import(typeof(System.Collections.Generic.List<>));
|
||||
ListCountReference = Resolvers.ResolveProperty(ListType, currentAssembly, "Count");
|
||||
ListGetItemReference = Resolvers.ResolveMethod(ListType, currentAssembly, "get_Item");
|
||||
ListAddReference = Resolvers.ResolveMethod(ListType, currentAssembly, "Add");
|
||||
ListConstructorReference = Resolvers.ResolveMethod(ListType, currentAssembly, ".ctor");
|
||||
|
||||
TypeReference NetworkServerType = Import(typeof(NetworkServer));
|
||||
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_active");
|
||||
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, currentAssembly, "get_localClientActive");
|
||||
TypeReference NetworkClientType = Import(typeof(NetworkClient));
|
||||
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, currentAssembly, "get_active");
|
||||
|
||||
TypeReference cmdDelegateReference = Import<RemoteCalls.CmdDelegate>();
|
||||
CmdDelegateConstructor = Resolvers.ResolveMethod(cmdDelegateReference, currentAssembly, ".ctor");
|
||||
|
||||
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
|
||||
TypeReference RemoteCallHelperType = Import(typeof(RemoteCalls.RemoteCallHelper));
|
||||
|
||||
TypeReference ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
|
||||
|
||||
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
|
||||
ScriptableObjectType, currentAssembly,
|
||||
md => md.Name == "CreateInstance" && md.HasGenericParameters);
|
||||
|
||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, currentAssembly, "syncVarDirtyBits");
|
||||
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
|
||||
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "GetWriter");
|
||||
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, currentAssembly, "Recycle");
|
||||
|
||||
TypeReference ClientSceneType = Import(typeof(ClientScene));
|
||||
ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, currentAssembly, "get_readyConnection");
|
||||
|
||||
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarEqual");
|
||||
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarNetworkIdentityEqual");
|
||||
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarGameObjectEqual");
|
||||
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVar");
|
||||
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "setSyncVarHookGuard");
|
||||
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "getSyncVarHookGuard");
|
||||
|
||||
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarGameObject");
|
||||
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarGameObject");
|
||||
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkIdentity");
|
||||
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkIdentity");
|
||||
registerCommandDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterCommandDelegate");
|
||||
registerRpcDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterRpcDelegate");
|
||||
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
|
||||
logErrorReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, "LogError");
|
||||
logWarningReference = Resolvers.ResolveMethod(unityDebug, currentAssembly, "LogWarning");
|
||||
|
||||
TypeReference typeType = Import(typeof(Type));
|
||||
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, currentAssembly, "GetTypeFromHandle");
|
||||
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendCommandInternal");
|
||||
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendRPCInternal");
|
||||
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SendTargetRPCInternal");
|
||||
|
||||
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "InitSyncObject");
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
309
Assets/Mirror/Editor/Weaver/Writers.cs
Normal file
309
Assets/Mirror/Editor/Weaver/Writers.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using Mono.CecilX.Rocks;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class Writers
|
||||
{
|
||||
static Dictionary<string, MethodReference> writeFuncs;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
writeFuncs = new Dictionary<string, MethodReference>();
|
||||
}
|
||||
|
||||
public static void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
writeFuncs[dataType.FullName] = methodReference;
|
||||
}
|
||||
|
||||
static void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
|
||||
{
|
||||
writeFuncs[typeReference.FullName] = newWriterFunc;
|
||||
|
||||
Weaver.WeaveLists.generateContainerClass.Methods.Add(newWriterFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds existing writer for type, if non exists trys to create one
|
||||
/// <para>This method is recursive</para>
|
||||
/// </summary>
|
||||
/// <param name="variable"></param>
|
||||
/// <param name="recursionCount"></param>
|
||||
/// <returns>Returns <see cref="MethodReference"/> or null</returns>
|
||||
public static MethodReference GetWriteFunc(TypeReference variable)
|
||||
{
|
||||
if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
|
||||
{
|
||||
return foundFunc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
|
||||
try
|
||||
{
|
||||
return GenerateWriter(variable);
|
||||
}
|
||||
catch (GenerateWriterException e)
|
||||
{
|
||||
Weaver.Error(e.Message, e.MemberReference);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="GenerateWriterException">Throws when writer could not be generated for type</exception>
|
||||
static MethodDefinition GenerateWriter(TypeReference variableReference)
|
||||
{
|
||||
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,
|
||||
// eg 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));
|
||||
}
|
||||
|
||||
if (variableReference.Resolve()?.IsEnum ?? false)
|
||||
{
|
||||
// serialize enum as their base type
|
||||
return GenerateEnumWriteFunc(variableReference);
|
||||
}
|
||||
|
||||
// check for collections
|
||||
if (variableReference.Is(typeof(ArraySegment<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment));
|
||||
}
|
||||
if (variableReference.Is(typeof(List<>)))
|
||||
{
|
||||
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||
|
||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private static MethodDefinition GenerateEnumWriteFunc(TypeReference variable)
|
||||
{
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
|
||||
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType());
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
worker.Append(worker.Create(OpCodes.Call, underlyingWriter));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
private static 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, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
|
||||
writerFunc.Body.InitLocals = true;
|
||||
|
||||
RegisterWriteFunc(variable, writerFunc);
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable)
|
||||
{
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
|
||||
if (!variable.Resolve().IsValueType)
|
||||
WriteNullCheck(worker);
|
||||
|
||||
if (!WriteAllFields(variable, worker))
|
||||
return null;
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
private static void WriteNullCheck(ILProcessor worker)
|
||||
{
|
||||
// if (value == null)
|
||||
// {
|
||||
// writer.WriteBoolean(false);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
|
||||
Instruction labelNotNull = worker.Create(OpCodes.Nop);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, labelNotNull));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>())));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
worker.Append(labelNotNull);
|
||||
|
||||
// write.WriteBoolean(true);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
|
||||
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(WeaverTypes.Import<bool>())));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all fields in type and write them
|
||||
/// </summary>
|
||||
/// <param name="variable"></param>
|
||||
/// <param name="worker"></param>
|
||||
/// <returns>false if fail</returns>
|
||||
static bool WriteAllFields(TypeReference variable, ILProcessor worker)
|
||||
{
|
||||
uint fields = 0;
|
||||
foreach (FieldDefinition field in variable.FindAllPublicFields())
|
||||
{
|
||||
MethodReference writeFunc = GetWriteFunc(field.FieldType);
|
||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
||||
if (writeFunc == null) { return false; }
|
||||
|
||||
FieldReference fieldRef = Weaver.CurrentAssembly.MainModule.ImportReference(field);
|
||||
|
||||
fields++;
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fieldRef));
|
||||
worker.Append(worker.Create(OpCodes.Call, writeFunc));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction)
|
||||
{
|
||||
|
||||
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||
|
||||
MethodReference elementWriteFunc = GetWriteFunc(elementType);
|
||||
MethodReference intWriterFunc = GetWriteFunc(WeaverTypes.Import<int>());
|
||||
|
||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
||||
if (elementWriteFunc == null)
|
||||
{
|
||||
Weaver.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
|
||||
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
|
||||
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, Weaver.CurrentAssembly, writerFunction);
|
||||
|
||||
GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
|
||||
methodRef.GenericArguments.Add(elementType);
|
||||
|
||||
// generates
|
||||
// reader.WriteArray<T>(array);
|
||||
|
||||
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0)); // writer
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1)); // collection
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, methodRef)); // WriteArray
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
return writerFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a delegate for each one of the writers into <see cref="Writer{T}.write"/>
|
||||
/// </summary>
|
||||
/// <param name="worker"></param>
|
||||
internal static void InitializeWriters(ILProcessor worker)
|
||||
{
|
||||
ModuleDefinition module = Weaver.CurrentAssembly.MainModule;
|
||||
|
||||
TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
|
||||
|
||||
System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
|
||||
FieldReference fieldRef = module.ImportReference(fieldInfo);
|
||||
TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
|
||||
TypeReference actionRef = module.ImportReference(typeof(Action<,>));
|
||||
MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
|
||||
|
||||
foreach (MethodReference writerMethod in writeFuncs.Values)
|
||||
{
|
||||
|
||||
TypeReference dataType = writerMethod.Parameters[1].ParameterType;
|
||||
|
||||
// create a Action<NetworkWriter, T> delegate
|
||||
worker.Append(worker.Create(OpCodes.Ldnull));
|
||||
worker.Append(worker.Create(OpCodes.Ldftn, writerMethod));
|
||||
GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, dataType);
|
||||
MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(actionGenericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Newobj, actionRefInstance));
|
||||
|
||||
// save it in Writer<T>.write
|
||||
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(dataType);
|
||||
FieldReference specializedField = fieldRef.SpecializeField(genericInstance);
|
||||
worker.Append(worker.Create(OpCodes.Stsfld, specializedField));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Weaver/Writers.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Writers.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a90060ad76ea044aba613080dd922709
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user