1
Fork 0
mirror of https://github.com/Steffo99/better-tee.git synced 2024-11-21 23:04:19 +00:00

Readd Mirror 😅

This commit is contained in:
Steffo 2019-09-17 17:43:32 +02:00
parent 69ba96c168
commit bcaf8d26c7
280 changed files with 24425 additions and 3 deletions

View file

@ -1,4 +1,4 @@
using Telepathy; using Mirror;
namespace NetMessage namespace NetMessage

8
Assets/Packages.meta Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2723bee01450514382d1e32aa01f968
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5cf8eb36be0834b3da408c694a41cb88
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "Mirror.Components",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 72872094b21c16e48b631b2224833d49
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,427 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Mirror
{
/// <summary>
/// A component to synchronize Mecanim animation states for networked objects.
/// </summary>
/// <remarks>
/// <para>The animation of game objects can be networked by this component. There are two models of authority for networked movement:</para>
/// <para>If the object has authority on the client, then it should animated locally on the owning client. The animation state information will be sent from the owning client to the server, then broadcast to all of the other clients. This is common for player objects.</para>
/// <para>If the object has authority on the server, then it should be animated on the server and state information will be sent to all clients. This is common for objects not related to a specific client, such as an enemy unit.</para>
/// <para>The NetworkAnimator synchronizes the animation parameters that are checked in the inspector view. It does not automatically sychronize triggers. The function SetTrigger can by used by an object with authority to fire an animation trigger on other clients.</para>
/// </remarks>
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkAnimator")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkAnimator.html")]
public class NetworkAnimator : NetworkBehaviour
{
/// <summary>
/// The animator component to synchronize.
/// </summary>
[FormerlySerializedAs("m_Animator")]
public Animator animator;
// Note: not an object[] array because otherwise initialization is real annoying
int[] lastIntParameters;
float[] lastFloatParameters;
bool[] lastBoolParameters;
AnimatorControllerParameter[] parameters;
int[] animationHash; // multiple layers
int[] transitionHash;
float sendTimer;
bool sendMessagesAllowed
{
get
{
if (isServer)
{
if (!localPlayerAuthority)
return true;
// This is a special case where we have localPlayerAuthority set
// on a NetworkIdentity but we have not assigned the client who has
// authority over it, no animator data will be sent over the network by the server.
//
// So we check here for a clientAuthorityOwner and if it is null we will
// let the server send animation data until we receive an owner.
if (netIdentity != null && netIdentity.clientAuthorityOwner == null)
return true;
}
return hasAuthority;
}
}
void Awake()
{
// store the animator parameters in a variable - the "Animator.parameters" getter allocates
// a new parameter array every time it is accessed so we should avoid doing it in a loop
parameters = animator.parameters;
lastIntParameters = new int[parameters.Length];
lastFloatParameters = new float[parameters.Length];
lastBoolParameters = new bool[parameters.Length];
animationHash = new int[animator.layerCount];
transitionHash = new int[animator.layerCount];
}
void FixedUpdate()
{
if (!sendMessagesAllowed)
return;
CheckSendRate();
for(int i = 0; i < animator.layerCount; i++)
{
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, i))
{
continue;
}
NetworkWriter writer = new NetworkWriter();
WriteParameters(writer);
SendAnimationMessage(stateHash, normalizedTime, i, writer.ToArray());
}
}
bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layerId)
{
stateHash = 0;
normalizedTime = 0;
if (animator.IsInTransition(layerId))
{
AnimatorTransitionInfo tt = animator.GetAnimatorTransitionInfo(layerId);
if (tt.fullPathHash != transitionHash[layerId])
{
// first time in this transition
transitionHash[layerId] = tt.fullPathHash;
animationHash[layerId] = 0;
return true;
}
return false;
}
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(layerId);
if (st.fullPathHash != animationHash[layerId])
{
// first time in this animation state
if (animationHash[layerId] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
transitionHash[layerId] = 0;
animationHash[layerId] = st.fullPathHash;
return true;
}
return false;
}
void CheckSendRate()
{
if (sendMessagesAllowed && syncInterval != 0 && sendTimer < Time.time)
{
sendTimer = Time.time + syncInterval;
NetworkWriter writer = new NetworkWriter();
if (WriteParameters(writer))
{
SendAnimationParametersMessage(writer.ToArray());
}
}
}
void SendAnimationMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
if (isServer)
{
RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
}
else if (ClientScene.readyConnection != null)
{
CmdOnAnimationServerMessage(stateHash, normalizedTime, layerId, parameters);
}
}
void SendAnimationParametersMessage(byte[] parameters)
{
if (isServer)
{
RpcOnAnimationParametersClientMessage(parameters);
}
else if (ClientScene.readyConnection != null)
{
CmdOnAnimationParametersServerMessage(parameters);
}
}
void HandleAnimMsg(int stateHash, float normalizedTime, int layerId, NetworkReader reader)
{
if (hasAuthority)
return;
// usually transitions will be triggered by parameters, if not, play anims directly.
// NOTE: this plays "animations", not transitions, so any transitions will be skipped.
// NOTE: there is no API to play a transition(?)
if (stateHash != 0)
{
animator.Play(stateHash, layerId, normalizedTime);
}
ReadParameters(reader);
}
void HandleAnimParamsMsg(NetworkReader reader)
{
if (hasAuthority)
return;
ReadParameters(reader);
}
void HandleAnimTriggerMsg(int hash)
{
animator.SetTrigger(hash);
}
ulong NextDirtyBits()
{
ulong dirtyBits = 0;
for (int i = 0; i < parameters.Length; i++)
{
AnimatorControllerParameter par = parameters[i];
bool changed = false;
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = animator.GetInteger(par.nameHash);
changed = newIntValue != lastIntParameters[i];
if (changed)
{
lastIntParameters[i] = newIntValue;
}
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = animator.GetFloat(par.nameHash);
changed = Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f;
if (changed)
{
lastFloatParameters[i] = newFloatValue;
}
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = animator.GetBool(par.nameHash);
changed = newBoolValue != lastBoolParameters[i];
if (changed)
{
lastBoolParameters[i] = newBoolValue;
}
}
if (changed)
{
dirtyBits |= 1ul << i;
}
}
return dirtyBits;
}
bool WriteParameters(NetworkWriter writer)
{
ulong dirtyBits = NextDirtyBits();
writer.WritePackedUInt64(dirtyBits);
for (int i = 0; i < parameters.Length; i++)
{
if ((dirtyBits & (1ul << i)) == 0)
continue;
AnimatorControllerParameter par = parameters[i];
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = animator.GetInteger(par.nameHash);
writer.WritePackedInt32(newIntValue);
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = animator.GetFloat(par.nameHash);
writer.WriteSingle(newFloatValue);
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = animator.GetBool(par.nameHash);
writer.WriteBoolean(newBoolValue);
}
}
return dirtyBits != 0;
}
void ReadParameters(NetworkReader reader)
{
ulong dirtyBits = reader.ReadPackedUInt64();
for (int i = 0; i < parameters.Length; i++)
{
if ((dirtyBits & (1ul << i)) == 0)
continue;
AnimatorControllerParameter par = parameters[i];
if (par.type == AnimatorControllerParameterType.Int)
{
int newIntValue = reader.ReadPackedInt32();
animator.SetInteger(par.nameHash, newIntValue);
}
else if (par.type == AnimatorControllerParameterType.Float)
{
float newFloatValue = reader.ReadSingle();
animator.SetFloat(par.nameHash, newFloatValue);
}
else if (par.type == AnimatorControllerParameterType.Bool)
{
bool newBoolValue = reader.ReadBoolean();
animator.SetBool(par.nameHash, newBoolValue);
}
}
}
/// <summary>
/// Custom Serialization
/// </summary>
/// <param name="writer"></param>
/// <param name="forceAll"></param>
/// <returns></returns>
public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
if (forceAll)
{
for(int i = 0; i < animator.layerCount; i++)
{
if (animator.IsInTransition(i))
{
AnimatorStateInfo st = animator.GetNextAnimatorStateInfo(i);
writer.WriteInt32(st.fullPathHash);
writer.WriteSingle(st.normalizedTime);
}
else
{
AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(i);
writer.WriteInt32(st.fullPathHash);
writer.WriteSingle(st.normalizedTime);
}
}
WriteParameters(writer);
return true;
}
return false;
}
/// <summary>
/// Custom Deserialization
/// </summary>
/// <param name="reader"></param>
/// <param name="initialState"></param>
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
for(int i = 0; i < animator.layerCount; i++)
{
int stateHash = reader.ReadInt32();
float normalizedTime = reader.ReadSingle();
animator.Play(stateHash, i, normalizedTime);
}
ReadParameters(reader);
}
}
/// <summary>
/// Causes an animation trigger to be invoked for a networked object.
/// <para>If local authority is set, and this is called from the client, then the trigger will be invoked on the server and all clients. If not, then this is called on the server, and the trigger will be called on all clients.</para>
/// </summary>
/// <param name="triggerName">Name of trigger.</param>
public void SetTrigger(string triggerName)
{
SetTrigger(Animator.StringToHash(triggerName));
}
/// <summary>
/// Causes an animation trigger to be invoked for a networked object.
/// </summary>
/// <param name="hash">Hash id of trigger (from the Animator).</param>
public void SetTrigger(int hash)
{
if (hasAuthority && localPlayerAuthority)
{
if (ClientScene.readyConnection != null)
{
CmdOnAnimationTriggerServerMessage(hash);
}
return;
}
if (isServer && !localPlayerAuthority)
{
RpcOnAnimationTriggerClientMessage(hash);
}
}
#region server message handlers
[Command]
void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
if (LogFilter.Debug) Debug.Log("OnAnimationMessage for netId=" + netId);
// handle and broadcast
HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
}
[Command]
void CmdOnAnimationParametersServerMessage(byte[] parameters)
{
// handle and broadcast
HandleAnimParamsMsg(new NetworkReader(parameters));
RpcOnAnimationParametersClientMessage(parameters);
}
[Command]
void CmdOnAnimationTriggerServerMessage(int hash)
{
// handle and broadcast
HandleAnimTriggerMsg(hash);
RpcOnAnimationTriggerClientMessage(hash);
}
#endregion
#region client message handlers
[ClientRpc]
void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
{
HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
}
[ClientRpc]
void RpcOnAnimationParametersClientMessage(byte[] parameters)
{
HandleAnimParamsMsg(new NetworkReader(parameters));
}
// server sends this to one client
[ClientRpc]
void RpcOnAnimationTriggerClientMessage(int hash)
{
HandleAnimTriggerMsg(hash);
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f6f3bf89aa97405989c802ba270f815
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,659 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
namespace Mirror
{
/// <summary>
/// This is a specialized NetworkManager that includes a networked lobby.
/// </summary>
/// <remarks>
/// <para>The lobby has slots that track the joined players, and a maximum player count that is enforced. It requires that the NetworkLobbyPlayer component be on the lobby player objects.</para>
/// <para>NetworkLobbyManager is derived from NetworkManager, and so it implements many of the virtual functions provided by the NetworkManager class. To avoid accidentally replacing functionality of the NetworkLobbyManager, there are new virtual functions on the NetworkLobbyManager that begin with "OnLobby". These should be used on classes derived from NetworkLobbyManager instead of the virtual functions on NetworkManager.</para>
/// <para>The OnLobby*() functions have empty implementations on the NetworkLobbyManager base class, so the base class functions do not have to be called.</para>
/// </remarks>
[AddComponentMenu("Network/NetworkLobbyManager")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyManager.html")]
public class NetworkLobbyManager : NetworkManager
{
public struct PendingPlayer
{
public NetworkConnection conn;
public GameObject lobbyPlayer;
}
[Header("Lobby Settings")]
[FormerlySerializedAs("m_ShowLobbyGUI")]
[SerializeField]
internal bool showLobbyGUI = true;
[FormerlySerializedAs("m_MinPlayers")]
[SerializeField]
int minPlayers = 1;
[FormerlySerializedAs("m_LobbyPlayerPrefab")]
[SerializeField]
NetworkLobbyPlayer lobbyPlayerPrefab;
/// <summary>
/// The scene to use for the lobby. This is similar to the offlineScene of the NetworkManager.
/// </summary>
[Scene]
public string LobbyScene;
/// <summary>
/// The scene to use for the playing the game from the lobby. This is similar to the onlineScene of the NetworkManager.
/// </summary>
[Scene]
public string GameplayScene;
/// <summary>
/// List of players that are in the Lobby
/// </summary>
[FormerlySerializedAs("m_PendingPlayers")]
public List<PendingPlayer> pendingPlayers = new List<PendingPlayer>();
/// <summary>
/// These slots track players that enter the lobby.
/// <para>The slotId on players is global to the game - across all players.</para>
/// </summary>
public List<NetworkLobbyPlayer> lobbySlots = new List<NetworkLobbyPlayer>();
/// <summary>
/// True when all players have submitted a Ready message
/// </summary>
public bool allPlayersReady;
public override void OnValidate()
{
// always >= 0
maxConnections = Mathf.Max(maxConnections, 0);
// always <= maxConnections
minPlayers = Mathf.Min(minPlayers, maxConnections);
// always >= 0
minPlayers = Mathf.Max(minPlayers, 0);
if (lobbyPlayerPrefab != null)
{
NetworkIdentity identity = lobbyPlayerPrefab.GetComponent<NetworkIdentity>();
if (identity == null)
{
lobbyPlayerPrefab = null;
Debug.LogError("LobbyPlayer prefab must have a NetworkIdentity component.");
}
}
base.OnValidate();
}
internal void ReadyStatusChanged()
{
int CurrentPlayers = 0;
int ReadyPlayers = 0;
foreach (NetworkLobbyPlayer item in lobbySlots)
{
if (item != null)
{
CurrentPlayers++;
if (item.readyToBegin)
ReadyPlayers++;
}
}
if (CurrentPlayers == ReadyPlayers)
CheckReadyToBegin();
else
allPlayersReady = false;
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerReady(NetworkConnection conn)
{
if (LogFilter.Debug) Debug.Log("NetworkLobbyManager OnServerReady");
base.OnServerReady(conn);
if (conn != null && conn.playerController != null)
{
GameObject lobbyPlayer = conn.playerController.gameObject;
// if null or not a lobby player, dont replace it
if (lobbyPlayer != null && lobbyPlayer.GetComponent<NetworkLobbyPlayer>() != null)
SceneLoadedForPlayer(conn, lobbyPlayer);
}
}
void SceneLoadedForPlayer(NetworkConnection conn, GameObject lobbyPlayer)
{
if (LogFilter.Debug) Debug.LogFormat("NetworkLobby SceneLoadedForPlayer scene: {0} {1}", SceneManager.GetActiveScene().name, conn);
if (SceneManager.GetActiveScene().name == LobbyScene)
{
// cant be ready in lobby, add to ready list
PendingPlayer pending;
pending.conn = conn;
pending.lobbyPlayer = lobbyPlayer;
pendingPlayers.Add(pending);
return;
}
GameObject gamePlayer = OnLobbyServerCreateGamePlayer(conn);
if (gamePlayer == null)
{
// get start position from base class
Transform startPos = GetStartPosition();
gamePlayer = startPos != null
? Instantiate(playerPrefab, startPos.position, startPos.rotation)
: Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
gamePlayer.name = playerPrefab.name;
}
if (!OnLobbyServerSceneLoadedForPlayer(lobbyPlayer, gamePlayer))
return;
// replace lobby player with game player
NetworkServer.ReplacePlayerForConnection(conn, gamePlayer);
}
/// <summary>
/// CheckReadyToBegin checks all of the players in the lobby to see if their readyToBegin flag is set.
/// <para>If all of the players are ready, then the server switches from the LobbyScene to the PlayScene - essentially starting the game. This is called automatically in response to NetworkLobbyPlayer.SendReadyToBeginMessage().</para>
/// </summary>
public void CheckReadyToBegin()
{
if (SceneManager.GetActiveScene().name != LobbyScene) return;
if (minPlayers > 0 && NetworkServer.connections.Count(conn => conn.Value != null && conn.Value.playerController.gameObject.GetComponent<NetworkLobbyPlayer>().readyToBegin) < minPlayers)
{
allPlayersReady = false;
return;
}
pendingPlayers.Clear();
allPlayersReady = true;
OnLobbyServerPlayersReady();
}
void CallOnClientEnterLobby()
{
OnLobbyClientEnter();
foreach (NetworkLobbyPlayer player in lobbySlots)
if (player != null)
{
player.OnClientEnterLobby();
}
}
void CallOnClientExitLobby()
{
OnLobbyClientExit();
foreach (NetworkLobbyPlayer player in lobbySlots)
if (player != null)
{
player.OnClientExitLobby();
}
}
#region server handlers
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerConnect(NetworkConnection conn)
{
if (numPlayers >= maxConnections)
{
conn.Disconnect();
return;
}
// cannot join game in progress
if (SceneManager.GetActiveScene().name != LobbyScene)
{
conn.Disconnect();
return;
}
base.OnServerConnect(conn);
OnLobbyServerConnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnServerDisconnect(NetworkConnection conn)
{
if (conn.playerController != null)
{
NetworkLobbyPlayer player = conn.playerController.GetComponent<NetworkLobbyPlayer>();
if (player != null)
lobbySlots.Remove(player);
}
allPlayersReady = false;
foreach (NetworkLobbyPlayer player in lobbySlots)
{
if (player != null)
player.GetComponent<NetworkLobbyPlayer>().readyToBegin = false;
}
if (SceneManager.GetActiveScene().name == LobbyScene)
RecalculateLobbyPlayerIndices();
base.OnServerDisconnect(conn);
OnLobbyServerDisconnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
/// <param name="extraMessage"></param>
public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
{
if (SceneManager.GetActiveScene().name != LobbyScene) return;
if (lobbySlots.Count == maxConnections) return;
allPlayersReady = false;
if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager.OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name);
GameObject newLobbyGameObject = OnLobbyServerCreateLobbyPlayer(conn);
if (newLobbyGameObject == null)
newLobbyGameObject = (GameObject)Instantiate(lobbyPlayerPrefab.gameObject, Vector3.zero, Quaternion.identity);
NetworkLobbyPlayer newLobbyPlayer = newLobbyGameObject.GetComponent<NetworkLobbyPlayer>();
lobbySlots.Add(newLobbyPlayer);
RecalculateLobbyPlayerIndices();
NetworkServer.AddPlayerForConnection(conn, newLobbyGameObject);
}
void RecalculateLobbyPlayerIndices()
{
if (lobbySlots.Count > 0)
{
for (int i = 0; i < lobbySlots.Count; i++)
{
lobbySlots[i].index = i;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="sceneName"></param>
public override void ServerChangeScene(string sceneName)
{
if (sceneName == LobbyScene)
{
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
{
if (lobbyPlayer == null) continue;
// find the game-player object for this connection, and destroy it
NetworkIdentity identity = lobbyPlayer.GetComponent<NetworkIdentity>();
NetworkIdentity playerController = identity.connectionToClient.playerController;
NetworkServer.Destroy(playerController.gameObject);
if (NetworkServer.active)
{
// re-add the lobby object
lobbyPlayer.GetComponent<NetworkLobbyPlayer>().readyToBegin = false;
NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, lobbyPlayer.gameObject);
}
}
}
else
{
if (dontDestroyOnLoad)
{
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
{
if (lobbyPlayer != null)
{
lobbyPlayer.transform.SetParent(null);
DontDestroyOnLoad(lobbyPlayer);
}
}
}
}
base.ServerChangeScene(sceneName);
}
/// <summary>
///
/// </summary>
/// <param name="sceneName"></param>
public override void OnServerSceneChanged(string sceneName)
{
if (sceneName != LobbyScene)
{
// call SceneLoadedForPlayer on any players that become ready while we were loading the scene.
foreach (PendingPlayer pending in pendingPlayers)
SceneLoadedForPlayer(pending.conn, pending.lobbyPlayer);
pendingPlayers.Clear();
}
OnLobbyServerSceneChanged(sceneName);
}
/// <summary>
///
/// </summary>
public override void OnStartServer()
{
if (string.IsNullOrEmpty(LobbyScene))
{
Debug.LogError("NetworkLobbyManager LobbyScene is empty. Set the LobbyScene in the inspector for the NetworkLobbyMangaer");
return;
}
if (string.IsNullOrEmpty(GameplayScene))
{
Debug.LogError("NetworkLobbyManager PlayScene is empty. Set the PlayScene in the inspector for the NetworkLobbyMangaer");
return;
}
OnLobbyStartServer();
}
/// <summary>
///
/// </summary>
public override void OnStartHost()
{
OnLobbyStartHost();
}
/// <summary>
///
/// </summary>
public override void OnStopServer()
{
lobbySlots.Clear();
base.OnStopServer();
}
/// <summary>
///
/// </summary>
public override void OnStopHost()
{
OnLobbyStopHost();
}
#endregion
#region client handlers
/// <summary>
///
/// </summary>
public override void OnStartClient()
{
if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
else
ClientScene.RegisterPrefab(lobbyPlayerPrefab.gameObject);
if (playerPrefab == null)
Debug.LogError("NetworkLobbyManager no GamePlayer prefab is registered. Please add a GamePlayer prefab.");
else
ClientScene.RegisterPrefab(playerPrefab);
OnLobbyStartClient();
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientConnect(NetworkConnection conn)
{
OnLobbyClientConnect(conn);
CallOnClientEnterLobby();
base.OnClientConnect(conn);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientDisconnect(NetworkConnection conn)
{
OnLobbyClientDisconnect(conn);
base.OnClientDisconnect(conn);
}
/// <summary>
///
/// </summary>
public override void OnStopClient()
{
OnLobbyStopClient();
CallOnClientExitLobby();
if (!string.IsNullOrEmpty(offlineScene))
{
// Move the LobbyManager from the virtual DontDestroyOnLoad scene to the Game scene.
// This let's it be destroyed when client changes to the Offline scene.
SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
}
}
/// <summary>
///
/// </summary>
/// <param name="newSceneName"></param>
public override void OnClientChangeScene(string newSceneName)
{
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && NetworkClient.isConnected)
{
if (NetworkClient.connection != null && NetworkClient.connection.playerController != null)
{
GameObject lobbyPlayer = NetworkClient.connection.playerController.gameObject;
if (lobbyPlayer != null)
{
lobbyPlayer.transform.SetParent(null);
DontDestroyOnLoad(lobbyPlayer);
}
else
Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
}
}
else
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1}", dontDestroyOnLoad, NetworkClient.isConnected);
}
/// <summary>
///
/// </summary>
/// <param name="conn">Connection of the client</param>
public override void OnClientSceneChanged(NetworkConnection conn)
{
if (SceneManager.GetActiveScene().name == LobbyScene)
{
if (NetworkClient.isConnected)
CallOnClientEnterLobby();
}
else
CallOnClientExitLobby();
base.OnClientSceneChanged(conn);
OnLobbyClientSceneChanged(conn);
}
#endregion
#region lobby server virtuals
/// <summary>
/// This is called on the host when a host is started.
/// </summary>
public virtual void OnLobbyStartHost() { }
/// <summary>
/// This is called on the host when the host is stopped.
/// </summary>
public virtual void OnLobbyStopHost() { }
/// <summary>
/// This is called on the server when the server is started - including when a host is started.
/// </summary>
public virtual void OnLobbyStartServer() { }
/// <summary>
/// This is called on the server when a new client connects to the server.
/// </summary>
/// <param name="conn">The new connection.</param>
public virtual void OnLobbyServerConnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the server when a client disconnects.
/// </summary>
/// <param name="conn">The connection that disconnected.</param>
public virtual void OnLobbyServerDisconnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the server when a networked scene finishes loading.
/// </summary>
/// <param name="sceneName">Name of the new scene.</param>
public virtual void OnLobbyServerSceneChanged(string sceneName) { }
/// <summary>
/// This allows customization of the creation of the lobby-player object on the server.
/// <para>By default the lobbyPlayerPrefab is used to create the lobby-player, but this function allows that behaviour to be customized.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
/// <returns>The new lobby-player object.</returns>
public virtual GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn)
{
return null;
}
/// <summary>
/// This allows customization of the creation of the GamePlayer object on the server.
/// <para>By default the gamePlayerPrefab is used to create the game-player, but this function allows that behaviour to be customized. The object returned from the function will be used to replace the lobby-player on the connection.</para>
/// </summary>
/// <param name="conn">The connection the player object is for.</param>
/// <returns>A new GamePlayer object.</returns>
public virtual GameObject OnLobbyServerCreateGamePlayer(NetworkConnection conn)
{
return null;
}
// for users to apply settings from their lobby player object to their in-game player object
/// <summary>
/// This is called on the server when it is told that a client has finished switching from the lobby scene to a game player scene.
/// <para>When switching from the lobby, the lobby-player is replaced with a game-player object. This callback function gives an opportunity to apply state from the lobby-player to the game-player object.</para>
/// </summary>
/// <param name="lobbyPlayer">The lobby player object.</param>
/// <param name="gamePlayer">The game player object.</param>
/// <returns>False to not allow this player to replace the lobby player.</returns>
public virtual bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer)
{
return true;
}
/// <summary>
/// This is called on the server when all the players in the lobby are ready.
/// <para>The default implementation of this function uses ServerChangeScene() to switch to the game player scene. By implementing this callback you can customize what happens when all the players in the lobby are ready, such as adding a countdown or a confirmation for a group leader.</para>
/// </summary>
public virtual void OnLobbyServerPlayersReady()
{
// all players are readyToBegin, start the game
ServerChangeScene(GameplayScene);
}
#endregion
#region lobby client virtuals
/// <summary>
/// This is a hook to allow custom behaviour when the game client enters the lobby.
/// </summary>
public virtual void OnLobbyClientEnter() { }
/// <summary>
/// This is a hook to allow custom behaviour when the game client exits the lobby.
/// </summary>
public virtual void OnLobbyClientExit() { }
/// <summary>
/// This is called on the client when it connects to server.
/// </summary>
/// <param name="conn">The connection that connected.</param>
public virtual void OnLobbyClientConnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the client when disconnected from a server.
/// </summary>
/// <param name="conn">The connection that disconnected.</param>
public virtual void OnLobbyClientDisconnect(NetworkConnection conn) { }
/// <summary>
/// This is called on the client when a client is started.
/// </summary>
/// <param name="lobbyClient">The connection for the lobby.</param>
public virtual void OnLobbyStartClient() { }
/// <summary>
/// This is called on the client when the client stops.
/// </summary>
public virtual void OnLobbyStopClient() { }
/// <summary>
/// This is called on the client when the client is finished loading a new networked scene.
/// </summary>
/// <param name="conn">The connection that finished loading a new networked scene.</param>
public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) { }
/// <summary>
/// Called on the client when adding a player to the lobby fails.
/// <para>This could be because the lobby is full, or the connection is not allowed to have more players.</para>
/// </summary>
public virtual void OnLobbyClientAddPlayerFailed() { }
#endregion
#region optional UI
/// <summary>
/// virtual so inheriting classes can roll their own
/// </summary>
public virtual void OnGUI()
{
if (!showLobbyGUI)
return;
if (SceneManager.GetActiveScene().name != LobbyScene)
return;
GUI.Box(new Rect(10f, 180f, 520f, 150f), "PLAYERS");
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 615e6c6589cf9e54cad646b5a11e0529
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,155 @@
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Mirror
{
/// <summary>
/// This component works in conjunction with the NetworkLobbyManager to make up the multiplayer lobby system.
/// <para>The LobbyPrefab object of the NetworkLobbyManager must have this component on it. This component holds basic lobby player data required for the lobby to function. Game specific data for lobby players can be put in other components on the LobbyPrefab or in scripts derived from NetworkLobbyPlayer.</para>
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkLobbyPlayer")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyPlayer.html")]
public class NetworkLobbyPlayer : NetworkBehaviour
{
/// <summary>
/// This flag controls whether the default UI is shown for the lobby player.
/// <para>As this UI is rendered using the old GUI system, it is only recommended for testing purposes.</para>
/// </summary>
public bool showLobbyGUI = true;
/// <summary>
/// This is a flag that control whether this player is ready for the game to begin.
/// <para>When all players are ready to begin, the game will start. This should not be set directly, the SendReadyToBeginMessage function should be called on the client to set it on the server.</para>
/// </summary>
[SyncVar(hook = nameof(ReadyStateChanged))]
public bool readyToBegin;
/// <summary>
/// Current index of the player, e.g. Player1, Player2, etc.
/// </summary>
[SyncVar]
public int index;
#region Unity Callbacks
/// <summary>
/// Do not use Start - Override OnStartrHost / OnStartClient instead!
/// </summary>
public void Start()
{
if (NetworkManager.singleton as NetworkLobbyManager)
OnClientEnterLobby();
else
Debug.LogError("LobbyPlayer could not find a NetworkLobbyManager. The LobbyPlayer requires a NetworkLobbyManager object to function. Make sure that there is one in the scene.");
}
#endregion
#region Commands
[Command]
public void CmdChangeReadyState(bool readyState)
{
readyToBegin = readyState;
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
if (lobby != null)
{
lobby.ReadyStatusChanged();
}
}
#endregion
#region SyncVar Hooks
void ReadyStateChanged(bool newReadyState)
{
OnClientReady(readyToBegin);
}
#endregion
#region Lobby Client Virtuals
/// <summary>
/// This is a hook that is invoked on all player objects when entering the lobby.
/// <para>Note: isLocalPlayer is not guaranteed to be set until OnStartLocalPlayer is called.</para>
/// </summary>
public virtual void OnClientEnterLobby() { }
/// <summary>
/// This is a hook that is invoked on all player objects when exiting the lobby.
/// </summary>
public virtual void OnClientExitLobby() { }
/// <summary>
/// This is a hook that is invoked on clients when a LobbyPlayer switches between ready or not ready.
/// <para>This function is called when the a client player calls SendReadyToBeginMessage() or SendNotReadyToBeginMessage().</para>
/// </summary>
/// <param name="readyState">Whether the player is ready or not.</param>
public virtual void OnClientReady(bool readyState) { }
#endregion
#region Optional UI
/// <summary>
/// Render a UI for the lobby. Override to provide your on UI
/// </summary>
public virtual void OnGUI()
{
if (!showLobbyGUI)
return;
NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
if (lobby)
{
if (!lobby.showLobbyGUI)
return;
if (SceneManager.GetActiveScene().name != lobby.LobbyScene)
return;
GUILayout.BeginArea(new Rect(20f + (index * 100), 200f, 90f, 130f));
GUILayout.Label($"Player [{index + 1}]");
if (readyToBegin)
GUILayout.Label("Ready");
else
GUILayout.Label("Not Ready");
if (((isServer && index > 0) || isServerOnly) && GUILayout.Button("REMOVE"))
{
// This button only shows on the Host for all players other than the Host
// Host and Players can't remove themselves (stop the client instead)
// Host can kick a Player this way.
GetComponent<NetworkIdentity>().connectionToClient.Disconnect();
}
GUILayout.EndArea();
if (NetworkClient.active && isLocalPlayer)
{
GUILayout.BeginArea(new Rect(20f, 300f, 120f, 20f));
if (readyToBegin)
{
if (GUILayout.Button("Cancel"))
CmdChangeReadyState(false);
}
else
{
if (GUILayout.Button("Ready"))
CmdChangeReadyState(true);
}
GUILayout.EndArea();
}
}
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 79874ac94d5b1314788ecf0e86bd23fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,171 @@
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
/// <summary>
/// Component that controls visibility of networked objects for players.
/// <para>Any object with this component on it will not be visible to players more than a (configurable) distance away.</para>
/// </summary>
[AddComponentMenu("Network/NetworkProximityChecker")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkProximityChecker.html")]
public class NetworkProximityChecker : NetworkBehaviour
{
/// <summary>
/// Enumeration of methods to use to check proximity.
/// </summary>
public enum CheckMethod
{
Physics3D,
Physics2D
}
/// <summary>
/// The maximim range that objects will be visible at.
/// </summary>
[Tooltip("The maximum range that objects will be visible at.")]
public int visRange = 10;
/// <summary>
/// How often (in seconds) that this object should update the list of observers that can see it.
/// </summary>
[Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")]
public float visUpdateInterval = 1;
/// <summary>
/// Which method to use for checking proximity of players.
/// <para>Physics3D uses 3D physics to determine proximity.</para>
/// <para>Physics2D uses 2D physics to determine proximity.</para>
/// </summary>
[Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\nPhysics2D uses 2D physics to determine proximity.")]
public CheckMethod checkMethod = CheckMethod.Physics3D;
/// <summary>
/// Flag to force this object to be hidden for players.
/// <para>If this object is a player object, it will not be hidden for that player.</para>
/// </summary>
[Tooltip("Enable to force this object to be hidden from players.")]
public bool forceHidden;
// Layers are used anyway, might as well expose them to the user.
/// <summary>
/// Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.
/// <para>~0 means 'Everything'.</para>
/// </summary>
[Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")]
public LayerMask castLayers = ~0;
float lastUpdateTime;
// OverlapSphereNonAlloc array to avoid allocations.
// -> static so we don't create one per component
// -> this is worth it because proximity checking happens for just about
// every entity on the server!
// -> should be big enough to work in just about all cases
static Collider[] hitsBuffer3D = new Collider[10000];
static Collider2D[] hitsBuffer2D = new Collider2D[10000];
void Update()
{
if (!NetworkServer.active)
return;
if (Time.time - lastUpdateTime > visUpdateInterval)
{
netIdentity.RebuildObservers(false);
lastUpdateTime = Time.time;
}
}
/// <summary>
/// Called when a new player enters
/// </summary>
/// <param name="newObserver"></param>
/// <returns></returns>
public override bool OnCheckObserver(NetworkConnection newObserver)
{
if (forceHidden)
return false;
return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange;
}
/// <summary>
/// Called when a new player enters, and when scene changes occur
/// </summary>
/// <param name="observers">List of players to be updated. Modify this set with all the players that can see this object</param>
/// <param name="initial">True if this is the first time the method is called for this object</param>
/// <returns>True if this component calculated the list of observers</returns>
public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
{
// if force hidden then return without adding any observers.
if (forceHidden)
// always return true when overwriting OnRebuildObservers so that
// Mirror knows not to use the built in rebuild method.
return true;
// find players within range
switch (checkMethod)
{
case CheckMethod.Physics3D:
{
// cast without allocating GC for maximum performance
int hitCount = Physics.OverlapSphereNonAlloc(transform.position, visRange, hitsBuffer3D, castLayers);
if (hitCount == hitsBuffer3D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapSphere test for " + name + " has filled the whole buffer(" + hitsBuffer3D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
for (int i = 0; i < hitCount; i++)
{
Collider hit = hitsBuffer3D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
{
observers.Add(identity.connectionToClient);
}
}
break;
}
case CheckMethod.Physics2D:
{
// cast without allocating GC for maximum performance
int hitCount = Physics2D.OverlapCircleNonAlloc(transform.position, visRange, hitsBuffer2D, castLayers);
if (hitCount == hitsBuffer2D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapCircle test for " + name + " has filled the whole buffer(" + hitsBuffer2D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
for (int i = 0; i < hitCount; i++)
{
Collider2D hit = hitsBuffer2D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
{
observers.Add(identity.connectionToClient);
}
}
break;
}
}
// always return true when overwriting OnRebuildObservers so that
// Mirror knows not to use the built in rebuild method.
return true;
}
/// <summary>
/// Called when hiding and showing objects on the host
/// </summary>
/// <param name="visible"></param>
public override void OnSetLocalVisibility(bool visible)
{
foreach (Renderer rend in GetComponentsInChildren<Renderer>())
{
rend.enabled = visible;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1731d8de2d0c84333b08ebe1e79f4118
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using UnityEngine;
namespace Mirror
{
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkTransform")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransform.html")]
public class NetworkTransform : NetworkTransformBase
{
protected override Transform targetComponent => transform;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f74aedd71d9a4f55b3ce499326d45fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,447 @@
// vis2k:
// base class for NetworkTransform and NetworkTransformChild.
// New method is simple and stupid. No more 1500 lines of code.
//
// Server sends current data.
// Client saves it and interpolates last and latest data points.
// Update handles transform movement / rotation
// FixedUpdate handles rigidbody movement / rotation
//
// Notes:
// * Built-in Teleport detection in case of lags / teleport / obstacles
// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp
// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code.
// * Initial delay might happen if server sends packet immediately after moving
// just 1cm, hence we move 1cm and then wait 100ms for next packet
// * Only way for smooth movement is to use a fixed movement speed during
// interpolation. interpolation over time is never that good.
//
using UnityEngine;
namespace Mirror
{
public abstract class NetworkTransformBase : NetworkBehaviour
{
// rotation compression. not public so that other scripts can't modify
// it at runtime. alternatively we could send 1 extra byte for the mode
// each time so clients know how to decompress, but the whole point was
// to save bandwidth in the first place.
// -> can still be modified in the Inspector while the game is running,
// but would cause errors immediately and be pretty obvious.
[Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")]
[SerializeField] Compression compressRotation = Compression.Much;
public enum Compression { None, Much, Lots, NoRotation }; // easily understandable and funny
// server
Vector3 lastPosition;
Quaternion lastRotation;
private Vector3 lastScale;
// client
public class DataPoint
{
public float timeStamp;
// use local position/rotation for VR support
public Vector3 localPosition;
public Quaternion localRotation;
public Vector3 localScale;
public float movementSpeed;
}
// interpolation start and goal
DataPoint start;
DataPoint goal;
// local authority send time
float lastClientSendTime;
// target transform to sync. can be on a child.
protected abstract Transform targetComponent { get; }
// serialization is needed by OnSerialize and by manual sending from authority
static void SerializeIntoWriter(NetworkWriter writer, Vector3 position, Quaternion rotation, Compression compressRotation, Vector3 scale)
{
// serialize position
writer.WriteVector3(position);
// serialize rotation
// writing quaternion = 16 byte
// writing euler angles = 12 byte
// -> quaternion->euler->quaternion always works.
// -> gimbal lock only occurs when adding.
Vector3 euler = rotation.eulerAngles;
if (compressRotation == Compression.None)
{
// write 3 floats = 12 byte
writer.WriteSingle(euler.x);
writer.WriteSingle(euler.y);
writer.WriteSingle(euler.z);
}
else if (compressRotation == Compression.Much)
{
// write 3 byte. scaling [0,360] to [0,255]
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue));
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue));
writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue));
}
else if (compressRotation == Compression.Lots)
{
// write 2 byte, 5 bits for each float
writer.WriteUInt16(FloatBytePacker.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360));
}
// serialize scale
writer.WriteVector3(scale);
}
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
// use local position/rotation/scale for VR support
SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
return true;
}
// try to estimate movement speed for a data point based on how far it
// moved since the previous one
// => if this is the first time ever then we use our best guess:
// -> delta based on transform.localPosition
// -> elapsed based on send interval hoping that it roughly matches
static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval)
{
Vector3 delta = to.localPosition - (from != null ? from.localPosition : transform.localPosition);
float elapsed = from != null ? to.timeStamp - from.timeStamp : sendInterval;
return elapsed > 0 ? delta.magnitude / elapsed : 0; // avoid NaN
}
// serialization is needed by OnSerialize and by manual sending from authority
void DeserializeFromReader(NetworkReader reader)
{
// put it into a data point immediately
DataPoint temp = new DataPoint
{
// deserialize position
localPosition = reader.ReadVector3()
};
// deserialize rotation
if (compressRotation == Compression.None)
{
// read 3 floats = 16 byte
float x = reader.ReadSingle();
float y = reader.ReadSingle();
float z = reader.ReadSingle();
temp.localRotation = Quaternion.Euler(x, y, z);
}
else if (compressRotation == Compression.Much)
{
// read 3 byte. scaling [0,255] to [0,360]
float x = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
float y = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
float z = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
temp.localRotation = Quaternion.Euler(x, y, z);
}
else if (compressRotation == Compression.Lots)
{
// read 2 byte, 5 bits per float
Vector3 xyz = FloatBytePacker.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360);
temp.localRotation = Quaternion.Euler(xyz.x, xyz.y, xyz.z);
}
temp.localScale = reader.ReadVector3();
temp.timeStamp = Time.time;
// movement speed: based on how far it moved since last time
// has to be calculated before 'start' is overwritten
temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetComponent.transform, syncInterval);
// reassign start wisely
// -> first ever data point? then make something up for previous one
// so that we can start interpolation without waiting for next.
if (start == null)
{
start = new DataPoint
{
timeStamp = Time.time - syncInterval,
// local position/rotation for VR support
localPosition = targetComponent.transform.localPosition,
localRotation = targetComponent.transform.localRotation,
localScale = targetComponent.transform.localScale,
movementSpeed = temp.movementSpeed
};
}
// -> second or nth data point? then update previous, but:
// we start at where ever we are right now, so that it's
// perfectly smooth and we don't jump anywhere
//
// example if we are at 'x':
//
// A--x->B
//
// and then receive a new point C:
//
// A--x--B
// |
// |
// C
//
// then we don't want to just jump to B and start interpolation:
//
// x
// |
// |
// C
//
// we stay at 'x' and interpolate from there to C:
//
// x..B
// \ .
// \.
// C
//
else
{
float oldDistance = Vector3.Distance(start.localPosition, goal.localPosition);
float newDistance = Vector3.Distance(goal.localPosition, temp.localPosition);
start = goal;
// teleport / lag / obstacle detection: only continue at current
// position if we aren't too far away
//
// // local position/rotation for VR support
if (Vector3.Distance(targetComponent.transform.localPosition, start.localPosition) < oldDistance + newDistance)
{
start.localPosition = targetComponent.transform.localPosition;
start.localRotation = targetComponent.transform.localRotation;
start.localScale = targetComponent.transform.localScale;
}
}
// set new destination in any case. new data is best data.
goal = temp;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
// deserialize
DeserializeFromReader(reader);
}
// local authority client sends sync message to server for broadcasting
[Command]
void CmdClientToServerSync(byte[] payload)
{
// deserialize payload
NetworkReader reader = new NetworkReader(payload);
DeserializeFromReader(reader);
// server-only mode does no interpolation to save computations,
// but let's set the position directly
if (isServer && !isClient)
ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
// set dirty so that OnSerialize broadcasts it
SetDirtyBit(1UL);
}
// where are we in the timeline between start and goal? [0,1]
static float CurrentInterpolationFactor(DataPoint start, DataPoint goal)
{
if (start != null)
{
float difference = goal.timeStamp - start.timeStamp;
// the moment we get 'goal', 'start' is supposed to
// start, so elapsed time is based on:
float elapsed = Time.time - goal.timeStamp;
return difference > 0 ? elapsed / difference : 0; // avoid NaN
}
return 0;
}
static Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition)
{
if (start != null)
{
// Option 1: simply interpolate based on time. but stutter
// will happen, it's not that smooth. especially noticeable if
// the camera automatically follows the player
// float t = CurrentInterpolationFactor();
// return Vector3.Lerp(start.position, goal.position, t);
// Option 2: always += speed
// -> speed is 0 if we just started after idle, so always use max
// for best results
float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed);
return Vector3.MoveTowards(currentPosition, goal.localPosition, speed * Time.deltaTime);
}
return currentPosition;
}
static Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation)
{
if (start != null)
{
float t = CurrentInterpolationFactor(start, goal);
return Quaternion.Slerp(start.localRotation, goal.localRotation, t);
}
return defaultRotation;
}
static Vector3 InterpolateScale(DataPoint start, DataPoint goal, Vector3 currentScale)
{
if (start != null)
{
float t = CurrentInterpolationFactor(start, goal);
return Vector3.Lerp(start.localScale, goal.localScale, t);
}
return currentScale;
}
// teleport / lag / stuck detection
// -> checking distance is not enough since there could be just a tiny
// fence between us and the goal
// -> checking time always works, this way we just teleport if we still
// didn't reach the goal after too much time has elapsed
bool NeedsTeleport()
{
// calculate time between the two data points
float startTime = start != null ? start.timeStamp : Time.time - syncInterval;
float goalTime = goal != null ? goal.timeStamp : Time.time;
float difference = goalTime - startTime;
float timeSinceGoalReceived = Time.time - goalTime;
return timeSinceGoalReceived > difference * 5;
}
// moved since last time we checked it?
bool HasEitherMovedRotatedScaled()
{
// moved or rotated or scaled?
// local position/rotation/scale for VR support
bool moved = lastPosition != targetComponent.transform.localPosition;
bool rotated = lastRotation != targetComponent.transform.localRotation;
bool scaled = lastScale != targetComponent.transform.localScale;
// save last for next frame to compare
// (only if change was detected. otherwise slow moving objects might
// never sync because of C#'s float comparison tolerance. see also:
// https://github.com/vis2k/Mirror/pull/428)
bool change = moved || rotated || scaled;
if (change)
{
// local position/rotation for VR support
lastPosition = targetComponent.transform.localPosition;
lastRotation = targetComponent.transform.localRotation;
lastScale = targetComponent.transform.localScale;
}
return change;
}
// set position carefully depending on the target component
void ApplyPositionRotationScale(Vector3 position, Quaternion rotation, Vector3 scale)
{
// local position/rotation for VR support
targetComponent.transform.localPosition = position;
if (Compression.NoRotation != compressRotation)
{
targetComponent.transform.localRotation = rotation;
}
targetComponent.transform.localScale = scale;
}
void Update()
{
// if server then always sync to others.
if (isServer)
{
// just use OnSerialize via SetDirtyBit only sync when position
// changed. set dirty bits 0 or 1
SetDirtyBit(HasEitherMovedRotatedScaled() ? 1UL : 0UL);
}
// no 'else if' since host mode would be both
if (isClient)
{
// send to server if we have local authority (and aren't the server)
// -> only if connectionToServer has been initialized yet too
if (!isServer && hasAuthority)
{
// check only each 'syncInterval'
if (Time.time - lastClientSendTime >= syncInterval)
{
if (HasEitherMovedRotatedScaled())
{
// serialize
// local position/rotation for VR support
NetworkWriter writer = new NetworkWriter();
SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
// send to server
CmdClientToServerSync(writer.ToArray());
}
lastClientSendTime = Time.time;
}
}
// apply interpolation on client for all players
// unless this client has authority over the object. could be
// himself or another object that he was assigned authority over
if (!hasAuthority)
{
// received one yet? (initialized?)
if (goal != null)
{
// teleport or interpolate
if (NeedsTeleport())
{
// local position/rotation for VR support
ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
}
else
{
// local position/rotation for VR support
ApplyPositionRotationScale(InterpolatePosition(start, goal, targetComponent.transform.localPosition),
InterpolateRotation(start, goal, targetComponent.transform.localRotation),
InterpolateScale(start, goal, targetComponent.transform.localScale));
}
}
}
}
}
static void DrawDataPointGizmo(DataPoint data, Color color)
{
// use a little offset because transform.localPosition might be in
// the ground in many cases
Vector3 offset = Vector3.up * 0.01f;
// draw position
Gizmos.color = color;
Gizmos.DrawSphere(data.localPosition + offset, 0.5f);
// draw forward and up
Gizmos.color = Color.blue; // like unity move tool
Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.forward);
Gizmos.color = Color.green; // like unity move tool
Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.up);
}
static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color)
{
Gizmos.color = color;
Gizmos.DrawLine(data1.localPosition, data2.localPosition);
}
// draw the data points for easier debugging
void OnDrawGizmos()
{
// draw start and goal points
if (start != null) DrawDataPointGizmo(start, Color.gray);
if (goal != null) DrawDataPointGizmo(goal, Color.white);
// draw line between them
if (start != null && goal != null) DrawLineBetweenDataPoints(start, goal, Color.cyan);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e77294d8ccbc4e7cb8ca2bd0d3e99ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
using UnityEngine;
namespace Mirror
{
/// <summary>
/// A component to synchronize the position of child transforms of networked objects.
/// <para>There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the recieved values.</para>
/// </summary>
[AddComponentMenu("Network/NetworkTransformChild")]
[HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransformChild.html")]
public class NetworkTransformChild : NetworkTransformBase
{
public Transform target;
protected override Transform targetComponent => target;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 734b48bea0b204338958ee3d885e11f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2539267b6934a4026a505690a1e1eda2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
{
"name": "Mirror.Editor",
"references": [
"Mirror"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1c7c33eb5480dd24c9e29a8250c1a775
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,5 @@
// This file was removed in Mirror 3.4.9
// The purpose of this file is to get the old file overwritten
// when users update from the asset store to prevent a flood of errors
// from having the old file still in the project as a straggler.
// This file will be dropped from the Asset Store package in May 2019

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9589e903d4e98490fb1157762a307fd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,197 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourInspector : Editor
{
bool initialized;
protected List<string> syncVarNames = new List<string>();
bool syncsAnything;
bool[] showSyncLists;
readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");
internal virtual bool HideScriptField => false;
// does this type sync anything? otherwise we don't need to show syncInterval
bool SyncsAnything(Type scriptClass)
{
// 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.
// (look for 'Mirror.Sync'. not '.SyncObject' because we'd have to
// check base type for that again)
foreach (FieldInfo field in scriptClass.GetFields())
{
if (field.FieldType.BaseType != null &&
field.FieldType.BaseType.FullName != null &&
field.FieldType.BaseType.FullName.Contains("Mirror.Sync"))
{
return true;
}
}
return false;
}
void Init(MonoScript script)
{
initialized = true;
Type scriptClass = script.GetClass();
// find public SyncVars to show (user doesn't want protected ones to be shown in inspector)
foreach (FieldInfo field in scriptClass.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
Attribute[] fieldMarkers = (Attribute[])field.GetCustomAttributes(typeof(SyncVarAttribute), true);
if (fieldMarkers.Length > 0)
{
syncVarNames.Add(field.Name);
}
}
int numSyncLists = scriptClass.GetFields().Count(
field => field.FieldType.BaseType != null &&
field.FieldType.BaseType.Name.Contains("SyncList"));
if (numSyncLists > 0)
{
showSyncLists = new bool[numSyncLists];
}
syncsAnything = SyncsAnything(scriptClass);
}
public override void OnInspectorGUI()
{
if (!initialized)
{
serializedObject.Update();
SerializedProperty scriptProperty = serializedObject.FindProperty("m_Script");
if (scriptProperty == null)
return;
MonoScript targetScript = scriptProperty.objectReferenceValue as MonoScript;
Init(targetScript);
}
EditorGUI.BeginChangeCheck();
serializedObject.Update();
// Loop through properties and create one field (including children) for each top level property.
SerializedProperty property = serializedObject.GetIterator();
bool expanded = true;
while (property.NextVisible(expanded))
{
bool isSyncVar = syncVarNames.Contains(property.name);
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if (property.name == "m_Script")
{
if (HideScriptField)
{
continue;
}
EditorGUI.BeginDisabledGroup(true);
}
EditorGUILayout.PropertyField(property, true);
if (isSyncVar)
{
GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
}
if (property.name == "m_Script")
{
EditorGUI.EndDisabledGroup();
}
}
else
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(property, true);
if (isSyncVar)
{
GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
}
EditorGUILayout.EndHorizontal();
}
expanded = false;
}
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
// find SyncLists.. they are not properties.
int syncListIndex = 0;
foreach (FieldInfo field in serializedObject.targetObject.GetType().GetFields())
{
if (field.FieldType.BaseType != null && field.FieldType.BaseType.Name.Contains("SyncList"))
{
showSyncLists[syncListIndex] = EditorGUILayout.Foldout(showSyncLists[syncListIndex], "SyncList " + field.Name + " [" + field.FieldType.Name + "]");
if (showSyncLists[syncListIndex])
{
EditorGUI.indentLevel += 1;
if (field.GetValue(serializedObject.targetObject) is IEnumerable synclist)
{
int index = 0;
IEnumerator enu = synclist.GetEnumerator();
while (enu.MoveNext())
{
if (enu.Current != null)
{
EditorGUILayout.LabelField("Item:" + index, enu.Current.ToString());
}
index += 1;
}
}
EditorGUI.indentLevel -= 1;
}
syncListIndex += 1;
}
}
// 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)
{
NetworkBehaviour networkBehaviour = target as NetworkBehaviour;
if (networkBehaviour != null)
{
// syncMode
serializedObject.FindProperty("syncMode").enumValueIndex = (int)(SyncMode)
EditorGUILayout.EnumPopup("Network Sync Mode", networkBehaviour.syncMode);
// syncInterval
// [0,2] should be enough. anything >2s is too laggy anyway.
serializedObject.FindProperty("syncInterval").floatValue = EditorGUILayout.Slider(
new GUIContent("Network Sync Interval",
"Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)"),
networkBehaviour.syncInterval, 0, 2);
// apply
serializedObject.ApplyModifiedProperties();
}
}
}
}
} //namespace

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f02853db46b6346e4866594a96c3b0e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,106 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Mirror
{
[CustomEditor(typeof(NetworkIdentity), true)]
[CanEditMultipleObjects]
public class NetworkIdentityEditor : Editor
{
SerializedProperty serverOnlyProperty;
SerializedProperty localPlayerAuthorityProperty;
readonly GUIContent serverOnlyLabel = new GUIContent("Server Only", "True if the object should only exist on the server.");
readonly GUIContent localPlayerAuthorityLabel = new GUIContent("Local Player Authority", "True if this object will be controlled by a player on a client.");
readonly GUIContent spawnLabel = new GUIContent("Spawn Object", "This causes an unspawned server object to be spawned on clients");
NetworkIdentity networkIdentity;
bool initialized;
bool showObservers;
void Init()
{
if (initialized)
{
return;
}
initialized = true;
networkIdentity = target as NetworkIdentity;
serverOnlyProperty = serializedObject.FindProperty("serverOnly");
localPlayerAuthorityProperty = serializedObject.FindProperty("localPlayerAuthority");
}
public override void OnInspectorGUI()
{
if (serverOnlyProperty == null)
{
initialized = false;
}
Init();
serializedObject.Update();
if (serverOnlyProperty.boolValue)
{
EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
EditorGUILayout.LabelField("Local Player Authority cannot be set for server-only objects");
}
else if (localPlayerAuthorityProperty.boolValue)
{
EditorGUILayout.LabelField("Server Only cannot be set for Local Player Authority objects");
EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
}
else
{
EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
}
serializedObject.ApplyModifiedProperties();
if (!Application.isPlaying)
{
return;
}
// Runtime actions below here
EditorGUILayout.Separator();
if (networkIdentity.observers != null && networkIdentity.observers.Count > 0)
{
showObservers = EditorGUILayout.Foldout(showObservers, "Observers");
if (showObservers)
{
EditorGUI.indentLevel += 1;
foreach (KeyValuePair<int, NetworkConnection> kvp in networkIdentity.observers)
{
if (kvp.Value.playerController != null)
EditorGUILayout.ObjectField("Connection " + kvp.Value.connectionId, kvp.Value.playerController.gameObject, typeof(GameObject), false);
else
EditorGUILayout.TextField("Connection " + kvp.Value.connectionId);
}
EditorGUI.indentLevel -= 1;
}
}
if (PrefabUtility.IsPartOfPrefabAsset(networkIdentity.gameObject))
return;
if (networkIdentity.gameObject.activeSelf && networkIdentity.netId == 0 && NetworkServer.active)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(spawnLabel);
if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft))
{
NetworkServer.Spawn(networkIdentity.gameObject);
EditorUtility.SetDirty(target); // preview window STILL doens't update immediately..
}
EditorGUILayout.EndHorizontal();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b6e3680cc14b4769bff378e5dbc3544
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,280 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Mirror
{
[CustomPreview(typeof(GameObject))]
class NetworkInformationPreview : ObjectPreview
{
class NetworkIdentityInfo
{
public GUIContent name;
public GUIContent value;
}
class 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;
}
}
List<NetworkIdentityInfo> info;
List<NetworkBehaviourInfo> behavioursInfo;
NetworkIdentity identity;
GUIContent title;
Styles styles = new Styles();
public override void Initialize(UnityObject[] targets)
{
base.Initialize(targets);
GetNetworkInformation(target as GameObject);
}
public override GUIContent GetPreviewTitle()
{
if (title == null)
{
title = new GUIContent("Network Information");
}
return title;
}
public override bool HasPreviewGUI()
{
return info != null && info.Count > 0;
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
if (Event.current.type != EventType.Repaint)
return;
if (info == null || info.Count == 0)
return;
if (styles == null)
styles = new Styles();
// 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();
//Apply padding
RectOffset previewPadding = new RectOffset(-5, -5, -5, -5);
Rect paddedr = previewPadding.Add(r);
//Centering
float initialX = paddedr.x + 10;
float initialY = paddedr.y + 10;
Rect labelRect = new Rect(initialX, initialY, maxNameLabelSize.x, maxNameLabelSize.y);
Rect idLabelRect = new Rect(maxNameLabelSize.x, initialY, maxValueLabelSize.x, maxValueLabelSize.y);
foreach (NetworkIdentityInfo info in info)
{
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;
}
// Show behaviours list in a different way than the name/value pairs above
float lastY = labelRect.y;
if (behavioursInfo != null && behavioursInfo.Count > 0)
{
Vector2 maxBehaviourLabelSize = GetMaxBehaviourLabelSize();
Rect behaviourRect = new Rect(initialX, labelRect.y + 10, maxBehaviourLabelSize.x, maxBehaviourLabelSize.y);
GUI.Label(behaviourRect, new GUIContent("Network Behaviours"), styles.labelStyle);
behaviourRect.x += 20; // indent names
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;
lastY = behaviourRect.y;
}
if (identity.observers != null && identity.observers.Count > 0)
{
Rect observerRect = new Rect(initialX, lastY + 10, 200, 20);
GUI.Label(observerRect, new GUIContent("Network observers"), styles.labelStyle);
observerRect.x += 20; // indent names
observerRect.y += observerRect.height;
foreach (KeyValuePair<int, NetworkConnection> kvp in identity.observers)
{
GUI.Label(observerRect, kvp.Value.address + ":" + kvp.Value.connectionId, styles.componentName);
observerRect.y += observerRect.height;
lastY = observerRect.y;
}
}
if (identity.clientAuthorityOwner != null)
{
Rect ownerRect = new Rect(initialX, lastY + 10, 400, 20);
GUI.Label(ownerRect, new GUIContent("Client Authority: " + identity.clientAuthorityOwner), styles.labelStyle);
}
}
}
// Get the maximum size used by the value of information items
Vector2 GetMaxNameLabelSize()
{
Vector2 maxLabelSize = Vector2.zero;
foreach (NetworkIdentityInfo info in info)
{
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()
{
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;
}
void GetNetworkInformation(GameObject gameObject)
{
identity = gameObject.GetComponent<NetworkIdentity>();
if (identity != null)
{
info = new List<NetworkIdentityInfo>
{
GetAssetId(),
GetString("Scene ID", identity.sceneId.ToString("X"))
};
if (!Application.isPlaying)
{
return;
}
info.Add(GetString("Network ID", identity.netId.ToString()));
info.Add(GetBoolean("Is Client", identity.isClient));
info.Add(GetBoolean("Is Server", identity.isServer));
info.Add(GetBoolean("Has Authority", identity.hasAuthority));
info.Add(GetBoolean("Is Local Player", identity.isLocalPlayer));
NetworkBehaviour[] behaviours = gameObject.GetComponents<NetworkBehaviour>();
if (behaviours.Length > 0)
{
behavioursInfo = new List<NetworkBehaviourInfo>();
foreach (NetworkBehaviour behaviour in behaviours)
{
NetworkBehaviourInfo info = new NetworkBehaviourInfo
{
name = new GUIContent(behaviour.GetType().FullName),
behaviour = behaviour
};
behavioursInfo.Add(info);
}
}
}
}
NetworkIdentityInfo GetAssetId()
{
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)
{
NetworkIdentityInfo info = new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent(value)
};
return info;
}
static NetworkIdentityInfo GetBoolean(string name, bool value)
{
NetworkIdentityInfo info = new NetworkIdentityInfo
{
name = new GUIContent(name),
value = new GUIContent((value ? "Yes" : "No"))
};
return info;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51a99294efe134232932c34606737356
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,112 @@
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,
elementHeight = 16 // this uses a 16x16 icon. other sizes make it stretch.
};
}
}
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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 519712eb07f7a44039df57664811c2c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
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)
{
// 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) as GameObject;
#else
GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject) as 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)
{
if (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);
}
}
}
}
// 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.");
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3ec1c414d821444a9e77f18a2c130ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
using UnityEditor;
namespace Mirror
{
static class PreprocessorDefine
{
/// <summary>
/// Add define symbols as soon as Unity gets done compiling.
/// </summary>
[InitializeOnLoadMethod]
static void AddDefineSymbols()
{
HashSet<string> defines = new HashSet<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'))
{
"MIRROR",
"MIRROR_1726_OR_NEWER",
"MIRROR_3_0_OR_NEWER",
"MIRROR_3_12_OR_NEWER"
};
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, string.Join(";", defines));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1d66fe74ec6f42dd974cba37d25d453
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,56 @@
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 = GetSceneObject(property.stringValue);
SceneAsset scene = (SceneAsset)EditorGUI.ObjectField(position, label, sceneObject, typeof(SceneAsset), true);
if (scene == null)
{
property.stringValue = "";
}
else if (scene.name != property.stringValue)
{
SceneAsset sceneObj = GetSceneObject(scene.name);
if (sceneObj == null)
{
Debug.LogWarning("The scene " + scene.name + " cannot be used. To use this scene add it to the build settings for the project");
}
else
{
property.stringValue = scene.name;
}
}
}
else
EditorGUI.LabelField(position, label.text, "Use [Scene] with strings.");
}
protected SceneAsset GetSceneObject(string sceneObjectName)
{
if (string.IsNullOrEmpty(sceneObjectName))
{
return null;
}
foreach (EditorBuildSettingsScene editorScene in EditorBuildSettings.scenes)
{
if (editorScene.path.IndexOf(sceneObjectName) != -1)
{
return AssetDatabase.LoadAssetAtPath(editorScene.path, typeof(SceneAsset)) as SceneAsset;
}
}
Debug.LogWarning("Scene [" + sceneObjectName + "] cannot be used. Add this scene to the 'Scenes in the Build' in build settings.");
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b24704a46211b4ea294aba8f58715cea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d9f8e6274119b4ce29e498cfb8aca8a4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,141 @@
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";
public static Action<string> OnWeaverMessage; // delegate for subscription to Weaver debug messages
public static Action<string> OnWeaverWarning; // delegate for subscription to Weaver warning messages
public static Action<string> OnWeaverError; // delete for subscription to Weaver error messages
public static bool WeaverEnabled { get; set; } // controls whether we weave any assemblies when CompilationPipeline delegates are invoked
public static bool UnityLogEnabled = true; // controls weather Weaver errors are reported direct to the Unity console (tests enable this)
public static bool WeaveFailed { get; private set; } // holds the result status of our latest Weave operation
// debug message handler that also calls OnMessageMethod delegate
static void HandleMessage(string msg)
{
if (UnityLogEnabled) Debug.Log(msg);
if (OnWeaverMessage != null) OnWeaverMessage.Invoke(msg);
}
// 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]
static void OnInitializeOnLoad()
{
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
}
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;
}
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>();
dependencyPaths.Add(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));
}
}
// passing null in the outputDirectory param will do an in-place update of the assembly
if (Program.Process(unityEngineCoreModuleDLL, mirrorRuntimeDll, null, new[] { assemblyPath }, dependencyPaths.ToArray(), HandleWarning, HandleError))
{
WeaveFailed = false;
//Debug.Log("Weaving succeeded for: " + assemblyPath);
}
else
{
WeaveFailed = true;
if (UnityLogEnabled) Debug.LogError("Weaving failed for: " + assemblyPath);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de2aeb2e8068f421a9a1febe408f7051
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,151 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
public static class Extensions
{
public static bool IsDerivedFrom(this TypeDefinition td, TypeReference baseClass)
{
if (!td.IsClass)
return false;
// are ANY parent classes of baseClass?
TypeReference parent = td.BaseType;
while (parent != null)
{
string parentName = parent.FullName;
// strip generic parameters
int index = parentName.IndexOf('<');
if (index != -1)
{
parentName = parentName.Substring(0, index);
}
if (parentName == baseClass.FullName)
{
return true;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
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(this TypeDefinition td, TypeReference baseInterface)
{
TypeDefinition typedef = td;
while (typedef != null)
{
foreach (InterfaceImplementation iface in typedef.Interfaces)
{
if (iface.InterfaceType.FullName == baseInterface.FullName)
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 IsArrayType(this TypeReference tr)
{
if ((tr.IsArray && ((ArrayType)tr).ElementType.IsArray) || // jagged array
(tr.IsArray && ((ArrayType)tr).Rank > 1)) // multidimensional array
return false;
return true;
}
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;
}
// Given a method of a generic class such as ArraySegment<T>.get_Count,
// and a generic instance such as ArraySegment<int>
// Creates a reference to the specialized method ArraySegment<int>.get_Count;
// Note that calling ArraySegment<T>.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, 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);
}
public static CustomAttribute GetCustomAttribute(this MethodDefinition method, string attributeName)
{
foreach (CustomAttribute ca in method.CustomAttributes)
{
if (ca.AttributeType.FullName == attributeName)
return ca;
}
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 562a5cf0254cc45738e9aa549a7100b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,124 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Mdb;
using Mono.CecilX.Pdb;
namespace Mirror.Weaver
{
class Helpers
{
// This code is taken from SerializationWeaver
class AddSearchDirectoryHelper
{
delegate void AddSearchDirectoryDelegate(string directory);
readonly AddSearchDirectoryDelegate _addSearchDirectory;
public AddSearchDirectoryHelper(IAssemblyResolver assemblyResolver)
{
// reflection is used because IAssemblyResolver doesn't implement AddSearchDirectory but both DefaultAssemblyResolver and NuGetAssemblyResolver do
MethodInfo addSearchDirectory = assemblyResolver.GetType().GetMethod("AddSearchDirectory", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
if (addSearchDirectory == null)
throw new Exception("Assembly resolver doesn't implement AddSearchDirectory method.");
_addSearchDirectory = (AddSearchDirectoryDelegate)Delegate.CreateDelegate(typeof(AddSearchDirectoryDelegate), assemblyResolver, addSearchDirectory);
}
public void AddSearchDirectory(string directory)
{
_addSearchDirectory(directory);
}
}
public static string UnityEngineDLLDirectoryName()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
return directoryName?.Replace(@"file:\", "");
}
public static ISymbolReaderProvider GetSymbolReaderProvider(string inputFile)
{
string nakedFileName = inputFile.Substring(0, inputFile.Length - 4);
if (File.Exists(nakedFileName + ".pdb"))
{
Console.WriteLine("Symbols will be read from " + nakedFileName + ".pdb");
return new PdbReaderProvider();
}
if (File.Exists(nakedFileName + ".dll.mdb"))
{
Console.WriteLine("Symbols will be read from " + nakedFileName + ".dll.mdb");
return new MdbReaderProvider();
}
Console.WriteLine("No symbols for " + inputFile);
return null;
}
public static string DestinationFileFor(string outputDir, string assemblyPath)
{
string fileName = Path.GetFileName(assemblyPath);
Debug.Assert(fileName != null, "fileName != null");
return Path.Combine(outputDir, fileName);
}
public static string PrettyPrintType(TypeReference type)
{
// generic instances, such as List<Int32>
if (type.IsGenericInstance)
{
GenericInstanceType giType = (GenericInstanceType)type;
return giType.Name.Substring(0, giType.Name.Length - 2) + "<" + string.Join(", ", giType.GenericArguments.Select(PrettyPrintType).ToArray()) + ">";
}
// generic types, such as List<T>
if (type.HasGenericParameters)
{
return type.Name.Substring(0, type.Name.Length - 2) + "<" + string.Join(", ", type.GenericParameters.Select(x => x.Name).ToArray()) + ">";
}
// non-generic type such as Int
return type.Name;
}
public static ReaderParameters ReaderParameters(string assemblyPath, IEnumerable<string> extraPaths, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath)
{
ReaderParameters parameters = new ReaderParameters {ReadWrite = true};
if (assemblyResolver == null)
assemblyResolver = new DefaultAssemblyResolver();
AddSearchDirectoryHelper helper = new AddSearchDirectoryHelper(assemblyResolver);
helper.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
helper.AddSearchDirectory(UnityEngineDLLDirectoryName());
helper.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath));
helper.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath));
if (extraPaths != null)
{
foreach (string path in extraPaths)
helper.AddSearchDirectory(path);
}
parameters.AssemblyResolver = assemblyResolver;
parameters.SymbolReaderProvider = GetSymbolReaderProvider(assemblyPath);
return parameters;
}
public static WriterParameters GetWriterParameters(ReaderParameters readParams)
{
WriterParameters writeParams = new WriterParameters();
if (readParams.SymbolReaderProvider is PdbReaderProvider)
{
//Log("Will export symbols of pdb format");
writeParams.SymbolWriterProvider = new PdbWriterProvider();
}
else if (readParams.SymbolReaderProvider is MdbReaderProvider)
{
//Log("Will export symbols of mdb format");
writeParams.SymbolWriterProvider = new MdbWriterProvider();
}
return writeParams;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c4ed76daf48547c5abb7c58f8d20886
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "Mirror.Weaver",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1d0b9d21c3ff546a4aa32399dfd33474
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e538d627280d2471b8c72fdea822ca49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,153 @@
// all the [Command] code from NetworkBehaviourProcessor in one place
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class CommandProcessor
{
const string CmdPrefix = "InvokeCmd";
/*
// generates code like:
public void CallCmdThrust(float thrusting, int spin)
{
if (isServer)
{
// we are ON the server, invoke directly
CmdThrust(thrusting, spin);
return;
}
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, networkWriter, cmdName);
}
*/
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
{
MethodDefinition cmd = new MethodDefinition("Call" + md.Name,
MethodAttributes.Public | MethodAttributes.HideBySig,
Weaver.voidType);
// add parameters
foreach (ParameterDefinition pd in md.Parameters)
{
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(cmdWorker);
if (Weaver.GenerateLogErrors)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, "Call Command function " + md.Name));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
// local client check
Instruction localClientLabel = cmdWorker.Create(OpCodes.Nop);
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getBehaviourIsServer));
cmdWorker.Append(cmdWorker.Create(OpCodes.Brfalse, localClientLabel));
// call the cmd function directly.
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
for (int i = 0; i < md.Parameters.Count; i++)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg, i + 1));
}
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
cmdWorker.Append(localClientLabel);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteCreateWriter(cmdWorker);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(cmdWorker, md, false))
return null;
string cmdName = md.Name;
int index = cmdName.IndexOf(CmdPrefix);
if (index > -1)
{
cmdName = cmdName.Substring(CmdPrefix.Length);
}
// invoke internal send and return
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0)); // load 'base.' to call the SendCommand function with
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldtoken, td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, cmdName));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0)); // writer
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.sendCommandInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(cmdWorker);
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
return cmd;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition md)
{
MethodDefinition cmd = new MethodDefinition(CmdPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label = cmdWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(cmdWorker, md.Name, label, "Command");
// setup for reader
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, cmdWorker, false))
return null;
// invoke actual command function
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
return cmd;
}
public static bool ProcessMethodsValidateCommand(MethodDefinition md, CustomAttribute ca)
{
if (!md.Name.StartsWith("Cmd"))
{
Weaver.Error($"{md} must start with Cmd. Consider renaming it to Cmd{md.Name}");
return false;
}
if (md.IsStatic)
{
Weaver.Error($"{md} cannot be static");
return false;
}
// validate
return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,135 @@
// this class generates OnSerialize/OnDeserialize when inheriting from MessageBase
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class MessageClassProcessor
{
public static void Process(TypeDefinition td)
{
Weaver.DLog(td, "MessageClassProcessor Start");
GenerateSerialization(td);
if (Weaver.WeavingFailed)
{
return;
}
GenerateDeSerialization(td);
Weaver.DLog(td, "MessageClassProcessor Done");
}
static void GenerateSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == "Serialize")
return;
}
if (td.Fields.Count == 0)
{
return;
}
// check for self-referencing types
foreach (FieldDefinition field in td.Fields)
{
if (field.FieldType.FullName == td.FullName)
{
Weaver.Error($"{td} has field ${field} that references itself");
return;
}
}
MethodDefinition serializeFunc = new MethodDefinition("Serialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
foreach (FieldDefinition field in td.Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
if (field.FieldType.Resolve().HasGenericParameters && !field.FieldType.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
Weaver.Error($"{field} cannot have generic type {field.FieldType}. Consider creating a class that derives the generic type");
return;
}
if (field.FieldType.Resolve().IsInterface)
{
Weaver.Error($"{field} has unsupported type. Use a concrete class instead of interface {field.FieldType}");
return;
}
MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, field));
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{field} has unsupported type");
return;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
}
static void GenerateDeSerialization(TypeDefinition td)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == "Deserialize")
return;
}
if (td.Fields.Count == 0)
{
return;
}
MethodDefinition serializeFunc = new MethodDefinition("Deserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
foreach (FieldDefinition field in td.Fields)
{
if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
continue;
MethodReference readerFunc = Readers.GetReadFunc(field.FieldType);
if (readerFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
serWorker.Append(serWorker.Create(OpCodes.Stfld, field));
}
else
{
Weaver.Error($"{field} has unsupported type");
return;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3544c9f00f6e5443ea3c30873c5a06ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,77 @@
// this class only shows warnings in case we use SyncVars etc. for MonoBehaviour.
using Mono.CecilX;
namespace Mirror.Weaver
{
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)
{
foreach (CustomAttribute ca in fd.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
Weaver.Error($"[SyncVar] {fd} must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
}
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Weaver.Error($"{fd} is a SyncObject and must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
}
}
}
static void ProcessMethods(TypeDefinition td)
{
// find command and RPC functions
foreach (MethodDefinition md in td.Methods)
{
foreach (CustomAttribute ca in md.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
{
Weaver.Error($"[Command] {md} must be declared inside a NetworkBehaviour");
}
if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
{
Weaver.Error($"[ClienRpc] {md} must be declared inside a NetworkBehaviour");
}
if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
{
Weaver.Error($"[TargetRpc] {md} must be declared inside a NetworkBehaviour");
}
string attributeName = ca.Constructor.DeclaringType.ToString();
switch (attributeName)
{
case "Mirror.ServerAttribute":
Weaver.Error($"[Server] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ServerCallbackAttribute":
Weaver.Error($"[ServerCallback] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ClientAttribute":
Weaver.Error($"[Client] {md} must be declared inside a NetworkBehaviour");
break;
case "Mirror.ClientCallbackAttribute":
Weaver.Error($"[ClientCallback] {md} must be declared inside a NetworkBehaviour");
break;
}
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35c16722912b64af894e4f6668f2e54c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,858 @@
// this class processes SyncVars, Cmds, Rpcs, etc. of NetworkBehaviours
using System;
using System.Linq;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
class NetworkBehaviourProcessor
{
readonly List<FieldDefinition> syncVars = new List<FieldDefinition>();
readonly List<FieldDefinition> syncObjects = new List<FieldDefinition>();
readonly Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>(); // <SyncVarField,NetIdField>
readonly List<MethodDefinition> commands = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcs = new List<MethodDefinition>();
readonly List<EventDefinition> eventRpcs = new List<EventDefinition>();
readonly List<MethodDefinition> commandInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> eventRpcInvocationFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> commandCallFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> clientRpcCallFuncs = new List<MethodDefinition>();
readonly List<MethodDefinition> targetRpcCallFuncs = new List<MethodDefinition>();
readonly TypeDefinition netBehaviourSubclass;
public NetworkBehaviourProcessor(TypeDefinition td)
{
Weaver.DLog(td, "NetworkBehaviourProcessor");
netBehaviourSubclass = td;
}
public void Process()
{
if (netBehaviourSubclass.HasGenericParameters)
{
Weaver.Error($"{netBehaviourSubclass} cannot have generic parameters");
return;
}
Weaver.DLog(netBehaviourSubclass, "Process Start");
MarkAsProcessed(netBehaviourSubclass);
SyncVarProcessor.ProcessSyncVars(netBehaviourSubclass, syncVars, syncObjects, syncVarNetIds);
ProcessMethods();
SyncEventProcessor.ProcessEvents(netBehaviourSubclass, eventRpcs, eventRpcInvocationFuncs);
if (Weaver.WeavingFailed)
{
return;
}
GenerateConstants();
GenerateSerialization();
if (Weaver.WeavingFailed)
{
return;
}
GenerateDeSerialization();
Weaver.DLog(netBehaviourSubclass, "Process Done");
}
/*
generates code like:
if (!NetworkClient.active)
Debug.LogError((object) "Command function CmdRespawn called on server.");
which is used in InvokeCmd, InvokeRpc, etc.
*/
public static void WriteClientActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// client active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on server."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
/*
generates code like:
if (!NetworkServer.active)
Debug.LogError((object) "Command CmdMsgWhisper called on client.");
*/
public static void WriteServerActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// server active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on client."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
public static void WriteSetupLocals(ILProcessor worker)
{
worker.Body.InitLocals = true;
worker.Body.Variables.Add(new VariableDefinition(Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
}
public static void WriteCreateWriter(ILProcessor worker)
{
// create writer
worker.Append(worker.Create(OpCodes.Call, Weaver.GetPooledWriterReference));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
public static void WriteRecycleWriter(ILProcessor worker)
{
// NetworkWriterPool.Recycle(writer);
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, Weaver.RecycleWriterReference));
}
public static bool WriteArguments(ILProcessor worker, MethodDefinition md, bool skipFirst)
{
// write each argument
short argNum = 1;
foreach (ParameterDefinition pd in md.Parameters)
{
if (argNum == 1 && skipFirst)
{
argNum += 1;
continue;
}
MethodReference writeFunc = Writers.GetWriteFunc(pd.ParameterType);
if (writeFunc == null)
{
Weaver.Error($"{md} has invalid parameter {pd}" );
return false;
}
// use built-in writer func on writer object
worker.Append(worker.Create(OpCodes.Ldloc_0)); // writer object
worker.Append(worker.Create(OpCodes.Ldarg, argNum)); // argument
worker.Append(worker.Create(OpCodes.Call, writeFunc)); // call writer func on writer object
argNum += 1;
}
return true;
}
#region mark / check type as processed
public const string ProcessedFunctionName = "MirrorProcessed";
// by adding an empty MirrorProcessed() function
public static bool WasProcessed(TypeDefinition td)
{
return td.Methods.Any(method => method.Name == ProcessedFunctionName);
}
public static void MarkAsProcessed(TypeDefinition td)
{
if (!WasProcessed(td))
{
MethodDefinition versionMethod = new MethodDefinition(ProcessedFunctionName, MethodAttributes.Private, Weaver.voidType);
ILProcessor worker = versionMethod.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ret));
td.Methods.Add(versionMethod);
}
}
#endregion
void GenerateConstants()
{
if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && eventRpcs.Count == 0 && syncObjects.Count == 0)
return;
Weaver.DLog(netBehaviourSubclass, " GenerateConstants ");
// find static constructor
MethodDefinition cctor = null;
bool cctorFound = false;
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
if (md.Name == ".cctor")
{
cctor = md;
cctorFound = true;
}
}
if (cctor != null)
{
// remove the return opcode from end of function. will add our own later.
if (cctor.Body.Instructions.Count != 0)
{
Instruction ret = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1);
}
else
{
Weaver.Error($"{netBehaviourSubclass} has invalid class constructor");
return;
}
}
}
else
{
// make one!
cctor = new MethodDefinition(".cctor", MethodAttributes.Private |
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
Weaver.voidType);
}
// find instance constructor
MethodDefinition ctor = null;
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
if (md.Name == ".ctor")
{
ctor = md;
Instruction ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1);
}
else
{
Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
return;
}
break;
}
}
if (ctor == null)
{
Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
return;
}
ILProcessor ctorWorker = ctor.Body.GetILProcessor();
ILProcessor cctorWorker = cctor.Body.GetILProcessor();
for (int i = 0; i < commands.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], commands[i].Name);
}
for (int i = 0; i < clientRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name);
}
for (int i = 0; i < targetRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name);
}
for (int i = 0; i < eventRpcs.Count; ++i)
{
GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[i].Name);
}
foreach (FieldDefinition fd in syncObjects)
{
SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd);
}
cctorWorker.Append(cctorWorker.Create(OpCodes.Ret));
if (!cctorFound)
{
netBehaviourSubclass.Methods.Add(cctor);
}
// finish ctor
ctorWorker.Append(ctorWorker.Create(OpCodes.Ret));
// in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late
netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit;
}
/*
// This generates code like:
NetworkBehaviour.RegisterCommandDelegate(base.GetType(), "CmdThrust", new NetworkBehaviour.CmdDelegate(ShipControl.InvokeCmdCmdThrust));
*/
void GenerateRegisterCommandDelegate(ILProcessor awakeWorker, MethodReference registerMethod, MethodDefinition func, string cmdName)
{
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldtoken, netBehaviourSubclass));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldstr, cmdName));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldnull));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldftn, func));
awakeWorker.Append(awakeWorker.Create(OpCodes.Newobj, Weaver.CmdDelegateConstructor));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, registerMethod));
}
void GenerateSerialization()
{
Weaver.DLog(netBehaviourSubclass, " GenerateSerialization");
foreach (MethodDefinition m in netBehaviourSubclass.Methods)
{
if (m.Name == "OnSerialize")
return;
}
if (syncVars.Count == 0)
{
// no synvars, no need for custom OnSerialize
return;
}
MethodDefinition serialize = new MethodDefinition("OnSerialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.boolType);
serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
serialize.Body.InitLocals = true;
// loc_0, this local variable is to determine if any variable was dirty
VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType);
serialize.Body.Variables.Add(dirtyLocal);
MethodReference baseSerialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnSerialize");
if (baseSerialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to result of base.OnSerialize()
}
// Generates: if (forceAll);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (FieldDefinition syncVar in syncVars)
{
// Generates a writer call for each sync variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
}
// always return true if forceAll
// Generates: return true
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (forceAll);
serWorker.Append(initialStateLabel);
// write dirty bits before the data fields
// Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ());
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Call, Writers.GetWriteFunc(Weaver.uint64Type)));
// generate a writer call for any dirty variable in this class
// start at number of syncvars in parent
int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName);
foreach (FieldDefinition syncVar in syncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
// Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL)
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); // 8 bytes = long
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
// Generates a call to the writer for that field
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
// something was dirty
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to true
serWorker.Append(varLabel);
dirtyBit += 1;
}
if (Weaver.GenerateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
// generate: return dirtyLocal
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ret));
netBehaviourSubclass.Methods.Add(serialize);
}
public static int GetChannelId(CustomAttribute ca)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == "channel")
{
return (int)customField.Argument.Value;
}
}
return 0;
}
void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize)
{
// check for Hook function
if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod))
{
return;
}
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName ||
syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = syncVarNetIds[syncVar];
VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type);
deserialize.Body.Variables.Add(tmpValue);
// read id and store in a local variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type)));
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
if (foundMethod != null)
{
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
// because we send/receive the netID, not the GameObject/NetworkIdentity
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference));
else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
// set the netid field
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
}
else
{
MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType);
if (readFunc == null)
{
Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
return;
}
VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType);
deserialize.Body.Variables.Add(tmpValue);
// read value and put it in a local variable
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
if (foundMethod != null)
{
// call hook
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
// set the property
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
}
}
void GenerateDeSerialization()
{
Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization");
foreach (MethodDefinition m in netBehaviourSubclass.Methods)
{
if (m.Name == "OnDeserialize")
return;
}
if (syncVars.Count == 0)
{
// no synvars, no need for custom OnDeserialize
return;
}
MethodDefinition serialize = new MethodDefinition("OnDeserialize",
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
Weaver.voidType);
serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
// setup local for dirty bits
serialize.Body.InitLocals = true;
VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
serialize.Body.Variables.Add(dirtyBitsLocal);
MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnDeserialize");
if (baseDeserialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
}
// Generates: if (initialState);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (FieldDefinition syncVar in syncVars)
{
DeserializeField(syncVar, serWorker, serialize);
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (initialState);
serWorker.Append(initialStateLabel);
// get dirty bits
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint64Type)));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
// conditionally read each syncvar
int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); // start at number of syncvars in parent
foreach (FieldDefinition syncVar in syncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
// check if dirty bit is set
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit));
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
DeserializeField(syncVar, serWorker, serialize);
serWorker.Append(varLabel);
dirtyBit += 1;
}
if (Weaver.GenerateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
netBehaviourSubclass.Methods.Add(serialize);
}
public static bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst)
{
int count = 0;
// read cmd args from NetworkReader
foreach (ParameterDefinition arg in md.Parameters)
{
if (count++ == 0 && skipFirst)
{
continue;
}
MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); //?
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Call, readFunc));
// conversion.. is this needed?
if (arg.ParameterType.FullName == Weaver.singleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R4));
}
else if (arg.ParameterType.FullName == Weaver.doubleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R8));
}
}
else
{
Weaver.Error($"{md} has invalid parameter {arg}. Unsupported type {arg.ParameterType}, use a supported Mirror type instead");
return false;
}
}
return true;
}
public static void AddInvokeParameters(ICollection<ParameterDefinition> collection)
{
collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, Weaver.NetworkBehaviourType2));
collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
}
public static bool ProcessMethodsValidateFunction(MethodReference md)
{
if (md.ReturnType.FullName == Weaver.IEnumeratorType.FullName)
{
Weaver.Error($"{md} cannot be a coroutine");
return false;
}
if (md.ReturnType.FullName != Weaver.voidType.FullName)
{
Weaver.Error($"{md} cannot return a value. Make it void instead");
return false;
}
if (md.HasGenericParameters)
{
Weaver.Error($"{md} cannot have generic parameters");
return false;
}
return true;
}
public static bool ProcessMethodsValidateParameters(MethodReference md, CustomAttribute ca)
{
for (int i = 0; i < md.Parameters.Count; ++i)
{
ParameterDefinition p = md.Parameters[i];
if (p.IsOut)
{
Weaver.Error($"{md} cannot have out parameters");
return false;
}
if (p.IsOptional)
{
Weaver.Error($"{md} cannot have optional parameters");
return false;
}
if (p.ParameterType.Resolve().IsAbstract)
{
Weaver.Error($"{md} has invalid parameter {p}. Use concrete type instead of abstract type {p.ParameterType}");
return false;
}
if (p.ParameterType.IsByReference)
{
Weaver.Error($"{md} has invalid parameter {p}. Use supported type instead of reference type {p.ParameterType}");
return false;
}
// TargetRPC is an exception to this rule and can have a NetworkConnection as first parameter
if (p.ParameterType.FullName == Weaver.NetworkConnectionType.FullName &&
!(ca.AttributeType.FullName == Weaver.TargetRpcType.FullName && i == 0))
{
Weaver.Error($"{md} has invalid parameer {p}. Cannot pass NeworkConnections");
return false;
}
if (p.ParameterType.Resolve().IsDerivedFrom(Weaver.ComponentType))
{
if (p.ParameterType.FullName != Weaver.NetworkIdentityType.FullName)
{
Weaver.Error($"{md} has invalid parameter {p}. Cannot pass components in remote method calls");
return false;
}
}
}
return true;
}
void ProcessMethods()
{
HashSet<string> names = new HashSet<string>();
// find command and RPC functions
foreach (MethodDefinition md in netBehaviourSubclass.Methods)
{
foreach (CustomAttribute ca in md.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
{
ProcessCommand(names, md, ca);
break;
}
if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
{
ProcessTargetRpc(names, md, ca);
break;
}
if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
{
ProcessClientRpc(names, md, ca);
break;
}
}
}
// cmds
foreach (MethodDefinition md in commandInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in commandCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
// rpcs
foreach (MethodDefinition md in clientRpcInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in targetRpcInvocationFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in clientRpcCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
foreach (MethodDefinition md in targetRpcCallFuncs)
{
netBehaviourSubclass.Methods.Add(md);
}
}
void ProcessClientRpc(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!RpcProcessor.ProcessMethodsValidateRpc(md, ca))
{
return;
}
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate ClientRpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
clientRpcs.Add(md);
MethodDefinition rpcFunc = RpcProcessor.ProcessRpcInvoke(netBehaviourSubclass, md);
if (rpcFunc != null)
{
clientRpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = RpcProcessor.ProcessRpcCall(netBehaviourSubclass, md, ca);
if (rpcCallFunc != null)
{
clientRpcCallFuncs.Add(rpcCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
}
}
void ProcessTargetRpc(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!TargetRpcProcessor.ProcessMethodsValidateTargetRpc(md, ca))
return;
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate Target Rpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
targetRpcs.Add(md);
MethodDefinition rpcFunc = TargetRpcProcessor.ProcessTargetRpcInvoke(netBehaviourSubclass, md);
if (rpcFunc != null)
{
targetRpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = TargetRpcProcessor.ProcessTargetRpcCall(netBehaviourSubclass, md, ca);
if (rpcCallFunc != null)
{
targetRpcCallFuncs.Add(rpcCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
}
}
void ProcessCommand(HashSet<string> names, MethodDefinition md, CustomAttribute ca)
{
if (!CommandProcessor.ProcessMethodsValidateCommand(md, ca))
return;
if (names.Contains(md.Name))
{
Weaver.Error("Duplicate Command name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
return;
}
names.Add(md.Name);
commands.Add(md);
MethodDefinition cmdFunc = CommandProcessor.ProcessCommandInvoke(netBehaviourSubclass, md);
if (cmdFunc != null)
{
commandInvocationFuncs.Add(cmdFunc);
}
MethodDefinition cmdCallFunc = CommandProcessor.ProcessCommandCall(netBehaviourSubclass, md, ca);
if (cmdCallFunc != null)
{
commandCallFuncs.Add(cmdCallFunc);
Weaver.WeaveLists.replaceMethods[md.FullName] = cmdCallFunc;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8118d606be3214e5d99943ec39530dd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,356 @@
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class PropertySiteProcessor
{
public static void ProcessSitesModule(ModuleDefinition moduleDef)
{
DateTime startTime = DateTime.Now;
//Search through the types
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessSiteClass(td);
}
}
if (Weaver.WeaveLists.generateContainerClass != null)
{
moduleDef.Types.Add(Weaver.WeaveLists.generateContainerClass);
Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.WeaveLists.generateContainerClass);
foreach (MethodDefinition f in Weaver.WeaveLists.generatedReadFunctions)
{
Weaver.CurrentAssembly.MainModule.ImportReference(f);
}
foreach (MethodDefinition f in Weaver.WeaveLists.generatedWriteFunctions)
{
Weaver.CurrentAssembly.MainModule.ImportReference(f);
}
}
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(td, md);
}
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessSiteClass(nested);
}
}
static void ProcessSiteMethod(TypeDefinition td, 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("CallCmd") ||
md.Name.StartsWith("InvokeCmd") ||
md.Name.StartsWith("InvokeRpc") ||
md.Name.StartsWith("InvokeSyn"))
return;
if (md.Body != null && md.Body.Instructions != null)
{
// TODO move this to NetworkBehaviourProcessor
foreach (CustomAttribute attr in md.CustomAttributes)
{
switch (attr.Constructor.DeclaringType.ToString())
{
case "Mirror.ServerAttribute":
InjectServerGuard(td, md, true);
break;
case "Mirror.ServerCallbackAttribute":
InjectServerGuard(td, md, false);
break;
case "Mirror.ClientAttribute":
InjectClientGuard(td, md, true);
break;
case "Mirror.ClientCallbackAttribute":
InjectClientGuard(td, md, false);
break;
}
}
for (int iCount= 0; iCount < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[iCount];
iCount += ProcessInstruction(md, instr, iCount);
}
}
}
static void InjectServerGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
{
if (!Weaver.IsNetworkBehaviour(td))
{
Weaver.Error($"[Server] {md} must be declared in a NetworkBehaviour");
return;
}
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Server] function '" + md.FullName + "' called on client"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
static void InjectClientGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
{
if (!Weaver.IsNetworkBehaviour(td))
{
Weaver.Error($"[Client] {md} must be declared in a NetworkBehaviour");
return;
}
ILProcessor worker = md.Body.GetILProcessor();
Instruction top = md.Body.Instructions[0];
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Client] function '" + md.FullName + "' called on server"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
}
InjectGuardParameters(md, worker, top);
InjectGuardReturnValue(md, worker, top);
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}
// 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.Call || instr.OpCode == OpCodes.Callvirt)
{
if (instr.Operand is MethodReference opMethod)
{
ProcessInstructionMethod(md, instr, opMethod, iCount);
}
}
if (instr.OpCode == OpCodes.Stfld)
{
// this instruction sets the value of a field. cache the field reference.
if (instr.Operand is FieldDefinition opField)
{
ProcessInstructionSetterField(md, instr, opField);
}
}
if (instr.OpCode == OpCodes.Ldfld)
{
// this instruction gets the value of a field. cache the field reference.
if (instr.Operand is FieldDefinition opField)
{
ProcessInstructionGetterField(md, instr, opField);
}
}
if (instr.OpCode == OpCodes.Ldflda)
{
// loading a field by reference, watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
if (instr.Operand is FieldDefinition opField)
{
return ProcessInstructionLoadAddress(md, instr, opField, 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;
}
static void ProcessInstructionMethod(MethodDefinition md, Instruction instr, MethodReference opMethodRef, int iCount)
{
//DLog(td, "ProcessInstructionMethod " + opMethod.Name);
if (opMethodRef.Name == "Invoke")
{
// Events use an "Invoke" method to call the delegate.
// this code replaces the "Invoke" instruction with the generated "Call***" instruction which send the event to the server.
// but the "Invoke" instruction is called on the event field - where the "call" instruction is not.
// so the earlier instruction that loads the event field is replaced with a Noop.
// go backwards until find a ldfld instruction that matches ANY event
bool found = false;
while (iCount > 0 && !found)
{
iCount -= 1;
Instruction inst = md.Body.Instructions[iCount];
if (inst.OpCode == OpCodes.Ldfld)
{
FieldReference opField = inst.Operand as FieldReference;
// find replaceEvent with matching name
// NOTE: original weaver compared .Name, not just the MethodDefinition,
// that's why we use dict<string,method>.
if (Weaver.WeaveLists.replaceEvents.TryGetValue(opField.Name, out MethodDefinition replacement))
{
instr.Operand = replacement;
inst.OpCode = OpCodes.Nop;
found = true;
}
}
}
}
else
{
// should it be replaced?
// NOTE: original weaver compared .FullName, not just the MethodDefinition,
// that's why we use dict<string,method>.
if (Weaver.WeaveLists.replaceMethods.TryGetValue(opMethodRef.FullName, out MethodDefinition replacement))
{
//DLog(td, " replacing " + md.Name + ":" + i);
instr.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
}
// 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();
if (elementType.IsPrimitive)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
worker.InsertBefore(top, worker.Create(OpCodes.Stind_I4));
}
else
{
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.FullName != Weaver.voidType.FullName)
{
if (md.ReturnType.IsPrimitive)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
}
else
{
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));
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d48f1ab125e9940a995603796bccc59e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,98 @@
using System;
using Mono.CecilX;
using UnityEditor.Compilation;
using System.Linq;
using System.Collections.Generic;
using System.IO;
namespace Mirror.Weaver
{
public static class ReaderWriterProcessor
{
// find all readers and writers and register them
public static void ProcessReadersAndWriters(AssemblyDefinition CurrentAssembly)
{
Readers.Init();
Writers.Init();
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
{
if (unityAsm.name != CurrentAssembly.Name.Name)
{
try
{
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
{
ProcessAssemblyClasses(CurrentAssembly, assembly);
}
}
catch(FileNotFoundException)
{
// During first import, this gets called before some assemblies
// are built, just skip them
}
}
}
ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
}
static void ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
{
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)
{
LoadWriters(CurrentAssembly, klass);
LoadReaders(CurrentAssembly, klass);
}
}
}
static void LoadWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the writers in this class. Skip the ones with wrong signature
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 2)
continue;
if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkWriter")
continue;
if (method.ReturnType.FullName != "System.Void")
continue;
if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
continue;
TypeReference dataType = method.Parameters[1].ParameterType;
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
}
}
static void LoadReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
{
// register all the reader in this class. Skip the ones with wrong signature
foreach (MethodDefinition method in klass.Methods)
{
if (method.Parameters.Count != 1)
continue;
if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkReader")
continue;
if (method.ReturnType.FullName == "System.Void")
continue;
if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
continue;
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3263602f0a374ecd8d08588b1fc2f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,110 @@
// all the [Rpc] code from NetworkBehaviourProcessor in one place
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class RpcProcessor
{
public const string RpcPrefix = "InvokeRpc";
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md)
{
MethodDefinition rpc = new MethodDefinition(
RpcPrefix + md.Name,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(rpcWorker, md.Name, label, "RPC");
// setup for reader
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td));
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, rpcWorker, false))
return null;
// invoke actual command function
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
return rpc;
}
/* generates code like:
public void CallRpcTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32((uint)param);
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
}
*/
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
{
MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in md.Parameters)
{
rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker);
NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, false))
return null;
string rpcName = md.Name;
int index = rpcName.IndexOf(RpcPrefix);
if (index > -1)
{
rpcName = rpcName.Substring(RpcPrefix.Length);
}
// invoke SendInternal and return
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldtoken, td));
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(rpcWorker);
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
return rpc;
}
public static bool ProcessMethodsValidateRpc(MethodDefinition md, CustomAttribute ca)
{
if (!md.Name.StartsWith("Rpc"))
{
Weaver.Error($"{md} must start with Rpc. Consider renaming it to Rpc{md.Name}");
return false;
}
if (md.IsStatic)
{
Weaver.Error($"{md} must not be static");
return false;
}
// validate
return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3cb7051ff41947e59bba58bdd2b73fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
// this class generates OnSerialize/OnDeserialize for SyncLists
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class SyncDictionaryProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29e4a45f69822462ab0b15adda962a29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,152 @@
// all the SyncEvent code from NetworkBehaviourProcessor in one place
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncEventProcessor
{
public static MethodDefinition ProcessEventInvoke(TypeDefinition td, EventDefinition ed)
{
// find the field that matches the event
FieldDefinition eventField = null;
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FullName == ed.FullName)
{
eventField = fd;
break;
}
}
if (eventField == null)
{
Weaver.Error($"{td} not found. Did you declare the event?");
return null;
}
MethodDefinition cmd = new MethodDefinition("InvokeSyncEvent" + ed.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label1 = cmdWorker.Create(OpCodes.Nop);
Instruction label2 = cmdWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(cmdWorker, ed.Name, label1, "Event");
// null event check
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
cmdWorker.Append(cmdWorker.Create(OpCodes.Brtrue, label2));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
cmdWorker.Append(label2);
// setup reader
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
// read the event arguments
MethodReference invoke = Resolvers.ResolveMethod(eventField.FieldType, Weaver.CurrentAssembly, "Invoke");
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(invoke.Resolve(), cmdWorker, false))
return null;
// invoke actual event delegate function
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, invoke));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
return cmd;
}
public static MethodDefinition ProcessEventCall(TypeDefinition td, EventDefinition ed, CustomAttribute ca)
{
MethodReference invoke = Resolvers.ResolveMethod(ed.EventType, Weaver.CurrentAssembly, "Invoke");
MethodDefinition evt = new MethodDefinition("Call" + ed.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in invoke.Parameters)
{
evt.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor evtWorker = evt.Body.GetILProcessor();
Instruction label = evtWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteSetupLocals(evtWorker);
NetworkBehaviourProcessor.WriteServerActiveCheck(evtWorker, ed.Name, label, "Event");
NetworkBehaviourProcessor.WriteCreateWriter(evtWorker);
// write all the arguments that the user passed to the syncevent
if (!NetworkBehaviourProcessor.WriteArguments(evtWorker, invoke.Resolve(), false))
return null;
// invoke interal send and return
evtWorker.Append(evtWorker.Create(OpCodes.Ldarg_0)); // this
evtWorker.Append(evtWorker.Create(OpCodes.Ldtoken, td));
evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
evtWorker.Append(evtWorker.Create(OpCodes.Ldstr, ed.Name));
evtWorker.Append(evtWorker.Create(OpCodes.Ldloc_0)); // writer
evtWorker.Append(evtWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.sendEventInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(evtWorker);
evtWorker.Append(evtWorker.Create(OpCodes.Ret));
return evt;
}
public static void ProcessEvents(TypeDefinition td, List<EventDefinition> events, List<MethodDefinition> eventInvocationFuncs)
{
// find events
foreach (EventDefinition ed in td.Events)
{
foreach (CustomAttribute ca in ed.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncEventType.FullName)
{
if (!ed.Name.StartsWith("Event"))
{
Weaver.Error($"{ed} must start with Event. Consider renaming it to Event{ed.Name}");
return;
}
if (ed.EventType.Resolve().HasGenericParameters)
{
Weaver.Error($"{ed} must not have generic parameters. Consider creating a new class that inherits from {ed.EventType} instead");
return;
}
events.Add(ed);
MethodDefinition eventFunc = ProcessEventInvoke(td, ed);
if (eventFunc == null)
{
return;
}
td.Methods.Add(eventFunc);
eventInvocationFuncs.Add(eventFunc);
Weaver.DLog(td, "ProcessEvent " + ed);
MethodDefinition eventCallFunc = ProcessEventCall(td, ed, ca);
td.Methods.Add(eventCallFunc);
Weaver.WeaveLists.replaceEvents[ed.Name] = eventCallFunc; // original weaver compares .Name, not EventDefinition.
Weaver.DLog(td, " Event: " + ed.Name);
break;
}
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5d8b25543a624384944b599e5a832a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,5 @@
// This file was removed in Mirror 3.4.9
// The purpose of this file is to get the old file overwritten
// when users update from the asset store to prevent a flood of errors
// from having the old file still in the project as a straggler.
// This file will be dropped from the Asset Store package in May 2019

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97068e5d8cc14490b85933feb119d827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
// this class generates OnSerialize/OnDeserialize for SyncLists
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
static class SyncListProcessor
{
/// <summary>
/// Generates serialization methods for synclists
/// </summary>
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeItem", "DeserializeItem");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f3445268e45d437fac325837aff3246
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,5 @@
// This file was removed in Mirror 3.4.9
// The purpose of this file is to get the old file overwritten
// when users update from the asset store to prevent a flood of errors
// from having the old file still in the project as a straggler.
// This file will be dropped from the Asset Store package in May 2019

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28fb192f6a9bc1247b90aa4710f6d34f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,89 @@
// SyncObject code
using System;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectInitializer
{
public static void GenerateSyncObjectInitializer(ILProcessor methodWorker, FieldDefinition fd)
{
// call syncobject constructor
GenerateSyncObjectInstanceInitializer(methodWorker, fd);
// register syncobject in network behaviour
GenerateSyncObjectRegistration(methodWorker, fd);
}
// generates 'syncListInt = new SyncListInt()' if user didn't do that yet
static void GenerateSyncObjectInstanceInitializer(ILProcessor ctorWorker, FieldDefinition fd)
{
// check the ctor's instructions for an Stfld op-code for this specific sync list field.
foreach (Instruction ins in ctorWorker.Body.Instructions)
{
if (ins.OpCode.Code == Code.Stfld)
{
FieldDefinition field = (FieldDefinition)ins.Operand;
if (field.DeclaringType == fd.DeclaringType && field.Name == fd.Name)
{
// Already initialized by the user in the field definition, e.g:
// public SyncListInt Foo = new SyncListInt();
return;
}
}
}
// Not initialized by the user in the field definition, e.g:
// public SyncListInt Foo;
MethodReference objectConstructor;
try
{
objectConstructor = Weaver.CurrentAssembly.MainModule.ImportReference(fd.FieldType.Resolve().Methods.First<MethodDefinition>(x => x.Name == ".ctor" && !x.HasParameters));
}
catch (Exception)
{
Weaver.Error($"{fd} does not have a default constructor");
return;
}
ctorWorker.Append(ctorWorker.Create(OpCodes.Ldarg_0));
ctorWorker.Append(ctorWorker.Create(OpCodes.Newobj, objectConstructor));
ctorWorker.Append(ctorWorker.Create(OpCodes.Stfld, fd));
}
public static bool ImplementsSyncObject(TypeReference typeRef)
{
try
{
// value types cant inherit from SyncObject
if (typeRef.IsValueType)
{
return false;
}
return typeRef.Resolve().ImplementsInterface(Weaver.SyncObjectType);
}
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 methodWorker, FieldDefinition fd)
{
methodWorker.Append(methodWorker.Create(OpCodes.Ldarg_0));
methodWorker.Append(methodWorker.Create(OpCodes.Ldarg_0));
methodWorker.Append(methodWorker.Create(OpCodes.Ldfld, fd));
methodWorker.Append(methodWorker.Create(OpCodes.Call, Weaver.InitSyncObjectReference));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d02219b00b3674e59a2151f41e791688
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,123 @@
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncObjectProcessor
{
/// <summary>
/// Generates the serialization and deserialization methods for a specified generic argument
/// </summary>
/// <param name="td">The type of the class that needs serialization methods</param>
/// <param name="genericArgument">Which generic argument to serialize, 0 is the first one</param>
/// <param name="serializeMethod">The name of the serialize method</param>
/// <param name="deserializeMethod">The name of the deserialize method</param>
public static void GenerateSerialization(TypeDefinition td, int genericArgument, string serializeMethod, string deserializeMethod)
{
// find item type
GenericInstanceType gt = (GenericInstanceType)td.BaseType;
if (gt.GenericArguments.Count <= genericArgument)
{
Weaver.Error($"{td} should have {genericArgument} generic arguments");
return;
}
TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[genericArgument]);
Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);
MethodReference writeItemFunc = GenerateSerialization(serializeMethod, td, itemType);
if (Weaver.WeavingFailed)
{
return;
}
MethodReference readItemFunc = GenerateDeserialization(deserializeMethod, td, itemType);
if (readItemFunc == null || writeItemFunc == null)
return;
Weaver.DLog(td, "SyncObjectProcessor Done");
}
// serialization of individual element
static MethodReference GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateSerialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == methodName)
return m;
}
MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
if (itemType.IsGenericInstance)
{
Weaver.Error($"{td} cannot have generic elements {itemType}");
return null;
}
MethodReference writeFunc = Writers.GetWriteFunc(itemType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead");
return null;
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
td.Methods.Add(serializeFunc);
return serializeFunc;
}
static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType)
{
Weaver.DLog(td, " GenerateDeserialization");
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == methodName)
return m;
}
MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Public |
MethodAttributes.HideBySig,
itemType);
deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor serWorker = deserializeFunction.Body.GetILProcessor();
MethodReference readerFunc = Readers.GetReadFunc(itemType);
if (readerFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
serWorker.Append(serWorker.Create(OpCodes.Ret));
}
else
{
Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead");
return null;
}
td.Methods.Add(deserializeFunction);
return deserializeFunction;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78f71efc83cde4917b7d21efa90bcc9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,335 @@
// all the [SyncVar] code from NetworkBehaviourProcessor in one place
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncVarProcessor
{
const int SyncVarLimit = 64; // ulong = 64 bytes
// returns false for error, not for no-hook-exists
public static bool CheckForHookFunction(TypeDefinition td, FieldDefinition syncVar, out MethodDefinition foundMethod)
{
foundMethod = null;
foreach (CustomAttribute ca in syncVar.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == "hook")
{
string hookFunctionName = customField.Argument.Value as string;
foreach (MethodDefinition m in td.Methods)
{
if (m.Name == hookFunctionName)
{
if (m.Parameters.Count == 1)
{
if (m.Parameters[0].ParameterType != syncVar.FieldType)
{
Weaver.Error($"{m} should have signature:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}");
return false;
}
foundMethod = m;
return true;
}
Weaver.Error($"{m} should have signature:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}");
return false;
}
}
Weaver.Error($"No hook implementation found for {syncVar}. Add this method to your class:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}" );
return false;
}
}
}
}
return true;
}
public static MethodDefinition ProcessSyncVarGet(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 getWorker = get.Body.GetILProcessor();
// [SyncVar] GameObject?
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
{
// return this.GetSyncVarGameObject(ref field, uint netId);
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarGameObjectReference));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
// [SyncVar] NetworkIdentity?
else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarNetworkIdentityReference));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
// [SyncVar] int, string, etc.
else
{
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, fd));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
}
public static MethodDefinition ProcessSyncVarSet(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,
Weaver.voidType);
ILProcessor setWorker = set.Body.GetILProcessor();
CheckForHookFunction(td, fd, out MethodDefinition hookFunctionMethod);
if (hookFunctionMethod != null)
{
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
Instruction label = setWorker.Create(OpCodes.Nop);
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.NetworkServerGetLocalClientActive));
setWorker.Append(setWorker.Create(OpCodes.Brfalse, label));
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.getSyncVarHookGuard));
setWorker.Append(setWorker.Create(OpCodes.Brtrue, label));
// setSyncVarHookGuard(dirtyBit, true);
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_1));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
// call hook
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
setWorker.Append(setWorker.Create(OpCodes.Call, hookFunctionMethod));
// setSyncVarHookGuard(dirtyBit, false);
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_0));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
setWorker.Append(label);
}
// this
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
// new value to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
// reference to field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, fd));
// dirty bit
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit)); // 8 byte integer aka long
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
{
// reference to netId Field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarGameObjectReference));
}
else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// reference to netId Field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarNetworkIdentityReference));
}
else
{
// make generic version of SetSyncVar with field type
GenericInstanceMethod gm = new GenericInstanceMethod(Weaver.setSyncVarReference);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
setWorker.Append(setWorker.Create(OpCodes.Call, gm));
}
setWorker.Append(setWorker.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 + " " + Weaver.gameObjectType);
// GameObject/NetworkIdentity SyncVars have a new field for netId
FieldDefinition netIdField = null;
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName ||
fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
FieldAttributes.Private,
Weaver.uint32Type);
syncVarNetIds[fd] = netIdField;
}
MethodDefinition get = ProcessSyncVarGet(fd, originalName, netIdField);
MethodDefinition set = ProcessSyncVarSet(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.FullName == Weaver.gameObjectType.FullName ||
fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
Weaver.WeaveLists.replacementGetterProperties[fd] = get;
}
}
public static void ProcessSyncVars(TypeDefinition td, List<FieldDefinition> syncVars, List<FieldDefinition> syncObjects, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds)
{
int numSyncVars = 0;
// 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.GetSyncVarStart(td.BaseType.FullName);
syncVarNetIds.Clear();
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
foreach (CustomAttribute ca in fd.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
TypeDefinition resolvedField = fd.FieldType.Resolve();
if (resolvedField.IsDerivedFrom(Weaver.NetworkBehaviourType))
{
Weaver.Error($"{fd} has invalid type. SyncVars cannot be NetworkBehaviours");
return;
}
if (resolvedField.IsDerivedFrom(Weaver.ScriptableObjectType))
{
Weaver.Error($"{fd} has invalid type. SyncVars cannot be scriptable objects");
return;
}
if ((fd.Attributes & FieldAttributes.Static) != 0)
{
Weaver.Error($"{fd} cannot be static");
return;
}
if (resolvedField.HasGenericParameters)
{
Weaver.Error($"{fd} has invalid type. SyncVars cannot have generic parameters");
return;
}
if (resolvedField.IsInterface)
{
Weaver.Error($"{fd} has invalid type. Use a concrete type instead of interface {fd.FieldType}");
return;
}
if (fd.FieldType.IsArray)
{
Weaver.Error($"{fd} has invalid type. Use SyncLists instead of arrays");
return;
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Log.Warning($"{fd} has [SyncVar] attribute. SyncLists should not be marked with SyncVar");
break;
}
syncVars.Add(fd);
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
dirtyBitCounter += 1;
numSyncVars += 1;
if (dirtyBitCounter == SyncVarLimit)
{
Weaver.Error($"{td} has too many SyncVars. Consider refactoring your class into multiple components");
return;
}
break;
}
}
if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType))
{
if (fd.IsStatic)
{
Weaver.Error($"{fd} cannot be static");
return;
}
syncObjects.Add(fd);
}
}
// add all the new SyncVar __netId fields
foreach (FieldDefinition fd in syncVarNetIds.Values)
{
td.Fields.Add(fd);
}
Weaver.SetNumSyncVars(td.FullName, numSyncVars);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f52c39bddd95d42b88f9cd554dfd9198
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,151 @@
// all the [TargetRpc] code from NetworkBehaviourProcessor in one place
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class TargetRpcProcessor
{
const string TargetRpcPrefix = "InvokeTargetRpc";
// 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.FullName == Weaver.NetworkConnectionType.FullName;
}
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md)
{
MethodDefinition rpc = new MethodDefinition(RpcProcessor.RpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(rpcWorker, md.Name, label, "TargetRPC");
// setup for reader
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td));
// NetworkConnection parameter is optional
bool hasNetworkConnection = HasNetworkConnectionParameter(md);
if (hasNetworkConnection)
{
//ClientScene.readyconnection
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.ReadyConnectionReference));
}
// process reader parameters and skip first one if first one is NetworkConnection
if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, rpcWorker, hasNetworkConnection))
return null;
// invoke actual command function
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
return rpc;
}
/* generates code like:
public void CallTargetTest (NetworkConnection conn, int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
}
or if optional:
public void CallTargetTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
}
*/
public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
{
MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add parameters
foreach (ParameterDefinition pd in md.Parameters)
{
rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker);
NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker);
// NetworkConnection parameter is optional
bool hasNetworkConnection = HasNetworkConnectionParameter(md);
// write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection)
if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, hasNetworkConnection))
return null;
string rpcName = md.Name;
int index = rpcName.IndexOf(TargetRpcPrefix);
if (index > -1)
{
rpcName = rpcName.Substring(TargetRpcPrefix.Length);
}
// invoke SendInternal and return
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
if (HasNetworkConnectionParameter(md))
{
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1)); // connection
}
else
{
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldnull)); // null
}
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldtoken, td));
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendTargetRpcInternal));
NetworkBehaviourProcessor.WriteRecycleWriter(rpcWorker);
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
return rpc;
}
public static bool ProcessMethodsValidateTargetRpc(MethodDefinition md, CustomAttribute ca)
{
if (!md.Name.StartsWith("Target"))
{
Weaver.Error($"{md} must start with Target. Consider renaming it to Target{md.Name}");
return false;
}
if (md.IsStatic)
{
Weaver.Error($"{md} must not be static");
return false;
}
if (!NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md))
{
return false;
}
// validate
return NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb3ce6c6f3f2942ae88178b86f5a8282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Mirror.Weaver
{
public static class Log
{
public static Action<string> WarningMethod;
public static Action<string> ErrorMethod;
public static void Warning(string msg)
{
WarningMethod("Mirror.Weaver warning: " + msg);
}
public static void Error(string msg)
{
ErrorMethod("Mirror.Weaver error: " + msg);
}
}
public static class Program
{
public static bool Process(string unityEngine, string netDLL, string outputDirectory, string[] assemblies, string[] extraAssemblyPaths, Action<string> printWarning, Action<string> printError)
{
CheckDLLPath(unityEngine);
CheckDLLPath(netDLL);
CheckOutputDirectory(outputDirectory);
CheckAssemblies(assemblies);
Log.WarningMethod = printWarning;
Log.ErrorMethod = printError;
return Weaver.WeaveAssemblies(assemblies, extraAssemblyPaths, outputDirectory, unityEngine, netDLL);
}
static void CheckDLLPath(string path)
{
if (!File.Exists(path))
throw new Exception("dll could not be located at " + path + "!");
}
static void CheckAssemblies(IEnumerable<string> assemblyPaths)
{
foreach (string assemblyPath in assemblyPaths)
CheckAssemblyPath(assemblyPath);
}
static void CheckAssemblyPath(string assemblyPath)
{
if (!File.Exists(assemblyPath))
throw new Exception("Assembly " + assemblyPath + " does not exist!");
}
static void CheckOutputDirectory(string outputDir)
{
if (outputDir != null && !Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a21c60c40a4c4d679c2b71a7c40882e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,371 @@
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
public static class Readers
{
const int MaxRecursionCount = 128;
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 variable, int recursionCount = 0)
{
if (readFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
TypeDefinition td = variable.Resolve();
if (td == null)
{
Weaver.Error($"{variable} is not a supported type");
return null;
}
if (variable.IsByReference)
{
// error??
Weaver.Error($"{variable} is not a supported reference type");
return null;
}
MethodDefinition newReaderFunc;
if (variable.IsArray)
{
newReaderFunc = GenerateArrayReadFunc(variable, recursionCount);
}
else if (td.IsEnum)
{
return GetReadFunc(td.GetEnumUnderlyingType(), recursionCount);
}
else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
newReaderFunc = GenerateArraySegmentReadFunc(variable, recursionCount);
}
else
{
newReaderFunc = GenerateStructReadFunction(variable, recursionCount);
}
if (newReaderFunc == null)
{
Weaver.Error($"{variable} is not a supported type");
return null;
}
RegisterReadFunc(variable.FullName, newReaderFunc);
return newReaderFunc;
}
static void RegisterReadFunc(string name, MethodDefinition newReaderFunc)
{
readFuncs[name] = newReaderFunc;
Weaver.WeaveLists.generatedReadFunctions.Add(newReaderFunc);
Weaver.ConfirmGeneratedCodeClass();
Weaver.WeaveLists.generateContainerClass.Methods.Add(newReaderFunc);
}
static MethodDefinition GenerateArrayReadFunc(TypeReference variable, int recursionCount)
{
if (!variable.IsArrayType())
{
Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported");
return null;
}
TypeReference elementType = variable.GetElementType();
MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1);
if (elementReadFunc == null)
{
return null;
}
string functionName = "_ReadArray" + variable.GetElementType().Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
readerFunc.Body.InitLocals = true;
ILProcessor worker = readerFunc.Body.GetILProcessor();
// int length = reader.ReadPackedInt32();
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(Weaver.int32Type)));
worker.Append(worker.Create(OpCodes.Stloc_0));
// if (length < 0) {
// return null
// }
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Bge, labelEmptyArray));
// return null
worker.Append(worker.Create(OpCodes.Ldnull));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(labelEmptyArray);
// T value = new T[length];
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Newarr, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Stloc_1));
// for (int i=0; i< length ; i++) {
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_2));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
// value[i] = reader.ReadT();
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, elementReadFunc));
worker.Append(worker.Create(OpCodes.Stobj, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_2));
// loop while check
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return value;
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int recursionCount)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1);
if (elementReadFunc == null)
{
return null;
}
string functionName = "_ReadArraySegment_" + variable.GetElementType().Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
// int lengh
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
// T[] array
readerFunc.Body.Variables.Add(new VariableDefinition(elementType.MakeArrayType()));
// int i;
readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
readerFunc.Body.InitLocals = true;
ILProcessor worker = readerFunc.Body.GetILProcessor();
// int length = reader.ReadPackedInt32();
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, GetReadFunc(Weaver.int32Type)));
worker.Append(worker.Create(OpCodes.Stloc_0));
// T[] array = new int[length]
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Newarr, elementType));
worker.Append(worker.Create(OpCodes.Stloc_1));
// loop through array and deserialize each element
// generates code like this
// for (int i=0; i< length ; i++)
// {
// value[i] = reader.ReadXXX();
// }
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_2));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
{
// value[i] = reader.ReadT();
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldelema, elementType));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, elementReadFunc));
worker.Append(worker.Create(OpCodes.Stobj, elementType));
}
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_2));
// loop while check
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_2));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return new ArraySegment<T>(array);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Newobj, Weaver.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
static MethodDefinition GenerateStructReadFunction(TypeReference variable, int recursionCount)
{
if (recursionCount > MaxRecursionCount)
{
Weaver.Error($"{variable} can't be deserialized because it references itself");
return null;
}
if (!Weaver.IsValidTypeToGenerate(variable.Resolve()))
{
return null;
}
string functionName = "_Read" + variable.Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
variable);
// create local for return value
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
readerFunc.Body.InitLocals = true;
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
ILProcessor worker = readerFunc.Body.GetILProcessor();
if (variable.IsValueType)
{
// structs are created with Initobj
worker.Append(worker.Create(OpCodes.Ldloca, 0));
worker.Append(worker.Create(OpCodes.Initobj, variable));
}
else
{
// classes are created with their constructor
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
if (ctor == null)
{
Weaver.Error($"{variable} can't be deserialized bcause i has no default constructor");
return null;
}
worker.Append(worker.Create(OpCodes.Newobj, ctor));
worker.Append(worker.Create(OpCodes.Stloc_0));
}
uint fields = 0;
foreach (FieldDefinition field in variable.Resolve().Fields)
{
if (field.IsStatic || field.IsPrivate)
continue;
// 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, recursionCount + 1);
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, readFunc));
}
else
{
Weaver.Error($"{field} has an unsupported type");
return null;
}
worker.Append(worker.Create(OpCodes.Stfld, field));
fields++;
}
if (fields == 0)
{
Log.Warning($"{variable} has no public or non-static fields to deserialize");
}
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ret));
return readerFunc;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be40277098a024539bf63d0205cae824
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,141 @@
// 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)
{
//Console.WriteLine("ResolveMethod " + t.ToString () + " " + name);
if (tr == null)
{
Weaver.Error("Type missing for " + name);
return null;
}
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
{
if (methodRef.Name == name)
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
Weaver.Error($"{tr}.{name}() not found");
return null;
}
// TODO reuse ResolveMethod in here after Weaver.fail was removed
public static MethodReference ResolveMethodInParents(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
if (tr == null)
{
Weaver.Error("Type missing for " + name);
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 ResolveMethodInParents(tr.Resolve().BaseType, scriptDef, name);
}
// System.Byte[] arguments need a version with a string
public static MethodReference ResolveMethodWithArg(TypeReference tr, AssemblyDefinition scriptDef, string name, string argTypeFullName)
{
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
{
if (methodRef.Name == name)
{
if (methodRef.Parameters.Count == 1)
{
if (methodRef.Parameters[0].ParameterType.FullName == argTypeFullName)
{
return scriptDef.MainModule.ImportReference(methodRef);
}
}
}
}
Weaver.Error($"{tr}.{name}({argTypeFullName}) not found");
return null;
}
// reuse ResolveMethodWithArg string version
public static MethodReference ResolveMethodWithArg(TypeReference tr, AssemblyDefinition scriptDef, string name, TypeReference argType)
{
return ResolveMethodWithArg(tr, scriptDef, name, argType.FullName);
}
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 GenericInstanceMethod ResolveMethodGeneric(TypeReference t, AssemblyDefinition scriptDef, string name, TypeReference genericType)
{
foreach (MethodDefinition methodRef in t.Resolve().Methods)
{
if (methodRef.Name == name)
{
if (methodRef.Parameters.Count == 0)
{
if (methodRef.GenericParameters.Count == 1)
{
MethodReference tmp = scriptDef.MainModule.ImportReference(methodRef);
GenericInstanceMethod gm = new GenericInstanceMethod(tmp);
gm.GenericArguments.Add(genericType);
if (gm.GenericArguments[0].FullName == genericType.FullName)
{
return gm;
}
}
}
}
}
Weaver.Error($"{t}.{name}<{genericType}>() not found");
return null;
}
public static FieldReference ResolveField(TypeReference tr, AssemblyDefinition scriptDef, string name)
{
foreach (FieldDefinition fd in tr.Resolve().Fields)
{
if (fd.Name == name)
{
return scriptDef.MainModule.ImportReference(fd);
}
}
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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3039a59c76aec43c797ad66930430367
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,598 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
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>();
// [Command]/[ClientRpc] functions that should be replaced. dict<originalMethodFullName, replacement>
public Dictionary<string, MethodDefinition> replaceMethods = new Dictionary<string, MethodDefinition>();
// [SyncEvent] invoke functions that should be replaced. dict<originalEventName, replacement>
public Dictionary<string, MethodDefinition> replaceEvents = new Dictionary<string, MethodDefinition>();
public List<MethodDefinition> generatedReadFunctions = new List<MethodDefinition>();
public List<MethodDefinition> generatedWriteFunctions = new List<MethodDefinition>();
public TypeDefinition generateContainerClass;
// amount of SyncVars per class. dict<className, amount>
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
}
class Weaver
{
public static WeaverLists WeaveLists { get; private set; }
public static AssemblyDefinition CurrentAssembly { get; private set; }
public static ModuleDefinition CorLibModule { get; private set; }
public static AssemblyDefinition UnityAssembly { get; private set; }
public static AssemblyDefinition NetAssembly { get; private set; }
public static bool WeavingFailed { get; private set; }
public static bool GenerateLogErrors { get; set; }
// private properties
static readonly bool DebugLogEnabled = true;
// Network types
public static TypeReference NetworkBehaviourType;
public static TypeReference NetworkBehaviourType2;
public static TypeReference MonoBehaviourType;
public static TypeReference ScriptableObjectType;
public static TypeReference NetworkConnectionType;
public static TypeReference MessageBaseType;
public static TypeReference SyncListType;
public static TypeReference SyncSetType;
public static TypeReference SyncDictionaryType;
public static MethodReference NetworkBehaviourDirtyBitsReference;
public static MethodReference GetPooledWriterReference;
public static MethodReference RecycleWriterReference;
public static TypeReference NetworkClientType;
public static TypeReference NetworkServerType;
public static TypeReference NetworkReaderType;
public static TypeReference NetworkWriterType;
public static TypeReference NetworkIdentityType;
public static TypeReference IEnumeratorType;
public static TypeReference ClientSceneType;
public static MethodReference ReadyConnectionReference;
public static TypeReference ComponentType;
public static TypeReference CmdDelegateReference;
public static MethodReference CmdDelegateConstructor;
public static MethodReference NetworkServerGetActive;
public static MethodReference NetworkServerGetLocalClientActive;
public static MethodReference NetworkClientGetActive;
public static MethodReference getBehaviourIsServer;
// custom attribute types
public static TypeReference SyncVarType;
public static TypeReference CommandType;
public static TypeReference ClientRpcType;
public static TypeReference TargetRpcType;
public static TypeReference SyncEventType;
public static TypeReference SyncObjectType;
public static MethodReference InitSyncObjectReference;
// array segment
public static TypeReference ArraySegmentType;
public static MethodReference ArraySegmentConstructorReference;
public static MethodReference ArraySegmentArrayReference;
public static MethodReference ArraySegmentOffsetReference;
public static MethodReference ArraySegmentCountReference;
// system types
public static TypeReference voidType;
public static TypeReference singleType;
public static TypeReference doubleType;
public static TypeReference boolType;
public static TypeReference int64Type;
public static TypeReference uint64Type;
public static TypeReference int32Type;
public static TypeReference uint32Type;
public static TypeReference objectType;
public static TypeReference typeType;
public static TypeReference gameObjectType;
public static TypeReference transformType;
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 registerEventDelegateReference;
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;
public static MethodReference sendEventInternal;
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 int GetSyncVarStart(string className)
{
return WeaveLists.numSyncVars.ContainsKey(className)
? WeaveLists.numSyncVars[className]
: 0;
}
public static void SetNumSyncVars(string className, int num)
{
WeaveLists.numSyncVars[className] = num;
}
internal static void ConfirmGeneratedCodeClass()
{
if (WeaveLists.generateContainerClass == null)
{
WeaveLists.generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode",
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass,
objectType);
const MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
MethodDefinition method = new MethodDefinition(".ctor", methodAttributes, voidType);
method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
method.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Resolvers.ResolveMethod(objectType, CurrentAssembly, ".ctor")));
method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
WeaveLists.generateContainerClass.Methods.Add(method);
}
}
static bool ProcessNetworkBehaviourType(TypeDefinition td)
{
if (!NetworkBehaviourProcessor.WasProcessed(td))
{
DLog(td, "Found NetworkBehaviour " + td.FullName);
NetworkBehaviourProcessor proc = new NetworkBehaviourProcessor(td);
proc.Process();
return true;
}
return false;
}
static void SetupUnityTypes()
{
gameObjectType = UnityAssembly.MainModule.GetType("UnityEngine.GameObject");
transformType = UnityAssembly.MainModule.GetType("UnityEngine.Transform");
NetworkClientType = NetAssembly.MainModule.GetType("Mirror.NetworkClient");
NetworkServerType = NetAssembly.MainModule.GetType("Mirror.NetworkServer");
SyncVarType = NetAssembly.MainModule.GetType("Mirror.SyncVarAttribute");
CommandType = NetAssembly.MainModule.GetType("Mirror.CommandAttribute");
ClientRpcType = NetAssembly.MainModule.GetType("Mirror.ClientRpcAttribute");
TargetRpcType = NetAssembly.MainModule.GetType("Mirror.TargetRpcAttribute");
SyncEventType = NetAssembly.MainModule.GetType("Mirror.SyncEventAttribute");
SyncObjectType = NetAssembly.MainModule.GetType("Mirror.SyncObject");
}
static void SetupCorLib()
{
AssemblyNameReference name = AssemblyNameReference.Parse("mscorlib");
ReaderParameters parameters = new ReaderParameters
{
AssemblyResolver = CurrentAssembly.MainModule.AssemblyResolver
};
CorLibModule = CurrentAssembly.MainModule.AssemblyResolver.Resolve(name, parameters).MainModule;
}
static TypeReference ImportCorLibType(string fullName)
{
TypeDefinition type = CorLibModule.GetType(fullName) ?? CorLibModule.ExportedTypes.First(t => t.FullName == fullName).Resolve();
if (type != null)
{
return CurrentAssembly.MainModule.ImportReference(type);
}
Error("Failed to import mscorlib type: " + fullName + " because Resolve failed. (Might happen when trying to Resolve in NetStandard dll, see also: https://github.com/vis2k/Mirror/issues/791)");
return null;
}
static void SetupTargetTypes()
{
// system types
SetupCorLib();
voidType = ImportCorLibType("System.Void");
singleType = ImportCorLibType("System.Single");
doubleType = ImportCorLibType("System.Double");
boolType = ImportCorLibType("System.Boolean");
int64Type = ImportCorLibType("System.Int64");
uint64Type = ImportCorLibType("System.UInt64");
int32Type = ImportCorLibType("System.Int32");
uint32Type = ImportCorLibType("System.UInt32");
objectType = ImportCorLibType("System.Object");
typeType = ImportCorLibType("System.Type");
IEnumeratorType = ImportCorLibType("System.Collections.IEnumerator");
ArraySegmentType = ImportCorLibType("System.ArraySegment`1");
ArraySegmentArrayReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Array");
ArraySegmentCountReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Count");
ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Offset");
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, CurrentAssembly, ".ctor");
NetworkReaderType = NetAssembly.MainModule.GetType("Mirror.NetworkReader");
NetworkWriterType = NetAssembly.MainModule.GetType("Mirror.NetworkWriter");
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_active");
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_localClientActive");
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, CurrentAssembly, "get_active");
CmdDelegateReference = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour/CmdDelegate");
CmdDelegateConstructor = Resolvers.ResolveMethod(CmdDelegateReference, CurrentAssembly, ".ctor");
CurrentAssembly.MainModule.ImportReference(gameObjectType);
CurrentAssembly.MainModule.ImportReference(transformType);
TypeReference networkIdentityTmp = NetAssembly.MainModule.GetType("Mirror.NetworkIdentity");
NetworkIdentityType = CurrentAssembly.MainModule.ImportReference(networkIdentityTmp);
NetworkBehaviourType = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour");
NetworkBehaviourType2 = CurrentAssembly.MainModule.ImportReference(NetworkBehaviourType);
NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection");
MonoBehaviourType = UnityAssembly.MainModule.GetType("UnityEngine.MonoBehaviour");
ScriptableObjectType = UnityAssembly.MainModule.GetType("UnityEngine.ScriptableObject");
NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection");
NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1");
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
TypeDefinition NetworkWriterPoolType = NetAssembly.MainModule.GetType("Mirror.NetworkWriterPool");
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, CurrentAssembly, "GetWriter");
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, CurrentAssembly, "Recycle");
ComponentType = UnityAssembly.MainModule.GetType("UnityEngine.Component");
ClientSceneType = NetAssembly.MainModule.GetType("Mirror.ClientScene");
ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, CurrentAssembly, "get_readyConnection");
getBehaviourIsServer = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "get_isServer");
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(NetworkBehaviourType, CurrentAssembly, "RegisterCommandDelegate");
registerRpcDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "RegisterRpcDelegate");
registerEventDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "RegisterEventDelegate");
getTypeReference = Resolvers.ResolveMethod(objectType, CurrentAssembly, "GetType");
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, CurrentAssembly, "GetTypeFromHandle");
logErrorReference = Resolvers.ResolveMethod(UnityAssembly.MainModule.GetType("UnityEngine.Debug"), CurrentAssembly, "LogError");
logWarningReference = Resolvers.ResolveMethod(UnityAssembly.MainModule.GetType("UnityEngine.Debug"), CurrentAssembly, "LogWarning");
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendCommandInternal");
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendRPCInternal");
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendTargetRPCInternal");
sendEventInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendEventInternal");
SyncObjectType = CurrentAssembly.MainModule.ImportReference(SyncObjectType);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "InitSyncObject");
}
public static bool IsNetworkBehaviour(TypeDefinition td)
{
return td.IsDerivedFrom(NetworkBehaviourType);
}
public static bool IsValidTypeToGenerate(TypeDefinition variable)
{
// a valid type is a simple class or struct. so we generate only code for types we dont know, and if they are not inside
// this assembly it must mean that we are trying to serialize a variable outside our scope. and this will fail.
// no need to report an error here, the caller will report a better error
string assembly = CurrentAssembly.MainModule.Name;
return variable.Module.Name == assembly;
}
static void CheckMonoBehaviour(TypeDefinition td)
{
if (td.IsDerivedFrom(MonoBehaviourType))
{
MonoBehaviourProcessor.Process(td);
}
}
static bool CheckNetworkBehaviour(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!IsNetworkBehaviour(td))
{
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.FullName == NetworkBehaviourType.FullName)
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool didWork = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
didWork |= ProcessNetworkBehaviourType(behaviour);
}
return didWork;
}
static bool CheckMessageBase(TypeDefinition td)
{
if (!td.IsClass)
return false;
bool didWork = false;
// are ANY parent classes MessageBase
TypeReference parent = td.BaseType;
while (parent != null)
{
if (parent.FullName == MessageBaseType.FullName)
{
MessageClassProcessor.Process(td);
didWork = true;
break;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
// check for embedded types
foreach (TypeDefinition embedded in td.NestedTypes)
{
didWork |= CheckMessageBase(embedded);
}
return didWork;
}
static bool CheckSyncList(TypeDefinition td)
{
if (!td.IsClass)
return false;
bool didWork = false;
// are ANY parent classes SyncListStruct
TypeReference parent = td.BaseType;
while (parent != null)
{
if (parent.FullName.StartsWith(SyncListType.FullName, StringComparison.Ordinal))
{
SyncListProcessor.Process(td);
didWork = true;
break;
}
if (parent.FullName.StartsWith(SyncSetType.FullName, StringComparison.Ordinal))
{
SyncListProcessor.Process(td);
didWork = true;
break;
}
if (parent.FullName.StartsWith(SyncDictionaryType.FullName, StringComparison.Ordinal))
{
SyncDictionaryProcessor.Process(td);
didWork = true;
break;
}
try
{
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for pluins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
// check for embedded types
foreach (TypeDefinition embedded in td.NestedTypes)
{
didWork |= CheckSyncList(embedded);
}
return didWork;
}
static bool Weave(string assName, IEnumerable<string> dependencies, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir)
{
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());
asmResolver.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath));
asmResolver.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath));
if (dependencies != null)
{
foreach (string path in dependencies)
{
asmResolver.AddSearchDirectory(path);
}
}
SetupTargetTypes();
System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew();
ReaderWriterProcessor.ProcessReadersAndWriters(CurrentAssembly);
rwstopwatch.Stop();
Console.WriteLine("Find all reader and writers took " + rwstopwatch.ElapsedMilliseconds + " milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine("Script Module: {0}", moduleDefinition.Name);
// Process each NetworkBehaviour
bool didWork = false;
// We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first.
for (int pass = 0; pass < 2; pass++)
{
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
foreach (TypeDefinition td in moduleDefinition.Types)
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
try
{
if (pass == 0)
{
didWork |= CheckSyncList(td);
}
else
{
didWork |= CheckNetworkBehaviour(td);
didWork |= CheckMessageBase(td);
}
}
catch (Exception ex)
{
Error(ex.ToString());
throw ex;
}
}
if (WeavingFailed)
{
return false;
}
}
watch.Stop();
Console.WriteLine("Pass: " + pass + " took " + watch.ElapsedMilliseconds + " milliseconds");
}
if (didWork)
{
// this must be done for ALL code, not just NetworkBehaviours
try
{
PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule);
}
catch (Exception e)
{
Log.Error("ProcessPropertySites exception: " + e);
return false;
}
if (WeavingFailed)
{
//Log.Error("Failed phase II.");
return false;
}
// write to outputDir if specified, otherwise perform in-place write
WriterParameters writeParams = new WriterParameters { WriteSymbols = true };
if (outputDir != null)
{
CurrentAssembly.Write(Helpers.DestinationFileFor(outputDir, assName), writeParams);
}
else
{
CurrentAssembly.Write(writeParams);
}
}
}
return true;
}
public static bool WeaveAssemblies(IEnumerable<string> assemblies, IEnumerable<string> dependencies, string outputDir, string unityEngineDLLPath, string mirrorNetDLLPath)
{
WeavingFailed = false;
WeaveLists = new WeaverLists();
using (UnityAssembly = AssemblyDefinition.ReadAssembly(unityEngineDLLPath))
using (NetAssembly = AssemblyDefinition.ReadAssembly(mirrorNetDLLPath))
{
SetupUnityTypes();
try
{
foreach (string ass in assemblies)
{
if (!Weave(ass, dependencies, unityEngineDLLPath, mirrorNetDLLPath, outputDir))
{
return false;
}
}
}
catch (Exception e)
{
Log.Error("Exception :" + e);
return false;
}
}
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de160f52931054064852f2afd7e7a86f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,350 @@
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class Writers
{
const int MaxRecursionCount = 128;
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;
}
public static MethodReference GetWriteFunc(TypeReference variable, int recursionCount = 0)
{
if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
{
return foundFunc;
}
if (variable.IsByReference)
{
// error??
Weaver.Error($"{variable} has unsupported type. Use one of Mirror supported types instead");
return null;
}
MethodDefinition newWriterFunc;
if (variable.IsArray)
{
newWriterFunc = GenerateArrayWriteFunc(variable, recursionCount);
}
else if (variable.Resolve().IsEnum)
{
return GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), recursionCount);
}
else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
{
newWriterFunc = GenerateArraySegmentWriteFunc(variable, recursionCount);
}
else
{
newWriterFunc = GenerateStructWriterFunction(variable, recursionCount);
}
if (newWriterFunc == null)
{
return null;
}
RegisterWriteFunc(variable.FullName, newWriterFunc);
return newWriterFunc;
}
static void RegisterWriteFunc(string name, MethodDefinition newWriterFunc)
{
writeFuncs[name] = newWriterFunc;
Weaver.WeaveLists.generatedWriteFunctions.Add(newWriterFunc);
Weaver.ConfirmGeneratedCodeClass();
Weaver.WeaveLists.generateContainerClass.Methods.Add(newWriterFunc);
}
static MethodDefinition GenerateStructWriterFunction(TypeReference variable, int recursionCount)
{
if (recursionCount > MaxRecursionCount)
{
Weaver.Error($"{variable} can't be serialized because it references itself");
return null;
}
if (!Weaver.IsValidTypeToGenerate(variable.Resolve()))
{
return null;
}
string functionName = "_Write" + variable.Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
ILProcessor worker = writerFunc.Body.GetILProcessor();
uint fields = 0;
foreach (FieldDefinition field in variable.Resolve().Fields)
{
if (field.IsStatic || field.IsPrivate)
continue;
if (field.FieldType.Resolve().HasGenericParameters)
{
Weaver.Error($"{field} has unsupported type. Create a derived class instead of using generics");
return null;
}
if (field.FieldType.Resolve().IsInterface)
{
Weaver.Error($"{field} has unsupported type. Use a concrete class instead of an interface");
return null;
}
MethodReference writeFunc = GetWriteFunc(field.FieldType, recursionCount + 1);
if (writeFunc != null)
{
fields++;
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldfld, field));
worker.Append(worker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.Error($"{field} has unsupported type. Use a type supported by Mirror instead");
return null;
}
}
if (fields == 0)
{
Log.Warning($" {variable} has no no public or non-static fields to serialize");
}
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount)
{
if (!variable.IsArrayType())
{
Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported");
return null;
}
TypeReference elementType = variable.GetElementType();
MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
if (elementWriteFunc == null)
{
return null;
}
string functionName = "_WriteArray" + variable.GetElementType().Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.InitLocals = true;
ILProcessor worker = writerFunc.Body.GetILProcessor();
// if (value == null)
// {
// writer.WritePackedInt32(-1);
// return;
// }
Instruction labelNull = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Brtrue, labelNull));
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldc_I4_M1));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
worker.Append(worker.Create(OpCodes.Ret));
// int length = value.Length;
worker.Append(labelNull);
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldlen));
worker.Append(worker.Create(OpCodes.Stloc_0));
// writer.WritePackedInt32(length);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
// for (int i=0; i< value.length; i++) {
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_1));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
// writer.Write(value[i]);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Ldobj, variable.GetElementType()));
worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_1));
// end for loop
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Ldlen));
worker.Append(worker.Create(OpCodes.Conv_I4));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
static MethodDefinition GenerateArraySegmentWriteFunc(TypeReference variable, int recursionCount)
{
GenericInstanceType genericInstance = (GenericInstanceType)variable;
TypeReference elementType = genericInstance.GenericArguments[0];
MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
if (elementWriteFunc == null)
{
return null;
}
string functionName = "_WriteArraySegment_" + elementType.Name + "_";
if (variable.DeclaringType != null)
{
functionName += variable.DeclaringType.Name;
}
else
{
functionName += "None";
}
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition(functionName,
MethodAttributes.Public |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
writerFunc.Body.InitLocals = true;
ILProcessor worker = writerFunc.Body.GetILProcessor();
MethodReference countref = Weaver.ArraySegmentCountReference.MakeHostInstanceGeneric(genericInstance);
// int length = value.Count;
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, countref));
worker.Append(worker.Create(OpCodes.Stloc_0));
// writer.WritePackedInt32(length);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
// Loop through the ArraySegment<T> and call the writer for each element.
// generates this:
// for (int i=0; i< length; i++)
// {
// writer.Write(value.Array[i + value.Offset]);
// }
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Stloc_1));
Instruction labelHead = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Br, labelHead));
// loop body
Instruction labelBody = worker.Create(OpCodes.Nop);
worker.Append(labelBody);
{
// writer.Write(value.Array[i + value.Offset]);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentArrayReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentOffsetReference.MakeHostInstanceGeneric(genericInstance)));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Ldelema, elementType));
worker.Append(worker.Create(OpCodes.Ldobj, elementType));
worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
}
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
worker.Append(worker.Create(OpCodes.Add));
worker.Append(worker.Create(OpCodes.Stloc_1));
// end for loop
worker.Append(labelHead);
worker.Append(worker.Create(OpCodes.Ldloc_1));
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Blt, labelBody));
// return
worker.Append(worker.Create(OpCodes.Ret));
return writerFunc;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a90060ad76ea044aba613080dd922709
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,3 @@
The Mirror DLLs in the Plugins folder are MIT licensed:
https://github.com/vis2k/Mirror

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dbf30d11d3879431f87403d009e47bf7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 05eb4061e2eb94061b9a08c918fff99b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ce126b4e1a7d13b4c865cd92929f13c3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a8ec6c9ba48cb7348879483314d7ce07 guid: a078fc7c0dc14d047a28dea9c93fd259
PluginImporter: PluginImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2
@ -9,7 +9,6 @@ PluginImporter:
isPreloaded: 0 isPreloaded: 0
isOverridable: 0 isOverridable: 0
isExplicitlyReferenced: 0 isExplicitlyReferenced: 0
validateReferences: 1
platformData: platformData:
- first: - first:
Any: Any:

View file

@ -0,0 +1,32 @@
fileFormatVersion: 2
guid: 534d998d93b238041bddcd864f7f1088
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show more