mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-17 18:40:33 +00:00
Merged master
This commit is contained in:
commit
68ef7f2ffd
@ -22,14 +22,15 @@ public static void AddDefineSymbols()
|
||||
HashSet<string> defines = new HashSet<string>(currentDefines.Split(';'))
|
||||
{
|
||||
"MIRROR",
|
||||
"MIRROR_79_OR_NEWER",
|
||||
"MIRROR_81_OR_NEWER",
|
||||
"MIRROR_82_OR_NEWER",
|
||||
"MIRROR_83_OR_NEWER",
|
||||
"MIRROR_84_OR_NEWER",
|
||||
"MIRROR_85_OR_NEWER",
|
||||
"MIRROR_86_OR_NEWER",
|
||||
"MIRROR_89_OR_NEWER"
|
||||
"MIRROR_89_OR_NEWER",
|
||||
"MIRROR_90_OR_NEWER",
|
||||
"MIRROR_93_OR_NEWER"
|
||||
};
|
||||
|
||||
// only touch PlayerSettings if we actually modified it,
|
||||
|
@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL("https://mirror-networking.gitbook.io/docs/components/network-lerp-rigidbody")]
|
||||
[Obsolete("Use the new NetworkRigidbodyReliable/Unreliable component with Snapshot Interpolation instead.")]
|
||||
public class NetworkLerpRigidbody : NetworkBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[SerializeField] internal Rigidbody target = null;
|
||||
|
||||
[Tooltip("How quickly current velocity approaches target velocity")]
|
||||
[SerializeField] float lerpVelocityAmount = 0.5f;
|
||||
|
||||
[Tooltip("How quickly current position approaches target position")]
|
||||
[SerializeField] float lerpPositionAmount = 0.5f;
|
||||
|
||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||
[SerializeField] bool clientAuthority = false;
|
||||
|
||||
double nextSyncTime;
|
||||
|
||||
[SyncVar()]
|
||||
Vector3 targetVelocity;
|
||||
|
||||
[SyncVar()]
|
||||
Vector3 targetPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Ignore value if is host or client with Authority
|
||||
/// </summary>
|
||||
bool IgnoreSync => isServer || ClientWithAuthority;
|
||||
|
||||
bool ClientWithAuthority => clientAuthority && isOwned;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
if (target == null)
|
||||
target = GetComponent<Rigidbody>();
|
||||
|
||||
syncDirection = SyncDirection.ClientToServer;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (isServer)
|
||||
SyncToClients();
|
||||
else if (ClientWithAuthority)
|
||||
SendToServer();
|
||||
}
|
||||
|
||||
void SyncToClients()
|
||||
{
|
||||
targetVelocity = target.velocity;
|
||||
targetPosition = target.position;
|
||||
}
|
||||
|
||||
void SendToServer()
|
||||
{
|
||||
double now = NetworkTime.localTime; // Unity 2019 doesn't have Time.timeAsDouble yet
|
||||
if (now > nextSyncTime)
|
||||
{
|
||||
nextSyncTime = now + syncInterval;
|
||||
CmdSendState(target.velocity, target.position);
|
||||
}
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendState(Vector3 velocity, Vector3 position)
|
||||
{
|
||||
target.velocity = velocity;
|
||||
target.position = position;
|
||||
targetVelocity = velocity;
|
||||
targetPosition = position;
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (IgnoreSync) { return; }
|
||||
|
||||
target.velocity = Vector3.Lerp(target.velocity, targetVelocity, lerpVelocityAmount);
|
||||
target.position = Vector3.Lerp(target.position, targetPosition, lerpPositionAmount);
|
||||
// add velocity to position as position would have moved on server at that velocity
|
||||
target.position += target.velocity * Time.fixedDeltaTime;
|
||||
|
||||
// TODO does this also need to sync acceleration so and update velocity?
|
||||
}
|
||||
}
|
||||
}
|
@ -1,354 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL("https://mirror-networking.gitbook.io/docs/components/network-rigidbody")]
|
||||
[Obsolete("Use the new NetworkRigidbodyReliable/Unreliable component with Snapshot Interpolation instead.")]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[SerializeField] internal Rigidbody target = null;
|
||||
|
||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||
public bool clientAuthority = false;
|
||||
|
||||
[Header("Velocity")]
|
||||
[Tooltip("Syncs Velocity every SyncInterval")]
|
||||
[SerializeField] bool syncVelocity = true;
|
||||
|
||||
[Tooltip("Set velocity to 0 each frame (only works if syncVelocity is false")]
|
||||
[SerializeField] bool clearVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float velocitySensitivity = 0.1f;
|
||||
|
||||
[Header("Angular Velocity")]
|
||||
[Tooltip("Syncs AngularVelocity every SyncInterval")]
|
||||
[SerializeField] bool syncAngularVelocity = true;
|
||||
|
||||
[Tooltip("Set angularVelocity to 0 each frame (only works if syncAngularVelocity is false")]
|
||||
[SerializeField] bool clearAngularVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float angularVelocitySensitivity = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Values sent on client with authority after they are sent to the server
|
||||
/// </summary>
|
||||
readonly ClientSyncState previousValue = new ClientSyncState();
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
if (target == null)
|
||||
target = GetComponent<Rigidbody>();
|
||||
|
||||
syncDirection = SyncDirection.ClientToServer;
|
||||
}
|
||||
|
||||
#region Sync vars
|
||||
|
||||
[SyncVar(hook = nameof(OnVelocityChanged))]
|
||||
Vector3 velocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularVelocityChanged))]
|
||||
Vector3 angularVelocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnIsKinematicChanged))]
|
||||
bool isKinematic;
|
||||
|
||||
[SyncVar(hook = nameof(OnUseGravityChanged))]
|
||||
bool useGravity;
|
||||
|
||||
[SyncVar(hook = nameof(OnuDragChanged))]
|
||||
float drag;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularDragChanged))]
|
||||
float angularDrag;
|
||||
|
||||
/// <summary>
|
||||
/// Ignore value if is host or client with Authority
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IgnoreSync => isServer || ClientWithAuthority;
|
||||
|
||||
bool ClientWithAuthority => clientAuthority && isOwned;
|
||||
|
||||
void OnVelocityChanged(Vector3 _, Vector3 newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.velocity = newValue;
|
||||
}
|
||||
|
||||
void OnAngularVelocityChanged(Vector3 _, Vector3 newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularVelocity = newValue;
|
||||
}
|
||||
|
||||
void OnIsKinematicChanged(bool _, bool newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.isKinematic = newValue;
|
||||
}
|
||||
|
||||
void OnUseGravityChanged(bool _, bool newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.useGravity = newValue;
|
||||
}
|
||||
|
||||
void OnuDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.drag = newValue;
|
||||
}
|
||||
|
||||
void OnAngularDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularDrag = newValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (isServer)
|
||||
SyncToClients();
|
||||
else if (ClientWithAuthority)
|
||||
SendToServer();
|
||||
}
|
||||
|
||||
internal void FixedUpdate()
|
||||
{
|
||||
if (clearAngularVelocity && !syncAngularVelocity)
|
||||
target.angularVelocity = Vector3.zero;
|
||||
|
||||
if (clearVelocity && !syncVelocity)
|
||||
target.velocity = Vector3.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates sync var values on server so that they sync to the client
|
||||
/// </summary>
|
||||
[Server]
|
||||
void SyncToClients()
|
||||
{
|
||||
// only update if they have changed more than Sensitivity
|
||||
|
||||
Vector3 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
|
||||
|
||||
if (velocityChanged)
|
||||
{
|
||||
velocity = currentVelocity;
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
angularVelocity = currentAngularVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
|
||||
// other rigidbody settings
|
||||
isKinematic = target.isKinematic;
|
||||
useGravity = target.useGravity;
|
||||
drag = target.drag;
|
||||
angularDrag = target.angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses Command to send values to server
|
||||
/// </summary>
|
||||
[Client]
|
||||
void SendToServer()
|
||||
{
|
||||
if (!isOwned)
|
||||
{
|
||||
Debug.LogWarning("SendToServer called without authority");
|
||||
return;
|
||||
}
|
||||
|
||||
SendVelocity();
|
||||
SendRigidBodySettings();
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendVelocity()
|
||||
{
|
||||
double now = NetworkTime.localTime; // Unity 2019 doesn't have Time.timeAsDouble yet
|
||||
if (now < previousValue.nextSyncTime)
|
||||
return;
|
||||
|
||||
Vector3 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
|
||||
|
||||
// if angularVelocity has changed it is likely that velocity has also changed so just sync both values
|
||||
// however if only velocity has changed just send velocity
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
CmdSendVelocityAndAngular(currentVelocity, currentAngularVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
else if (velocityChanged)
|
||||
{
|
||||
CmdSendVelocity(currentVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
|
||||
// only update syncTime if either has changed
|
||||
if (angularVelocityChanged || velocityChanged)
|
||||
previousValue.nextSyncTime = now + syncInterval;
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendRigidBodySettings()
|
||||
{
|
||||
// These shouldn't change often so it is ok to send in their own Command
|
||||
if (previousValue.isKinematic != target.isKinematic)
|
||||
{
|
||||
CmdSendIsKinematic(target.isKinematic);
|
||||
previousValue.isKinematic = target.isKinematic;
|
||||
}
|
||||
if (previousValue.useGravity != target.useGravity)
|
||||
{
|
||||
CmdSendUseGravity(target.useGravity);
|
||||
previousValue.useGravity = target.useGravity;
|
||||
}
|
||||
if (previousValue.drag != target.drag)
|
||||
{
|
||||
CmdSendDrag(target.drag);
|
||||
previousValue.drag = target.drag;
|
||||
}
|
||||
if (previousValue.angularDrag != target.angularDrag)
|
||||
{
|
||||
CmdSendAngularDrag(target.angularDrag);
|
||||
previousValue.angularDrag = target.angularDrag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when only Velocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocity(Vector3 velocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.velocity = velocity;
|
||||
target.velocity = velocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when angularVelocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocityAndAngular(Vector3 velocity, Vector3 angularVelocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
if (syncVelocity)
|
||||
{
|
||||
this.velocity = velocity;
|
||||
target.velocity = velocity;
|
||||
}
|
||||
|
||||
this.angularVelocity = angularVelocity;
|
||||
target.angularVelocity = angularVelocity;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendIsKinematic(bool isKinematic)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.isKinematic = isKinematic;
|
||||
target.isKinematic = isKinematic;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendUseGravity(bool useGravity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.useGravity = useGravity;
|
||||
target.useGravity = useGravity;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendDrag(float drag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.drag = drag;
|
||||
target.drag = drag;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendAngularDrag(float angularDrag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.angularDrag = angularDrag;
|
||||
target.angularDrag = angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// holds previously synced values
|
||||
/// </summary>
|
||||
public class ClientSyncState
|
||||
{
|
||||
/// <summary>
|
||||
/// Next sync time that velocity will be synced, based on syncInterval.
|
||||
/// </summary>
|
||||
public double nextSyncTime;
|
||||
public Vector3 velocity;
|
||||
public Vector3 angularVelocity;
|
||||
public bool isKinematic;
|
||||
public bool useGravity;
|
||||
public float drag;
|
||||
public float angularDrag;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,351 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
[AddComponentMenu("")]
|
||||
[HelpURL("https://mirror-networking.gitbook.io/docs/components/network-rigidbody")]
|
||||
[Obsolete("Use the new NetworkRigidbodyReliable/Unreliable 2D component with Snapshot Interpolation instead.")]
|
||||
public class NetworkRigidbody2D : NetworkBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
[SerializeField] internal Rigidbody2D target = null;
|
||||
|
||||
[Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
|
||||
public bool clientAuthority = false;
|
||||
|
||||
[Header("Velocity")]
|
||||
[Tooltip("Syncs Velocity every SyncInterval")]
|
||||
[SerializeField] bool syncVelocity = true;
|
||||
|
||||
[Tooltip("Set velocity to 0 each frame (only works if syncVelocity is false")]
|
||||
[SerializeField] bool clearVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float velocitySensitivity = 0.1f;
|
||||
|
||||
[Header("Angular Velocity")]
|
||||
[Tooltip("Syncs AngularVelocity every SyncInterval")]
|
||||
[SerializeField] bool syncAngularVelocity = true;
|
||||
|
||||
[Tooltip("Set angularVelocity to 0 each frame (only works if syncAngularVelocity is false")]
|
||||
[SerializeField] bool clearAngularVelocity = false;
|
||||
|
||||
[Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
|
||||
[SerializeField] float angularVelocitySensitivity = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Values sent on client with authority after they are sent to the server
|
||||
/// </summary>
|
||||
readonly ClientSyncState previousValue = new ClientSyncState();
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
if (target == null)
|
||||
target = GetComponent<Rigidbody2D>();
|
||||
|
||||
syncDirection = SyncDirection.ClientToServer;
|
||||
}
|
||||
|
||||
#region Sync vars
|
||||
|
||||
[SyncVar(hook = nameof(OnVelocityChanged))]
|
||||
Vector2 velocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularVelocityChanged))]
|
||||
float angularVelocity;
|
||||
|
||||
[SyncVar(hook = nameof(OnIsKinematicChanged))]
|
||||
bool isKinematic;
|
||||
|
||||
[SyncVar(hook = nameof(OnGravityScaleChanged))]
|
||||
float gravityScale;
|
||||
|
||||
[SyncVar(hook = nameof(OnuDragChanged))]
|
||||
float drag;
|
||||
|
||||
[SyncVar(hook = nameof(OnAngularDragChanged))]
|
||||
float angularDrag;
|
||||
|
||||
/// <summary>
|
||||
/// Ignore value if is host or client with Authority
|
||||
/// </summary>
|
||||
bool IgnoreSync => isServer || ClientWithAuthority;
|
||||
|
||||
bool ClientWithAuthority => clientAuthority && isOwned;
|
||||
|
||||
void OnVelocityChanged(Vector2 _, Vector2 newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.velocity = newValue;
|
||||
}
|
||||
|
||||
void OnAngularVelocityChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularVelocity = newValue;
|
||||
}
|
||||
|
||||
void OnIsKinematicChanged(bool _, bool newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.isKinematic = newValue;
|
||||
}
|
||||
|
||||
void OnGravityScaleChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.gravityScale = newValue;
|
||||
}
|
||||
|
||||
void OnuDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.drag = newValue;
|
||||
}
|
||||
|
||||
void OnAngularDragChanged(float _, float newValue)
|
||||
{
|
||||
if (IgnoreSync)
|
||||
return;
|
||||
|
||||
target.angularDrag = newValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (isServer)
|
||||
SyncToClients();
|
||||
else if (ClientWithAuthority)
|
||||
SendToServer();
|
||||
}
|
||||
|
||||
internal void FixedUpdate()
|
||||
{
|
||||
if (clearAngularVelocity && !syncAngularVelocity)
|
||||
target.angularVelocity = 0f;
|
||||
|
||||
if (clearVelocity && !syncVelocity)
|
||||
target.velocity = Vector2.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates sync var values on server so that they sync to the client
|
||||
/// </summary>
|
||||
[Server]
|
||||
void SyncToClients()
|
||||
{
|
||||
// only update if they have changed more than Sensitivity
|
||||
|
||||
Vector2 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
float currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity) > angularVelocitySensitivity);
|
||||
|
||||
if (velocityChanged)
|
||||
{
|
||||
velocity = currentVelocity;
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
angularVelocity = currentAngularVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
|
||||
// other rigidbody settings
|
||||
isKinematic = target.isKinematic;
|
||||
gravityScale = target.gravityScale;
|
||||
drag = target.drag;
|
||||
angularDrag = target.angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses Command to send values to server
|
||||
/// </summary>
|
||||
[Client]
|
||||
void SendToServer()
|
||||
{
|
||||
if (!isOwned)
|
||||
{
|
||||
Debug.LogWarning("SendToServer called without authority");
|
||||
return;
|
||||
}
|
||||
|
||||
SendVelocity();
|
||||
SendRigidBodySettings();
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendVelocity()
|
||||
{
|
||||
float now = Time.time;
|
||||
if (now < previousValue.nextSyncTime)
|
||||
return;
|
||||
|
||||
Vector2 currentVelocity = syncVelocity ? target.velocity : default;
|
||||
float currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
|
||||
|
||||
bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
|
||||
bool angularVelocityChanged = syncAngularVelocity && previousValue.angularVelocity != currentAngularVelocity;//((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
|
||||
|
||||
// if angularVelocity has changed it is likely that velocity has also changed so just sync both values
|
||||
// however if only velocity has changed just send velocity
|
||||
if (angularVelocityChanged)
|
||||
{
|
||||
CmdSendVelocityAndAngular(currentVelocity, currentAngularVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
previousValue.angularVelocity = currentAngularVelocity;
|
||||
}
|
||||
else if (velocityChanged)
|
||||
{
|
||||
CmdSendVelocity(currentVelocity);
|
||||
previousValue.velocity = currentVelocity;
|
||||
}
|
||||
|
||||
// only update syncTime if either has changed
|
||||
if (angularVelocityChanged || velocityChanged)
|
||||
previousValue.nextSyncTime = now + syncInterval;
|
||||
}
|
||||
|
||||
[Client]
|
||||
void SendRigidBodySettings()
|
||||
{
|
||||
// These shouldn't change often so it is ok to send in their own Command
|
||||
if (previousValue.isKinematic != target.isKinematic)
|
||||
{
|
||||
CmdSendIsKinematic(target.isKinematic);
|
||||
previousValue.isKinematic = target.isKinematic;
|
||||
}
|
||||
if (previousValue.gravityScale != target.gravityScale)
|
||||
{
|
||||
CmdChangeGravityScale(target.gravityScale);
|
||||
previousValue.gravityScale = target.gravityScale;
|
||||
}
|
||||
if (previousValue.drag != target.drag)
|
||||
{
|
||||
CmdSendDrag(target.drag);
|
||||
previousValue.drag = target.drag;
|
||||
}
|
||||
if (previousValue.angularDrag != target.angularDrag)
|
||||
{
|
||||
CmdSendAngularDrag(target.angularDrag);
|
||||
previousValue.angularDrag = target.angularDrag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when only Velocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocity(Vector2 velocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.velocity = velocity;
|
||||
target.velocity = velocity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when angularVelocity has changed on the client
|
||||
/// </summary>
|
||||
[Command]
|
||||
void CmdSendVelocityAndAngular(Vector2 velocity, float angularVelocity)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
if (syncVelocity)
|
||||
{
|
||||
this.velocity = velocity;
|
||||
target.velocity = velocity;
|
||||
}
|
||||
this.angularVelocity = angularVelocity;
|
||||
target.angularVelocity = angularVelocity;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendIsKinematic(bool isKinematic)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.isKinematic = isKinematic;
|
||||
target.isKinematic = isKinematic;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdChangeGravityScale(float gravityScale)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.gravityScale = gravityScale;
|
||||
target.gravityScale = gravityScale;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendDrag(float drag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.drag = drag;
|
||||
target.drag = drag;
|
||||
}
|
||||
|
||||
[Command]
|
||||
void CmdSendAngularDrag(float angularDrag)
|
||||
{
|
||||
// Ignore messages from client if not in client authority mode
|
||||
if (!clientAuthority)
|
||||
return;
|
||||
|
||||
this.angularDrag = angularDrag;
|
||||
target.angularDrag = angularDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// holds previously synced values
|
||||
/// </summary>
|
||||
public class ClientSyncState
|
||||
{
|
||||
/// <summary>
|
||||
/// Next sync time that velocity will be synced, based on syncInterval.
|
||||
/// </summary>
|
||||
public float nextSyncTime;
|
||||
public Vector2 velocity;
|
||||
public float angularVelocity;
|
||||
public bool isKinematic;
|
||||
public float gravityScale;
|
||||
public float drag;
|
||||
public float angularDrag;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,21 @@ public class NetworkRigidbodyReliable : NetworkTransformReliable
|
||||
Rigidbody rb;
|
||||
bool wasKinematic;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody.target {target.name} is missing a Rigidbody", this);
|
||||
}
|
||||
}
|
||||
|
||||
// cach Rigidbody and original isKinematic setting
|
||||
protected override void Awake()
|
||||
{
|
||||
@ -82,18 +97,6 @@ void FixedUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody.target {target.name} is missing a Rigidbody", this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTeleport(Vector3 destination)
|
||||
{
|
||||
base.OnTeleport(destination);
|
||||
|
@ -11,6 +11,20 @@ public class NetworkRigidbodyReliable2D : NetworkTransformReliable
|
||||
Rigidbody2D rb;
|
||||
bool wasKinematic;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody2D>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
}
|
||||
}
|
||||
|
||||
// cach Rigidbody and original isKinematic setting
|
||||
protected override void Awake()
|
||||
{
|
||||
@ -22,7 +36,11 @@ protected override void Awake()
|
||||
Debug.LogError($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
return;
|
||||
}
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
wasKinematic = rb.bodyType.HasFlag(RigidbodyType2D.Kinematic);
|
||||
#else
|
||||
wasKinematic = rb.isKinematic;
|
||||
#endif
|
||||
base.Awake();
|
||||
}
|
||||
|
||||
@ -31,8 +49,13 @@ protected override void Awake()
|
||||
// for example, a game may run as client, set rigidbody.iskinematic=true,
|
||||
// then run as server, where .iskinematic isn't touched and remains at
|
||||
// the overwritten=true, even though the user set it to false originally.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
public override void OnStopServer() => rb.bodyType = wasKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
|
||||
public override void OnStopClient() => rb.bodyType = wasKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
|
||||
#else
|
||||
public override void OnStopServer() => rb.isKinematic = wasKinematic;
|
||||
public override void OnStopClient() => rb.isKinematic = wasKinematic;
|
||||
#endif
|
||||
|
||||
// overwriting Construct() and Apply() to set Rigidbody.MovePosition
|
||||
// would give more jittery movement.
|
||||
@ -55,7 +78,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
#endif
|
||||
}
|
||||
// client only
|
||||
else if (isClient)
|
||||
@ -67,7 +94,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
#endif
|
||||
}
|
||||
// server only
|
||||
else if (isServer)
|
||||
@ -78,19 +109,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody2D>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,21 @@ public class NetworkRigidbodyUnreliable : NetworkTransformUnreliable
|
||||
Rigidbody rb;
|
||||
bool wasKinematic;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody.target {target.name} is missing a Rigidbody", this);
|
||||
}
|
||||
}
|
||||
|
||||
// cach Rigidbody and original isKinematic setting
|
||||
protected override void Awake()
|
||||
{
|
||||
@ -82,18 +97,6 @@ void FixedUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody.target {target.name} is missing a Rigidbody", this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTeleport(Vector3 destination)
|
||||
{
|
||||
base.OnTeleport(destination);
|
||||
|
@ -11,6 +11,21 @@ public class NetworkRigidbodyUnreliable2D : NetworkTransformUnreliable
|
||||
Rigidbody2D rb;
|
||||
bool wasKinematic;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody2D>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
}
|
||||
}
|
||||
|
||||
// cach Rigidbody and original isKinematic setting
|
||||
protected override void Awake()
|
||||
{
|
||||
@ -22,7 +37,12 @@ protected override void Awake()
|
||||
Debug.LogError($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
wasKinematic = rb.bodyType.HasFlag(RigidbodyType2D.Kinematic);
|
||||
#else
|
||||
wasKinematic = rb.isKinematic;
|
||||
#endif
|
||||
base.Awake();
|
||||
}
|
||||
|
||||
@ -31,9 +51,13 @@ protected override void Awake()
|
||||
// for example, a game may run as client, set rigidbody.iskinematic=true,
|
||||
// then run as server, where .iskinematic isn't touched and remains at
|
||||
// the overwritten=true, even though the user set it to false originally.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
public override void OnStopServer() => rb.bodyType = wasKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
|
||||
public override void OnStopClient() => rb.bodyType = wasKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
|
||||
#else
|
||||
public override void OnStopServer() => rb.isKinematic = wasKinematic;
|
||||
public override void OnStopClient() => rb.isKinematic = wasKinematic;
|
||||
|
||||
#endif
|
||||
// overwriting Construct() and Apply() to set Rigidbody.MovePosition
|
||||
// would give more jittery movement.
|
||||
|
||||
@ -55,7 +79,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
#endif
|
||||
}
|
||||
// client only
|
||||
else if (isClient)
|
||||
@ -67,7 +95,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
#endif
|
||||
}
|
||||
// server only
|
||||
else if (isServer)
|
||||
@ -78,19 +110,11 @@ void FixedUpdate()
|
||||
// only set to kinematic if we don't own it
|
||||
// otherwise don't touch isKinematic.
|
||||
// the authority owner might use it either way.
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (!owned) rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
#else
|
||||
if (!owned) rb.isKinematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// we can't overwrite .target to be a Rigidbody.
|
||||
// but we can ensure that .target has a Rigidbody, and use it.
|
||||
if (target.GetComponent<Rigidbody2D>() == null)
|
||||
{
|
||||
Debug.LogWarning($"{name}'s NetworkRigidbody2D.target {target.name} is missing a Rigidbody2D", this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ void SceneLoadedForPlayer(NetworkConnectionToClient conn, GameObject roomPlayer)
|
||||
return;
|
||||
|
||||
// replace room player with game player
|
||||
NetworkServer.ReplacePlayerForConnection(conn, gamePlayer, true);
|
||||
NetworkServer.ReplacePlayerForConnection(conn, gamePlayer, ReplacePlayerOptions.KeepAuthority);
|
||||
}
|
||||
|
||||
internal void CallOnClientEnterRoom()
|
||||
@ -342,7 +342,7 @@ public override void ServerChangeScene(string newSceneName)
|
||||
{
|
||||
// re-add the room object
|
||||
roomPlayer.GetComponent<NetworkRoomPlayer>().readyToBegin = false;
|
||||
NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, roomPlayer.gameObject);
|
||||
NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, roomPlayer.gameObject, ReplacePlayerOptions.KeepAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// DEPRECATED 2023-06-15
|
||||
[AddComponentMenu("")]
|
||||
[Obsolete("NetworkTransform was renamed to NetworkTransformUnreliable.\nYou can easily swap the component's script by going into the Unity Inspector debug mode:\n1. Click the vertical dots on the top right in the Inspector tab.\n2. Find your NetworkTransform component\n3. Drag NetworkTransformUnreliable into the 'Script' field in the Inspector.\n4. Find the three dots and return to Normal mode.")]
|
||||
public class NetworkTransform : NetworkTransformUnreliable {}
|
||||
}
|
@ -95,14 +95,41 @@ public abstract class NetworkTransformBase : NetworkBehaviour
|
||||
[Tooltip("Local by default. World may be better when changing hierarchy, or non-NetworkTransforms root position/rotation/scale values.")]
|
||||
public CoordinateSpace coordinateSpace = CoordinateSpace.Local;
|
||||
|
||||
[Header("Send Interval Multiplier")]
|
||||
[Tooltip("Check/Sync every multiple of Network Manager send interval (= 1 / NM Send Rate), instead of every send interval.\n(30 NM send rate, and 3 interval, is a send every 0.1 seconds)\nA larger interval means less network sends, which has a variety of upsides. The drawbacks are delays and lower accuracy, you should find a nice balance between not sending too much, but the results looking good for your particular scenario.")]
|
||||
[Range(1, 120)]
|
||||
public uint sendIntervalMultiplier = 1;
|
||||
// convert syncInterval to sendIntervalMultiplier.
|
||||
// in the future this can be moved into core to support tick aligned Sync,
|
||||
public uint sendIntervalMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (syncInterval > 0)
|
||||
{
|
||||
// if syncInterval is > 0, calculate how many multiples of NetworkManager.sendRate it is
|
||||
//
|
||||
// for example:
|
||||
// NetworkServer.sendInterval is 1/60 = 0.16
|
||||
// NetworkTransform.syncInterval is 0.5 (500ms).
|
||||
// 0.5 / 0.16 = 3.125
|
||||
// in other words: 3.125 x sendInterval
|
||||
//
|
||||
// note that NetworkServer.sendInterval is usually set on start.
|
||||
// to make this work in Edit mode, make sure that NetworkManager
|
||||
// OnValidate sets NetworkServer.sendInterval immediately.
|
||||
float multiples = syncInterval / NetworkServer.sendInterval;
|
||||
|
||||
// syncInterval is always supposed to sync at a minimum of 1 x sendInterval.
|
||||
// that's what we do for every other NetworkBehaviour since
|
||||
// we only sync in Broadcast() which is called @ sendInterval.
|
||||
return multiples > 1 ? (uint)Mathf.RoundToInt(multiples) : 1;
|
||||
}
|
||||
|
||||
// if syncInterval is 0, use NetworkManager.sendRate (x1)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
[Header("Timeline Offset")]
|
||||
[Tooltip("Add a small timeline offset to account for decoupled arrival of NetworkTime and NetworkTransform snapshots.\nfixes: https://github.com/MirrorNetworking/Mirror/issues/3427")]
|
||||
public bool timelineOffset = false;
|
||||
public bool timelineOffset = true;
|
||||
|
||||
// Ninja's Notes on offset & mulitplier:
|
||||
//
|
||||
@ -130,6 +157,17 @@ public abstract class NetworkTransformBase : NetworkBehaviour
|
||||
|
||||
public Color overlayColor = new Color(0, 0, 0, 0.5f);
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
base.OnValidate();
|
||||
|
||||
// configure in awake
|
||||
Configure();
|
||||
}
|
||||
|
||||
// initialization //////////////////////////////////////////////////////
|
||||
// forcec configuration of some settings
|
||||
protected virtual void Configure()
|
||||
@ -137,13 +175,6 @@ protected virtual void Configure()
|
||||
// set target to self if none yet
|
||||
if (target == null) target = transform;
|
||||
|
||||
// time snapshot interpolation happens globally.
|
||||
// value (transform) happens in here.
|
||||
// both always need to be on the same send interval.
|
||||
// force the setting to '0' in OnValidate to make it obvious that we
|
||||
// actually use NetworkServer.sendInterval.
|
||||
syncInterval = 0;
|
||||
|
||||
// Unity doesn't support setting world scale.
|
||||
// OnValidate force disables syncScale in world mode.
|
||||
if (coordinateSpace == CoordinateSpace.World) synchronizationSelections &= ~SyncInterpolateOptions.Scale;
|
||||
@ -157,14 +188,6 @@ protected virtual void Awake()
|
||||
Configure();
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
// configure in awake
|
||||
Configure();
|
||||
}
|
||||
|
||||
// snapshot functions //////////////////////////////////////////////////
|
||||
// get local/world position
|
||||
protected virtual Vector3 GetPosition() =>
|
||||
@ -354,6 +377,14 @@ public void RpcTeleport(Vector3 destination, Quaternion rotation)
|
||||
OnTeleport(destination, rotation);
|
||||
}
|
||||
|
||||
// teleport on server, broadcast to clients.
|
||||
[Server]
|
||||
public void ServerTeleport(Vector3 destination, Quaternion rotation)
|
||||
{
|
||||
OnTeleport(destination, rotation);
|
||||
RpcTeleport(destination, rotation);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void RpcResetState()
|
||||
{
|
||||
|
@ -1,12 +0,0 @@
|
||||
// A component to synchronize the position of child transforms of networked objects.
|
||||
// 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.
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// Deprecated 2022-10-25
|
||||
[AddComponentMenu("")]
|
||||
[Obsolete("NetworkTransformChild is not needed anymore. The .target is now exposed in NetworkTransform itself. Note you can open the Inspector in debug view and replace the source script instead of reassigning everything.")]
|
||||
public class NetworkTransformChild : NetworkTransform {}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 734b48bea0b204338958ee3d885e11f0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -42,8 +42,6 @@ public class NetworkTransformReliable : NetworkTransformBase
|
||||
// Used to store last sent snapshots
|
||||
protected TransformSnapshot last;
|
||||
|
||||
protected int lastClientCount = 1;
|
||||
|
||||
// update //////////////////////////////////////////////////////////////
|
||||
void Update()
|
||||
{
|
||||
@ -123,8 +121,6 @@ protected virtual void UpdateClient()
|
||||
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
|
||||
Apply(computed, to);
|
||||
}
|
||||
|
||||
lastClientCount = clientSnapshots.Count;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,21 +14,14 @@ public class NetworkTransformUnreliable : NetworkTransformBase
|
||||
// Testing under really bad network conditions, 2%-5% packet loss and 250-1200ms ping, 5 proved to eliminate any twitching, however this should not be the default as it is a rare case Developers may want to cover.
|
||||
[Tooltip("How much time, as a multiple of send interval, has passed before clearing buffers.\nA larger buffer means more delay, but results in smoother movement.\nExample: 1 for faster responses minimal smoothing, 5 covers bad pings but has noticable delay, 3 is recommended for balanced results,.")]
|
||||
public float bufferResetMultiplier = 3;
|
||||
[Tooltip("Detect and send only changed data, such as Position X and Z, not the full Vector3 of X Y Z. Lowers network data at cost of extra calculations.")]
|
||||
public bool changedDetection = true;
|
||||
|
||||
[Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")]
|
||||
public float positionSensitivity = 0.01f;
|
||||
public float rotationSensitivity = 0.01f;
|
||||
public float scaleSensitivity = 0.01f;
|
||||
|
||||
protected bool positionChanged;
|
||||
protected bool rotationChanged;
|
||||
protected bool scaleChanged;
|
||||
|
||||
// Used to store last sent snapshots
|
||||
protected TransformSnapshot lastSnapshot;
|
||||
protected bool cachedSnapshotComparison;
|
||||
protected Changed cachedChangedComparison;
|
||||
protected bool hasSentUnchangedPosition;
|
||||
|
||||
@ -117,67 +110,22 @@ void UpdateServerBroadcast()
|
||||
// receiver gets it from batch timestamp to save bandwidth.
|
||||
TransformSnapshot snapshot = Construct();
|
||||
|
||||
if (changedDetection)
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
RpcServerToClientSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
RpcServerToClientSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (compressRotation)
|
||||
{
|
||||
RpcServerToClientSyncCompressRotation(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
RpcServerToClientSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
|
||||
// Fixes https://github.com/MirrorNetworking/Mirror/issues/3572
|
||||
// This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573
|
||||
// with the exception of Quaternion.Angle sensitivity has to be > 0.16.
|
||||
// Unity issue, we are leaving it as is.
|
||||
|
||||
if (positionChanged) lastSnapshot.position = snapshot.position;
|
||||
if (rotationChanged) lastSnapshot.rotation = snapshot.rotation;
|
||||
if (positionChanged) lastSnapshot.scale = snapshot.scale;
|
||||
}
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,66 +193,22 @@ void UpdateClientBroadcast()
|
||||
// receiver gets it from batch timestamp to save bandwidth.
|
||||
TransformSnapshot snapshot = Construct();
|
||||
|
||||
if (changedDetection)
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
CmdClientToServerSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
cachedChangedComparison = CompareChangedSnapshots(snapshot);
|
||||
|
||||
if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
SyncData syncData = new SyncData(cachedChangedComparison, snapshot);
|
||||
|
||||
CmdClientToServerSync(syncData);
|
||||
|
||||
if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedSnapshotComparison = CompareSnapshots(snapshot);
|
||||
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
|
||||
|
||||
if (compressRotation)
|
||||
{
|
||||
CmdClientToServerSyncCompressRotation(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CmdClientToServerSync(
|
||||
// only sync what the user wants to sync
|
||||
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
|
||||
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
|
||||
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedSnapshotComparison)
|
||||
{
|
||||
hasSentUnchangedPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasSentUnchangedPosition = false;
|
||||
|
||||
// Fixes https://github.com/MirrorNetworking/Mirror/issues/3572
|
||||
// This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573
|
||||
// with the exception of Quaternion.Angle sensitivity has to be > 0.16.
|
||||
// Unity issue, we are leaving it as is.
|
||||
if (positionChanged) lastSnapshot.position = snapshot.position;
|
||||
if (rotationChanged) lastSnapshot.rotation = snapshot.rotation;
|
||||
if (positionChanged) lastSnapshot.scale = snapshot.scale;
|
||||
}
|
||||
hasSentUnchangedPosition = false;
|
||||
UpdateLastSentSnapshot(cachedChangedComparison, snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,129 +258,6 @@ public override void OnDeserialize(NetworkReader reader, bool initialState)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if position, rotation AND scale are unchanged, within given sensitivity range.
|
||||
protected virtual bool CompareSnapshots(TransformSnapshot currentSnapshot)
|
||||
{
|
||||
positionChanged = Vector3.SqrMagnitude(lastSnapshot.position - currentSnapshot.position) > positionSensitivity * positionSensitivity;
|
||||
rotationChanged = Quaternion.Angle(lastSnapshot.rotation, currentSnapshot.rotation) > rotationSensitivity;
|
||||
scaleChanged = Vector3.SqrMagnitude(lastSnapshot.scale - currentSnapshot.scale) > scaleSensitivity * scaleSensitivity;
|
||||
|
||||
return (!positionChanged && !rotationChanged && !scaleChanged);
|
||||
}
|
||||
|
||||
// cmd /////////////////////////////////////////////////////////////////
|
||||
// only unreliable. see comment above of this file.
|
||||
[Command(channel = Channels.Unreliable)]
|
||||
void CmdClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale)
|
||||
{
|
||||
OnClientToServerSync(position, rotation, scale);
|
||||
//For client authority, immediately pass on the client snapshot to all other
|
||||
//clients instead of waiting for server to send its snapshots.
|
||||
if (syncDirection == SyncDirection.ClientToServer)
|
||||
RpcServerToClientSync(position, rotation, scale);
|
||||
}
|
||||
|
||||
// cmd /////////////////////////////////////////////////////////////////
|
||||
// only unreliable. see comment above of this file.
|
||||
[Command(channel = Channels.Unreliable)]
|
||||
void CmdClientToServerSyncCompressRotation(Vector3? position, uint? rotation, Vector3? scale)
|
||||
{
|
||||
// A fix to not apply current interpolated GetRotation when receiving null/unchanged value, instead use last sent snapshot rotation.
|
||||
Quaternion newRotation;
|
||||
if (rotation.HasValue)
|
||||
{
|
||||
newRotation = Compression.DecompressQuaternion((uint)rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
newRotation = serverSnapshots.Count > 0 ? serverSnapshots.Values[serverSnapshots.Count - 1].rotation : GetRotation();
|
||||
}
|
||||
OnClientToServerSync(position, newRotation, scale);
|
||||
//For client authority, immediately pass on the client snapshot to all other
|
||||
//clients instead of waiting for server to send its snapshots.
|
||||
if (syncDirection == SyncDirection.ClientToServer)
|
||||
RpcServerToClientSyncCompressRotation(position, rotation, scale);
|
||||
}
|
||||
|
||||
// local authority client sends sync message to server for broadcasting
|
||||
protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale)
|
||||
{
|
||||
// only apply if in client authority mode
|
||||
if (syncDirection != SyncDirection.ClientToServer) return;
|
||||
|
||||
// protect against ever growing buffer size attacks
|
||||
if (serverSnapshots.Count >= connectionToClient.snapshotBufferSizeLimit) return;
|
||||
|
||||
// only player owned objects (with a connection) can send to
|
||||
// server. we can get the timestamp from the connection.
|
||||
double timestamp = connectionToClient.remoteTimeStamp;
|
||||
|
||||
if (onlySyncOnChange)
|
||||
{
|
||||
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkClient.sendInterval;
|
||||
|
||||
if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
|
||||
ResetState();
|
||||
}
|
||||
|
||||
AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
|
||||
}
|
||||
|
||||
// rpc /////////////////////////////////////////////////////////////////
|
||||
// only unreliable. see comment above of this file.
|
||||
[ClientRpc(channel = Channels.Unreliable)]
|
||||
void RpcServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) =>
|
||||
OnServerToClientSync(position, rotation, scale);
|
||||
|
||||
// rpc /////////////////////////////////////////////////////////////////
|
||||
// only unreliable. see comment above of this file.
|
||||
[ClientRpc(channel = Channels.Unreliable)]
|
||||
void RpcServerToClientSyncCompressRotation(Vector3? position, uint? rotation, Vector3? scale)
|
||||
{
|
||||
// A fix to not apply current interpolated GetRotation when receiving null/unchanged value, instead use last sent snapshot rotation.
|
||||
Quaternion newRotation;
|
||||
if (rotation.HasValue)
|
||||
{
|
||||
newRotation = Compression.DecompressQuaternion((uint)rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
newRotation = clientSnapshots.Count > 0 ? clientSnapshots.Values[clientSnapshots.Count - 1].rotation : GetRotation();
|
||||
}
|
||||
OnServerToClientSync(position, newRotation, scale);
|
||||
}
|
||||
|
||||
// server broadcasts sync message to all clients
|
||||
protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale)
|
||||
{
|
||||
// in host mode, the server sends rpcs to all clients.
|
||||
// the host client itself will receive them too.
|
||||
// -> host server is always the source of truth
|
||||
// -> we can ignore any rpc on the host client
|
||||
// => otherwise host objects would have ever growing clientBuffers
|
||||
// (rpc goes to clients. if isServer is true too then we are host)
|
||||
if (isServer) return;
|
||||
|
||||
// don't apply for local player with authority
|
||||
if (IsClientWithAuthority) return;
|
||||
|
||||
// on the client, we receive rpcs for all entities.
|
||||
// not all of them have a connectionToServer.
|
||||
// but all of them go through NetworkClient.connection.
|
||||
// we can get the timestamp from there.
|
||||
double timestamp = NetworkClient.connection.remoteTimeStamp;
|
||||
|
||||
if (onlySyncOnChange)
|
||||
{
|
||||
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkServer.sendInterval;
|
||||
|
||||
if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
|
||||
ResetState();
|
||||
}
|
||||
|
||||
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
|
||||
}
|
||||
|
||||
protected virtual void UpdateLastSentSnapshot(Changed change, TransformSnapshot currentSnapshot)
|
||||
{
|
||||
if (change == Changed.None || change == Changed.CompressRot) return;
|
||||
@ -520,7 +301,7 @@ protected virtual Changed CompareChangedSnapshots(TransformSnapshot currentSnaps
|
||||
}
|
||||
|
||||
if (syncRotation)
|
||||
{
|
||||
{
|
||||
if (compressRotation)
|
||||
{
|
||||
bool rotationChanged = Quaternion.Angle(lastSnapshot.rotation, currentSnapshot.rotation) > rotationSensitivity;
|
||||
@ -659,21 +440,5 @@ protected virtual void UpdateSyncData(ref SyncData syncData, SortedList<double,
|
||||
syncData.scale = (syncData.changedDataByte & Changed.Scale) > 0 ? syncData.scale : (snapshots.Count > 0 ? snapshots.Values[snapshots.Count - 1].scale : GetScale());
|
||||
}
|
||||
}
|
||||
|
||||
// This is to extract position/rotation/scale data from payload. Override
|
||||
// Construct and Deconstruct if you are implementing a different SyncData logic.
|
||||
// Note however that snapshot interpolation still requires the basic 3 data
|
||||
// position, rotation and scale, which are computed from here.
|
||||
protected virtual void DeconstructSyncData(System.ArraySegment<byte> receivedPayload, out byte? changedFlagData, out Vector3? position, out Quaternion? rotation, out Vector3? scale)
|
||||
{
|
||||
using (NetworkReaderPooled reader = NetworkReaderPool.Get(receivedPayload))
|
||||
{
|
||||
SyncData syncData = reader.Read<SyncData>();
|
||||
changedFlagData = (byte)syncData.changedDataByte;
|
||||
position = syncData.position;
|
||||
rotation = syncData.quatRotation;
|
||||
scale = syncData.scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,9 +423,12 @@ protected virtual bool IsMoving() =>
|
||||
// predictedRigidbody.velocity.magnitude >= motionSmoothingVelocityThreshold ||
|
||||
// predictedRigidbody.angularVelocity.magnitude >= motionSmoothingAngularVelocityThreshold;
|
||||
// faster implementation with cached ²
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
predictedRigidbody.linearVelocity.sqrMagnitude >= motionSmoothingVelocityThresholdSqr ||
|
||||
#else
|
||||
predictedRigidbody.velocity.sqrMagnitude >= motionSmoothingVelocityThresholdSqr ||
|
||||
#endif
|
||||
predictedRigidbody.angularVelocity.sqrMagnitude >= motionSmoothingAngularVelocityThresholdSqr;
|
||||
|
||||
// TODO maybe merge the IsMoving() checks & callbacks with UpdateState().
|
||||
void UpdateGhosting()
|
||||
{
|
||||
@ -583,7 +586,11 @@ void RecordState()
|
||||
// grab current position/rotation/velocity only once.
|
||||
// this is performance critical, avoid calling .transform multiple times.
|
||||
tf.GetPositionAndRotation(out Vector3 currentPosition, out Quaternion currentRotation); // faster than accessing .position + .rotation manually
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
Vector3 currentVelocity = predictedRigidbody.linearVelocity;
|
||||
#else
|
||||
Vector3 currentVelocity = predictedRigidbody.velocity;
|
||||
#endif
|
||||
Vector3 currentAngularVelocity = predictedRigidbody.angularVelocity;
|
||||
|
||||
// calculate delta to previous state (if any)
|
||||
@ -638,8 +645,13 @@ void ApplyState(double timestamp, Vector3 position, Quaternion rotation, Vector3
|
||||
// hard snap to the position below a threshold velocity.
|
||||
// this is fine because the visual object still smoothly interpolates to it.
|
||||
// => consider both velocity and angular velocity (in case of Rigidbodies only rotating with joints etc.)
|
||||
if (predictedRigidbody.velocity.magnitude <= snapThreshold &&
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
if (predictedRigidbody.linearVelocity.magnitude <= snapThreshold &&
|
||||
predictedRigidbody.angularVelocity.magnitude <= snapThreshold)
|
||||
#else
|
||||
if (predictedRigidbody.velocity.magnitude <= snapThreshold &&
|
||||
predictedRigidbody.angularVelocity.magnitude <= snapThreshold)
|
||||
#endif
|
||||
{
|
||||
// Debug.Log($"Prediction: snapped {name} into place because velocity {predictedRigidbody.velocity.magnitude:F3} <= {snapThreshold:F3}");
|
||||
|
||||
@ -652,7 +664,11 @@ void ApplyState(double timestamp, Vector3 position, Quaternion rotation, Vector3
|
||||
// projects may keep Rigidbodies as kinematic sometimes. in that case, setting velocity would log an error
|
||||
if (!predictedRigidbody.isKinematic)
|
||||
{
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
predictedRigidbody.linearVelocity = velocity;
|
||||
#else
|
||||
predictedRigidbody.velocity = velocity;
|
||||
#endif
|
||||
predictedRigidbody.angularVelocity = angularVelocity;
|
||||
}
|
||||
|
||||
@ -704,7 +720,11 @@ void ApplyState(double timestamp, Vector3 position, Quaternion rotation, Vector3
|
||||
// (projects may keep Rigidbodies as kinematic sometimes. in that case, setting velocity would log an error)
|
||||
if (!predictedRigidbody.isKinematic)
|
||||
{
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
predictedRigidbody.linearVelocity = velocity;
|
||||
#else
|
||||
predictedRigidbody.velocity = velocity;
|
||||
#endif
|
||||
predictedRigidbody.angularVelocity = angularVelocity;
|
||||
}
|
||||
}
|
||||
@ -896,7 +916,11 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
|
||||
Time.deltaTime,
|
||||
position,
|
||||
rotation,
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
predictedRigidbody.linearVelocity,
|
||||
#else
|
||||
predictedRigidbody.velocity,
|
||||
#endif
|
||||
predictedRigidbody.angularVelocity);//,
|
||||
// DO NOT SYNC SLEEPING! this cuts benchmark performance in half(!!!)
|
||||
// predictedRigidbody.IsSleeping());
|
||||
|
@ -21,8 +21,13 @@ public static void MoveRigidbody(GameObject source, GameObject destination, bool
|
||||
|
||||
// copy all properties
|
||||
rigidbodyCopy.mass = original.mass;
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
rigidbodyCopy.linearDamping = original.linearDamping;
|
||||
rigidbodyCopy.angularDamping = original.angularDamping;
|
||||
#else
|
||||
rigidbodyCopy.drag = original.drag;
|
||||
rigidbodyCopy.angularDrag = original.angularDrag;
|
||||
#endif
|
||||
rigidbodyCopy.useGravity = original.useGravity;
|
||||
rigidbodyCopy.isKinematic = original.isKinematic;
|
||||
rigidbodyCopy.interpolation = original.interpolation;
|
||||
@ -40,7 +45,11 @@ public static void MoveRigidbody(GameObject source, GameObject destination, bool
|
||||
// projects may keep Rigidbodies as kinematic sometimes. in that case, setting velocity would log an error
|
||||
if (!original.isKinematic)
|
||||
{
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
rigidbodyCopy.linearVelocity = original.linearVelocity;
|
||||
#else
|
||||
rigidbodyCopy.velocity = original.velocity;
|
||||
#endif
|
||||
rigidbodyCopy.angularVelocity = original.angularVelocity;
|
||||
}
|
||||
|
||||
|
3
Assets/Mirror/Components/Profiling.meta
Normal file
3
Assets/Mirror/Components/Profiling.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b799e47369048fcb1604e5b3e7d71cf
|
||||
timeCreated: 1724245584
|
217
Assets/Mirror/Components/Profiling/BaseUIGraph.cs
Normal file
217
Assets/Mirror/Components/Profiling/BaseUIGraph.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
namespace Mirror
|
||||
{
|
||||
public enum GraphAggregationMode
|
||||
{
|
||||
Sum,
|
||||
Average,
|
||||
PerSecond,
|
||||
Min,
|
||||
Max
|
||||
}
|
||||
|
||||
public abstract class BaseUIGraph : MonoBehaviour
|
||||
{
|
||||
static readonly int MaxValue = Shader.PropertyToID("_MaxValue");
|
||||
static readonly int GraphData = Shader.PropertyToID("_GraphData");
|
||||
static readonly int CategoryCount = Shader.PropertyToID("_CategoryCount");
|
||||
static readonly int Colors = Shader.PropertyToID("_CategoryColors");
|
||||
static readonly int Width = Shader.PropertyToID("_Width");
|
||||
static readonly int DataStart = Shader.PropertyToID("_DataStart");
|
||||
|
||||
public Material Material;
|
||||
public Graphic Renderer;
|
||||
[Range(1, 64)]
|
||||
public int Points = 64;
|
||||
public float SecondsPerPoint = 1;
|
||||
public Color[] CategoryColors = new[] { Color.cyan };
|
||||
public bool IsStacked;
|
||||
|
||||
public Text[] LegendTexts;
|
||||
[Header("Diagnostics")]
|
||||
[ReadOnly, SerializeField]
|
||||
Material runtimeMaterial;
|
||||
|
||||
float[] graphData;
|
||||
// graphData is a circular buffer, this is the offset to get the 0-index
|
||||
int graphDataStartIndex;
|
||||
// Is graphData dirty and needs to be set to the material
|
||||
bool isGraphDataDirty;
|
||||
// currently aggregating data to be added to the graph soon
|
||||
float[] aggregatingData;
|
||||
GraphAggregationMode[] aggregatingModes;
|
||||
// Counts for avg aggregation mode
|
||||
int[] aggregatingDataCounts;
|
||||
// How much time has elapsed since the last aggregation finished
|
||||
float aggregatingTime;
|
||||
|
||||
int DataLastIndex => (graphDataStartIndex - 1 + Points) % Points;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Renderer.material = runtimeMaterial = Instantiate(Material);
|
||||
graphData = new float[Points * CategoryColors.Length];
|
||||
aggregatingData = new float[CategoryColors.Length];
|
||||
aggregatingDataCounts = new int[CategoryColors.Length];
|
||||
aggregatingModes = new GraphAggregationMode[CategoryColors.Length];
|
||||
isGraphDataDirty = true;
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if (Renderer == null)
|
||||
Renderer = GetComponent<Graphic>();
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
for (int i = 0; i < CategoryColors.Length; i++)
|
||||
{
|
||||
CollectData(i, out float value, out GraphAggregationMode mode);
|
||||
// we probably don't need negative values, so lets skip supporting it
|
||||
if (value < 0)
|
||||
{
|
||||
Debug.LogWarning("Graphing negative values is not supported.");
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (mode != aggregatingModes[i])
|
||||
{
|
||||
aggregatingModes[i] = mode;
|
||||
ResetCurrent(i);
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case GraphAggregationMode.Average:
|
||||
case GraphAggregationMode.Sum:
|
||||
case GraphAggregationMode.PerSecond:
|
||||
aggregatingData[i] += value;
|
||||
aggregatingDataCounts[i]++;
|
||||
break;
|
||||
case GraphAggregationMode.Min:
|
||||
if (aggregatingData[i] > value)
|
||||
aggregatingData[i] = value;
|
||||
break;
|
||||
case GraphAggregationMode.Max:
|
||||
if (value > aggregatingData[i])
|
||||
aggregatingData[i] = value;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
aggregatingTime += Time.deltaTime;
|
||||
if (aggregatingTime > SecondsPerPoint)
|
||||
{
|
||||
graphDataStartIndex = (graphDataStartIndex + 1) % Points;
|
||||
ClearDataAt(DataLastIndex);
|
||||
for (int i = 0; i < CategoryColors.Length; i++)
|
||||
{
|
||||
float value = aggregatingData[i];
|
||||
switch (aggregatingModes[i])
|
||||
{
|
||||
case GraphAggregationMode.Sum:
|
||||
case GraphAggregationMode.Min:
|
||||
case GraphAggregationMode.Max:
|
||||
// do nothing!
|
||||
break;
|
||||
case GraphAggregationMode.Average:
|
||||
value /= aggregatingDataCounts[i];
|
||||
break;
|
||||
case GraphAggregationMode.PerSecond:
|
||||
value /= aggregatingTime;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
SetCurrentGraphData(i, value);
|
||||
ResetCurrent(i);
|
||||
}
|
||||
|
||||
aggregatingTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetCurrent(int i)
|
||||
{
|
||||
switch (aggregatingModes[i])
|
||||
{
|
||||
case GraphAggregationMode.Min:
|
||||
aggregatingData[i] = float.MaxValue;
|
||||
break;
|
||||
default:
|
||||
aggregatingData[i] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
aggregatingDataCounts[i] = 0;
|
||||
}
|
||||
|
||||
protected virtual string FormatValue(float value) => $"{value:N1}";
|
||||
|
||||
protected abstract void CollectData(int category, out float value, out GraphAggregationMode mode);
|
||||
|
||||
void SetCurrentGraphData(int c, float value)
|
||||
{
|
||||
graphData[DataLastIndex * CategoryColors.Length + c] = value;
|
||||
isGraphDataDirty = true;
|
||||
}
|
||||
|
||||
void ClearDataAt(int i)
|
||||
{
|
||||
for (int c = 0; c < CategoryColors.Length; c++)
|
||||
graphData[i * CategoryColors.Length + c] = 0;
|
||||
|
||||
isGraphDataDirty = true;
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
if (isGraphDataDirty)
|
||||
{
|
||||
runtimeMaterial.SetInt(Width, Points);
|
||||
runtimeMaterial.SetInt(DataStart, graphDataStartIndex);
|
||||
float max = 1;
|
||||
if (IsStacked)
|
||||
for (int x = 0; x < Points; x++)
|
||||
{
|
||||
float total = 0;
|
||||
for (int c = 0; c < CategoryColors.Length; c++)
|
||||
total += graphData[x * CategoryColors.Length + c];
|
||||
|
||||
if (total > max)
|
||||
max = total;
|
||||
}
|
||||
else
|
||||
for (int i = 0; i < graphData.Length; i++)
|
||||
{
|
||||
float v = graphData[i];
|
||||
if (v > max)
|
||||
max = v;
|
||||
}
|
||||
|
||||
max = AdjustMaxValue(max);
|
||||
for (int i = 0; i < LegendTexts.Length; i++)
|
||||
{
|
||||
Text legendText = LegendTexts[i];
|
||||
float pct = (float)i / (LegendTexts.Length - 1);
|
||||
legendText.text = FormatValue(max * pct);
|
||||
}
|
||||
|
||||
runtimeMaterial.SetFloat(MaxValue, max);
|
||||
runtimeMaterial.SetFloatArray(GraphData, graphData);
|
||||
runtimeMaterial.SetInt(CategoryCount, CategoryColors.Length);
|
||||
runtimeMaterial.SetColorArray(Colors, CategoryColors);
|
||||
isGraphDataDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual float AdjustMaxValue(float max) => Mathf.Ceil(max);
|
||||
}
|
||||
}
|
14
Assets/Mirror/Components/Profiling/BaseUIGraph.cs.meta
Normal file
14
Assets/Mirror/Components/Profiling/BaseUIGraph.cs.meta
Normal file
@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f7dbe0fe96842f3b3ec76d05d2bb24a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- Material: {fileID: 2100000, guid: 5f77111e39fad6240bbf2a93d735b648, type: 2}
|
||||
- Renderer: {instanceID: 0}
|
||||
- runtimeMaterial: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
40
Assets/Mirror/Components/Profiling/FpsMinMaxAvgGraph.cs
Normal file
40
Assets/Mirror/Components/Profiling/FpsMinMaxAvgGraph.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
namespace Mirror
|
||||
{
|
||||
public class FpsMinMaxAvgGraph : BaseUIGraph
|
||||
{
|
||||
protected override void CollectData(int category, out float value, out GraphAggregationMode mode)
|
||||
{
|
||||
value = 1 / Time.deltaTime;
|
||||
switch (category)
|
||||
{
|
||||
case 0:
|
||||
mode = GraphAggregationMode.Average;
|
||||
break;
|
||||
case 1:
|
||||
mode = GraphAggregationMode.Min;
|
||||
break;
|
||||
case 2:
|
||||
mode = GraphAggregationMode.Max;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"{category} is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (CategoryColors.Length != 3)
|
||||
CategoryColors = new[]
|
||||
{
|
||||
Color.cyan, // avg
|
||||
Color.red, // min
|
||||
Color.green // max
|
||||
};
|
||||
|
||||
IsStacked = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f74aedd71d9a4f55b3ce499326d45fb
|
||||
guid: 73bc3b929ec94537a8dbd67eb9d0c2c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
89
Assets/Mirror/Components/Profiling/LineGraph.mat
Normal file
89
Assets/Mirror/Components/Profiling/LineGraph.mat
Normal file
@ -0,0 +1,89 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: LineGraph
|
||||
m_Shader: {fileID: 4800000, guid: e9fd6820072746bbaa3a83a449c31709, type: 3}
|
||||
m_ShaderKeywords:
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Floats:
|
||||
- _BumpScale: 1
|
||||
- _CategoryCount: 0
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DataStart: 0
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _LineWidth: 1
|
||||
- _MaxValue: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _UseUIAlphaClip: 0
|
||||
- _Width: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
8
Assets/Mirror/Components/Profiling/LineGraph.mat.meta
Normal file
8
Assets/Mirror/Components/Profiling/LineGraph.mat.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f77111e39fad6240bbf2a93d735b648
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
178
Assets/Mirror/Components/Profiling/NetworkGraphLines.shader
Normal file
178
Assets/Mirror/Components/Profiling/NetworkGraphLines.shader
Normal file
@ -0,0 +1,178 @@
|
||||
Shader "Mirror/NetworkGraphLines"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
_Width ("Width", Int) = 0
|
||||
_LineWidth ("Line Width", Float) = 0.005
|
||||
_CategoryCount ("CategoryCount", Int) = 0
|
||||
_MaxValue ("MaxValue", Float) = 1
|
||||
_DataStart ("DataStart", Int) = 0
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex; // we dont use this, but unitys ui library expects the shader to have a texture
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
|
||||
uint _Width;
|
||||
half _LineWidth;
|
||||
uint _CategoryCount;
|
||||
uint _MaxValue;
|
||||
uint _DataStart;
|
||||
half _GraphData[64 /* max. 128 points */ * 8 /* max 8 categories */];
|
||||
half4 _CategoryColors[8 /* max 8 categories */];
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
|
||||
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
// Helper function to calculate the shortest distance from a point (p) to a line segment (from a to b)
|
||||
float distanceToLineSegment(float2 p, float2 a, float2 b)
|
||||
{
|
||||
float2 ab = b - a;
|
||||
float2 ap = p - a;
|
||||
float t = saturate(dot(ap, ab) / dot(ab, ab));
|
||||
// Clamp t between 0 and 1 to ensure we stay within the segment
|
||||
float2 closestPoint = a + t * ab; // Find the closest point on the line segment
|
||||
return length(p - closestPoint); // Return the distance from p to the closest point on the line
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
uint wCur = (uint)(IN.texcoord.x * _Width);
|
||||
uint wMin = wCur == 0 ? 0 : wCur - 1;
|
||||
uint wMax = wCur == _Width - 1 ? wCur : wCur + 1;
|
||||
float2 screenSize = _ScreenParams.xy;
|
||||
// this scaling only works if the object is flat and not rotated - but thats fine
|
||||
float2 pixelScale = float2(1 / ddx(IN.texcoord.x), 1 / ddy(IN.texcoord.y));
|
||||
float2 screenSpaceUV = IN.texcoord * pixelScale;
|
||||
half4 color = half4(0, 0, 0, 0);
|
||||
// Loop through the graph's points
|
||||
bool colored = false;
|
||||
for (uint wNonOffset = wMin; wNonOffset < wMax && !colored; wNonOffset++)
|
||||
{
|
||||
uint w = (wNonOffset + _DataStart) % _Width;
|
||||
// previous entry, unless it's the start, then we clamp to start
|
||||
uint nextW = (w + 1) % _Width;
|
||||
|
||||
float texPosCurrentX = float(wNonOffset) / _Width;
|
||||
float texPosPrevX = texPosCurrentX + 1.0f / _Width;
|
||||
|
||||
|
||||
for (uint c = 0; c < _CategoryCount; c++)
|
||||
{
|
||||
float categoryValueCurrent = _GraphData[w * _CategoryCount + c] / _MaxValue;
|
||||
float categoryValueNext = _GraphData[nextW * _CategoryCount + c] / _MaxValue;
|
||||
|
||||
float2 pointCurrent = float2(texPosCurrentX, categoryValueCurrent);
|
||||
float2 pointNext = float2(texPosPrevX, categoryValueNext);
|
||||
|
||||
float distance = distanceToLineSegment(screenSpaceUV, pointCurrent * pixelScale,
|
||||
pointNext * pixelScale);
|
||||
|
||||
if (distance < _LineWidth)
|
||||
{
|
||||
color = _CategoryColors[c];
|
||||
colored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color *= IN.color;
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9fd6820072746bbaa3a83a449c31709
|
||||
timeCreated: 1725809505
|
138
Assets/Mirror/Components/Profiling/NetworkGraphStacked.shader
Normal file
138
Assets/Mirror/Components/Profiling/NetworkGraphStacked.shader
Normal file
@ -0,0 +1,138 @@
|
||||
Shader "Mirror/NetworkGraphStacked"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
_Width ("Width", Int) = 0
|
||||
_CategoryCount ("CategoryCount", Int) = 0
|
||||
_MaxValue ("MaxValue", Float) = 1
|
||||
_DataStart ("DataStart", Int) = 0
|
||||
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Default"
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 2.0
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
|
||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
sampler2D _MainTex; // we dont use this, but unitys ui library expects the shader to have a texture
|
||||
fixed4 _Color;
|
||||
fixed4 _TextureSampleAdd;
|
||||
float4 _ClipRect;
|
||||
float4 _MainTex_ST;
|
||||
|
||||
uint _Width;
|
||||
uint _CategoryCount;
|
||||
uint _MaxValue;
|
||||
uint _DataStart;
|
||||
half _GraphData[64 /* max. 64 points */ * 8 /* max 8 categories */];
|
||||
half4 _CategoryColors[8 /* max 8 categories */];
|
||||
|
||||
v2f vert(appdata_t v)
|
||||
{
|
||||
v2f OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
OUT.worldPosition = v.vertex;
|
||||
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
|
||||
|
||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
|
||||
|
||||
OUT.color = v.color * _Color;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
uint w = ((uint)(IN.texcoord.x * _Width) + _DataStart) % _Width;
|
||||
half4 color = half4(0, 0, 0, 0);
|
||||
float totalValue = 0;
|
||||
for (uint c = 0; c < _CategoryCount; c++)
|
||||
{
|
||||
float categoryValue = _GraphData[w * _CategoryCount + c] / _MaxValue;
|
||||
totalValue += categoryValue;
|
||||
if (totalValue >= IN.texcoord.y)
|
||||
{
|
||||
color = _CategoryColors[c];
|
||||
break;
|
||||
}
|
||||
}
|
||||
color *= IN.color;
|
||||
|
||||
#ifdef UNITY_UI_CLIP_RECT
|
||||
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5b24284f35f4992bcd4cc43919267d7
|
||||
timeCreated: 1724246251
|
34
Assets/Mirror/Components/Profiling/NetworkPingGraph.cs
Normal file
34
Assets/Mirror/Components/Profiling/NetworkPingGraph.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkPingGraph : BaseUIGraph
|
||||
{
|
||||
protected override void CollectData(int category, out float value, out GraphAggregationMode mode)
|
||||
{
|
||||
mode = GraphAggregationMode.Average;
|
||||
switch (category)
|
||||
{
|
||||
case 0:
|
||||
value = (float)NetworkTime.rtt * 1000f;
|
||||
break;
|
||||
case 1:
|
||||
value = (float)NetworkTime.rttVariance * 1000f;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"{category} is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override string FormatValue(float value) => $"{value:N0}ms";
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (CategoryColors.Length != 2)
|
||||
CategoryColors = new[] { Color.cyan, Color.yellow };
|
||||
|
||||
IsStacked = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83392ae5c1b731446909f252fd494ae4
|
||||
guid: 14a68afedbbf4568b0decc5c3fe6dfd9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
315
Assets/Mirror/Components/Profiling/NetworkRuntimeProfiler.cs
Normal file
315
Assets/Mirror/Components/Profiling/NetworkRuntimeProfiler.cs
Normal file
@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Mirror.RemoteCalls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkRuntimeProfiler : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class Sorter : IComparer<Stat>
|
||||
{
|
||||
public SortBy Order;
|
||||
|
||||
public int Compare(Stat a, Stat b)
|
||||
{
|
||||
if (a == null)
|
||||
return 1;
|
||||
|
||||
if (b == null)
|
||||
return -1;
|
||||
|
||||
// Compare B to A for desc order
|
||||
switch (Order)
|
||||
{
|
||||
case SortBy.RecentBytes:
|
||||
return b.RecentBytes.CompareTo(a.RecentBytes);
|
||||
case SortBy.RecentCount:
|
||||
return b.RecentCount.CompareTo(a.RecentCount);
|
||||
case SortBy.TotalBytes:
|
||||
return b.TotalBytes.CompareTo(a.TotalBytes);
|
||||
case SortBy.TotalCount:
|
||||
return b.TotalCount.CompareTo(a.TotalCount);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SortBy
|
||||
{
|
||||
RecentBytes,
|
||||
RecentCount,
|
||||
TotalBytes,
|
||||
TotalCount
|
||||
}
|
||||
|
||||
public class Stat
|
||||
{
|
||||
public string Name;
|
||||
public long TotalCount;
|
||||
public long TotalBytes;
|
||||
|
||||
public long RecentCount;
|
||||
public long RecentBytes;
|
||||
|
||||
public void ResetRecent()
|
||||
{
|
||||
RecentCount = 0;
|
||||
RecentBytes = 0;
|
||||
}
|
||||
|
||||
public void Add(int count, int bytes)
|
||||
{
|
||||
TotalBytes += bytes;
|
||||
TotalCount += count;
|
||||
|
||||
RecentBytes += bytes;
|
||||
RecentCount += count;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageStats
|
||||
{
|
||||
public readonly Dictionary<Type, Stat> MessageByType = new Dictionary<Type, Stat>();
|
||||
public readonly Dictionary<ushort, Stat> RpcByHash = new Dictionary<ushort, Stat>();
|
||||
|
||||
public void Record(NetworkDiagnostics.MessageInfo info)
|
||||
{
|
||||
Type type = info.message.GetType();
|
||||
if (!MessageByType.TryGetValue(type, out Stat stat))
|
||||
{
|
||||
stat = new Stat
|
||||
{
|
||||
Name = type.ToString(),
|
||||
TotalCount = 0,
|
||||
TotalBytes = 0,
|
||||
RecentCount = 0,
|
||||
RecentBytes = 0
|
||||
};
|
||||
|
||||
MessageByType[type] = stat;
|
||||
}
|
||||
|
||||
stat.Add(info.count, info.bytes * info.count);
|
||||
|
||||
if (info.message is CommandMessage cmd)
|
||||
RecordRpc(cmd.functionHash, info);
|
||||
else if (info.message is RpcMessage rpc)
|
||||
RecordRpc(rpc.functionHash, info);
|
||||
}
|
||||
|
||||
void RecordRpc(ushort hash, NetworkDiagnostics.MessageInfo info)
|
||||
{
|
||||
if (!RpcByHash.TryGetValue(hash, out Stat stat))
|
||||
{
|
||||
string name = "n/a";
|
||||
RemoteCallDelegate rpcDelegate = RemoteProcedureCalls.GetDelegate(hash);
|
||||
if (rpcDelegate != null)
|
||||
name = $"{rpcDelegate.Method.DeclaringType}.{rpcDelegate.GetMethodName().Replace(RemoteProcedureCalls.InvokeRpcPrefix, "")}";
|
||||
|
||||
stat = new Stat
|
||||
{
|
||||
Name = name,
|
||||
TotalCount = 0,
|
||||
TotalBytes = 0,
|
||||
RecentCount = 0,
|
||||
RecentBytes = 0
|
||||
};
|
||||
|
||||
RpcByHash[hash] = stat;
|
||||
}
|
||||
|
||||
stat.Add(info.count, info.bytes * info.count);
|
||||
}
|
||||
|
||||
public void ResetRecent()
|
||||
{
|
||||
foreach (Stat stat in MessageByType.Values)
|
||||
stat.ResetRecent();
|
||||
|
||||
foreach (Stat stat in RpcByHash.Values)
|
||||
stat.ResetRecent();
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("How many seconds to accumulate 'recent' stats for, this is also the output interval")]
|
||||
public float RecentDuration = 5;
|
||||
public Sorter Sort = new Sorter();
|
||||
|
||||
public enum OutputType
|
||||
{
|
||||
UnityLog,
|
||||
StdOut,
|
||||
File
|
||||
}
|
||||
|
||||
public OutputType Output;
|
||||
[Tooltip("If Output is set to 'File', where to the path of that file")]
|
||||
public string OutputFilePath = "network-stats.log";
|
||||
|
||||
readonly MessageStats inStats = new MessageStats();
|
||||
readonly MessageStats outStats = new MessageStats();
|
||||
readonly StringBuilder printBuilder = new StringBuilder();
|
||||
float elapsedSinceReset;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Ordering, Awake happens before NetworkDiagnostics reset
|
||||
NetworkDiagnostics.InMessageEvent += HandleMessageIn;
|
||||
NetworkDiagnostics.OutMessageEvent += HandleMessageOut;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
NetworkDiagnostics.InMessageEvent -= HandleMessageIn;
|
||||
NetworkDiagnostics.OutMessageEvent -= HandleMessageOut;
|
||||
}
|
||||
|
||||
void HandleMessageOut(NetworkDiagnostics.MessageInfo info) => outStats.Record(info);
|
||||
|
||||
void HandleMessageIn(NetworkDiagnostics.MessageInfo info) => inStats.Record(info);
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
elapsedSinceReset += Time.deltaTime;
|
||||
if (elapsedSinceReset > RecentDuration)
|
||||
{
|
||||
elapsedSinceReset = 0;
|
||||
Print();
|
||||
inStats.ResetRecent();
|
||||
outStats.ResetRecent();
|
||||
}
|
||||
}
|
||||
|
||||
void Print()
|
||||
{
|
||||
printBuilder.Clear();
|
||||
printBuilder.AppendLine($"Stats for {DateTime.Now} ({RecentDuration:N1}s interval)");
|
||||
int nameMaxLength = "OUT Message".Length;
|
||||
|
||||
foreach (Stat stat in inStats.MessageByType.Values)
|
||||
if (stat.Name.Length > nameMaxLength)
|
||||
nameMaxLength = stat.Name.Length;
|
||||
|
||||
foreach (Stat stat in outStats.MessageByType.Values)
|
||||
if (stat.Name.Length > nameMaxLength)
|
||||
nameMaxLength = stat.Name.Length;
|
||||
|
||||
foreach (Stat stat in inStats.RpcByHash.Values)
|
||||
if (stat.Name.Length > nameMaxLength)
|
||||
nameMaxLength = stat.Name.Length;
|
||||
|
||||
foreach (Stat stat in outStats.RpcByHash.Values)
|
||||
if (stat.Name.Length > nameMaxLength)
|
||||
nameMaxLength = stat.Name.Length;
|
||||
|
||||
string recentBytes = "Recent Bytes";
|
||||
string recentCount = "Recent Count";
|
||||
string totalBytes = "Total Bytes";
|
||||
string totalCount = "Total Count";
|
||||
int maxBytesLength = FormatBytes(999999).Length;
|
||||
int maxCountLength = FormatCount(999999).Length;
|
||||
|
||||
int recentBytesPad = Mathf.Max(recentBytes.Length, maxBytesLength);
|
||||
int recentCountPad = Mathf.Max(recentCount.Length, maxCountLength);
|
||||
int totalBytesPad = Mathf.Max(totalBytes.Length, maxBytesLength);
|
||||
int totalCountPad = Mathf.Max(totalCount.Length, maxCountLength);
|
||||
string header = $"| {"IN Message".PadLeft(nameMaxLength)} | {recentBytes.PadLeft(recentBytesPad)} | {recentCount.PadLeft(recentCountPad)} | {totalBytes.PadLeft(totalBytesPad)} | {totalCount.PadLeft(totalCountPad)} |";
|
||||
string sep = "".PadLeft(header.Length, '-');
|
||||
printBuilder.AppendLine(sep);
|
||||
printBuilder.AppendLine(header);
|
||||
printBuilder.AppendLine(sep);
|
||||
|
||||
foreach (Stat stat in inStats.MessageByType.Values.OrderBy(stat => stat, Sort))
|
||||
printBuilder.AppendLine($"| {stat.Name.PadLeft(nameMaxLength)} | {FormatBytes(stat.RecentBytes).PadLeft(recentBytesPad)} | {FormatCount(stat.RecentCount).PadLeft(recentCountPad)} | {FormatBytes(stat.TotalBytes).PadLeft(totalBytesPad)} | {FormatCount(stat.TotalCount).PadLeft(totalCountPad)} |");
|
||||
|
||||
header = $"| {"IN RPCs".PadLeft(nameMaxLength)} | {recentBytes.PadLeft(recentBytesPad)} | {recentCount.PadLeft(recentCountPad)} | {totalBytes.PadLeft(totalBytesPad)} | {totalCount.PadLeft(totalCountPad)} |";
|
||||
printBuilder.AppendLine(sep);
|
||||
printBuilder.AppendLine(header);
|
||||
printBuilder.AppendLine(sep);
|
||||
foreach (Stat stat in inStats.RpcByHash.Values.OrderBy(stat => stat, Sort))
|
||||
printBuilder.AppendLine($"| {stat.Name.PadLeft(nameMaxLength)} | {FormatBytes(stat.RecentBytes).PadLeft(recentBytesPad)} | {FormatCount(stat.RecentCount).PadLeft(recentCountPad)} | {FormatBytes(stat.TotalBytes).PadLeft(totalBytesPad)} | {FormatCount(stat.TotalCount).PadLeft(totalCountPad)} |");
|
||||
|
||||
header = $"| {"OUT Message".PadLeft(nameMaxLength)} | {recentBytes.PadLeft(recentBytesPad)} | {recentCount.PadLeft(recentCountPad)} | {totalBytes.PadLeft(totalBytesPad)} | {totalCount.PadLeft(totalCountPad)} |";
|
||||
printBuilder.AppendLine(sep);
|
||||
printBuilder.AppendLine(header);
|
||||
printBuilder.AppendLine(sep);
|
||||
foreach (Stat stat in outStats.MessageByType.Values.OrderBy(stat => stat, Sort))
|
||||
printBuilder.AppendLine($"| {stat.Name.PadLeft(nameMaxLength)} | {FormatBytes(stat.RecentBytes).PadLeft(recentBytesPad)} | {FormatCount(stat.RecentCount).PadLeft(recentCountPad)} | {FormatBytes(stat.TotalBytes).PadLeft(totalBytesPad)} | {FormatCount(stat.TotalCount).PadLeft(totalCountPad)} |");
|
||||
|
||||
header = $"| {"OUT RPCs".PadLeft(nameMaxLength)} | {recentBytes.PadLeft(recentBytesPad)} | {recentCount.PadLeft(recentCountPad)} | {totalBytes.PadLeft(totalBytesPad)} | {totalCount.PadLeft(totalCountPad)} |";
|
||||
printBuilder.AppendLine(sep);
|
||||
printBuilder.AppendLine(header);
|
||||
printBuilder.AppendLine(sep);
|
||||
|
||||
foreach (Stat stat in outStats.RpcByHash.Values.OrderBy(stat => stat, Sort))
|
||||
printBuilder.AppendLine($"| {stat.Name.PadLeft(nameMaxLength)} | {FormatBytes(stat.RecentBytes).PadLeft(recentBytesPad)} | {FormatCount(stat.RecentCount).PadLeft(recentCountPad)} | {FormatBytes(stat.TotalBytes).PadLeft(totalBytesPad)} | {FormatCount(stat.TotalCount).PadLeft(totalCountPad)} |");
|
||||
|
||||
printBuilder.AppendLine(sep);
|
||||
|
||||
switch (Output)
|
||||
{
|
||||
case OutputType.UnityLog:
|
||||
Debug.Log(printBuilder.ToString());
|
||||
break;
|
||||
case OutputType.StdOut:
|
||||
Console.Write(printBuilder);
|
||||
break;
|
||||
case OutputType.File:
|
||||
File.AppendAllText(OutputFilePath, printBuilder.ToString());
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
static string FormatBytes(long bytes)
|
||||
{
|
||||
const double KiB = 1024;
|
||||
const double MiB = KiB * 1024;
|
||||
const double GiB = MiB * 1024;
|
||||
const double TiB = GiB * 1024;
|
||||
|
||||
if (bytes < KiB)
|
||||
return $"{bytes:N0} B";
|
||||
|
||||
if (bytes < MiB)
|
||||
return $"{bytes / KiB:N2} KiB";
|
||||
|
||||
if (bytes < GiB)
|
||||
return $"{bytes / MiB:N2} MiB";
|
||||
|
||||
if (bytes < TiB)
|
||||
return $"{bytes / GiB:N2} GiB";
|
||||
|
||||
return $"{bytes / TiB:N2} TiB";
|
||||
}
|
||||
|
||||
string FormatCount(long count)
|
||||
{
|
||||
const double K = 1000;
|
||||
const double M = K * 1000;
|
||||
const double G = M * 1000;
|
||||
const double T = G * 1000;
|
||||
|
||||
if (count < K)
|
||||
return $"{count:N0}";
|
||||
|
||||
if (count < M)
|
||||
return $"{count / K:N2} K";
|
||||
|
||||
if (count < G)
|
||||
return $"{count / M:N2} M";
|
||||
|
||||
if (count < T)
|
||||
return $"{count / G:N2} G";
|
||||
|
||||
return $"{count / T:N2} T";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f032128052c95a46afb0ddd97d994cc
|
||||
guid: ef8db82aeb77400bb9e80850e39065a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
85
Assets/Mirror/Components/Profiling/NetworkUsageGraph.cs
Normal file
85
Assets/Mirror/Components/Profiling/NetworkUsageGraph.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
namespace Mirror
|
||||
{
|
||||
public class NetworkUsageGraph : BaseUIGraph
|
||||
{
|
||||
int dataIn;
|
||||
int dataOut;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Ordering, Awake happens before NetworkDiagnostics reset
|
||||
NetworkDiagnostics.InMessageEvent += OnReceive;
|
||||
NetworkDiagnostics.OutMessageEvent += OnSend;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// If we've been inactive, clear counter
|
||||
dataIn = 0;
|
||||
dataOut = 0;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
NetworkDiagnostics.InMessageEvent -= OnReceive;
|
||||
NetworkDiagnostics.OutMessageEvent -= OnSend;
|
||||
}
|
||||
|
||||
void OnSend(NetworkDiagnostics.MessageInfo obj) => dataOut += obj.bytes;
|
||||
|
||||
void OnReceive(NetworkDiagnostics.MessageInfo obj) => dataIn += obj.bytes;
|
||||
|
||||
protected override void CollectData(int category, out float value, out GraphAggregationMode mode)
|
||||
{
|
||||
mode = GraphAggregationMode.PerSecond;
|
||||
switch (category)
|
||||
{
|
||||
case 0:
|
||||
value = dataIn;
|
||||
dataIn = 0;
|
||||
break;
|
||||
case 1:
|
||||
value = dataOut;
|
||||
dataOut = 0;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"{category} is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string[] Units = new[] { "B/s", "KiB/s", "MiB/s" };
|
||||
const float UnitScale = 1024;
|
||||
|
||||
protected override string FormatValue(float value)
|
||||
{
|
||||
string selectedUnit = null;
|
||||
for (int i = 0; i < Units.Length; i++)
|
||||
{
|
||||
string unit = Units[i];
|
||||
selectedUnit = unit;
|
||||
if (i > 0)
|
||||
value /= UnitScale;
|
||||
|
||||
if (value < UnitScale)
|
||||
break;
|
||||
}
|
||||
|
||||
return $"{value:N0} {selectedUnit}";
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
if (CategoryColors.Length != 2)
|
||||
CategoryColors = new[]
|
||||
{
|
||||
Color.red, // min
|
||||
Color.green // max
|
||||
};
|
||||
|
||||
IsStacked = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab2cbc52526ea384ba280d13cd1a57b9
|
||||
guid: e1ae19b97f0e4a5eb8cf5158d97506f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfbf2a1f2b300c5489dcab219ef2846e
|
||||
guid: 083c6613a11cad746bb252bc7748947f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
1976
Assets/Mirror/Components/Profiling/Prefabs/FPSMinMaxAvg.prefab
Normal file
1976
Assets/Mirror/Components/Profiling/Prefabs/FPSMinMaxAvg.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bdc42bca9b7109428d00fe33bdb5102
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
765
Assets/Mirror/Components/Profiling/Prefabs/GraphCanvas.prefab
Normal file
765
Assets/Mirror/Components/Profiling/Prefabs/GraphCanvas.prefab
Normal file
@ -0,0 +1,765 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &2777084101886578567
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3864348419064627986}
|
||||
- component: {fileID: 4699359553083341963}
|
||||
m_Layer: 5
|
||||
m_Name: Graphs
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &3864348419064627986
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2777084101886578567}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children:
|
||||
- {fileID: 4509006437822490170}
|
||||
- {fileID: 522813179161291686}
|
||||
- {fileID: 7802229218722708586}
|
||||
m_Father: {fileID: 5551289487596275721}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &4699359553083341963
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2777084101886578567}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 10
|
||||
m_Right: 0
|
||||
m_Top: 10
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 2
|
||||
m_Spacing: 0
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 0
|
||||
m_ChildControlHeight: 0
|
||||
m_ChildScaleWidth: 0
|
||||
m_ChildScaleHeight: 0
|
||||
--- !u!1 &4512081604627528395
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 5551289487596275721}
|
||||
- component: {fileID: 5641452096830013034}
|
||||
- component: {fileID: 4961392508020504993}
|
||||
- component: {fileID: 1349218098020237989}
|
||||
- component: {fileID: 8415974033864006777}
|
||||
m_Layer: 5
|
||||
m_Name: GraphCanvas
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &5551289487596275721
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4512081604627528395}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_Children:
|
||||
- {fileID: 3864348419064627986}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!223 &5641452096830013034
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4512081604627528395}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_RenderMode: 0
|
||||
m_Camera: {fileID: 0}
|
||||
m_PlaneDistance: 100
|
||||
m_PixelPerfect: 0
|
||||
m_ReceivesEvents: 1
|
||||
m_OverrideSorting: 0
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_AdditionalShaderChannelsFlag: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 0
|
||||
m_TargetDisplay: 0
|
||||
--- !u!114 &4961392508020504993
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4512081604627528395}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 1280, y: 720}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 1
|
||||
m_PhysicalUnit: 3
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
--- !u!114 &1349218098020237989
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4512081604627528395}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
--- !u!114 &8415974033864006777
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4512081604627528395}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a27b133d890c41828d3b01ffa12fe440, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Key: 291
|
||||
ToToggle: {fileID: 2777084101886578567}
|
||||
--- !u!1001 &4204022305993221602
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
m_TransformParent: {fileID: 3864348419064627986}
|
||||
m_Modifications:
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 86.666664
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 43.333332
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205212282745, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 86.666664
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 130
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546205281663118, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 300
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 150
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 1668.6208
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -85
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206339761113, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
value: FPSMinMaxAvg
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 86.666664
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 216.66666
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 343546206506823101, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 9bdc42bca9b7109428d00fe33bdb5102, type: 3}
|
||||
--- !u!224 &4509006437822490170 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 343546206339761112, guid: 9bdc42bca9b7109428d00fe33bdb5102,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 4204022305993221602}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &7469102913200609509
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
m_TransformParent: {fileID: 3864348419064627986}
|
||||
m_Modifications:
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 130
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 195
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 942441613928011978, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2997867828362567431, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
value: PingGraph
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 130
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 65
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6899184289734897349, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_RootOrder
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 300
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 150
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 1968.6208
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -85
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: ed3b4e27086dcc64b8c6605011a321e2, type: 3}
|
||||
--- !u!224 &522813179161291686 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 6982534731248530243, guid: ed3b4e27086dcc64b8c6605011a321e2,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 7469102913200609509}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &8208221870570858035
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
m_TransformParent: {fileID: 3864348419064627986}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 130
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 65
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1750250400045347475, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1986485545668258777, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: Points
|
||||
value: 32
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_RootOrder
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 300
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 150
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 2268.6208
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -85
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3670180597793129839, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
value: NetworkGraph
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 130
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 20
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 195
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8473536528693599174, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 3c7a97355c25a2b4da731b53876f8a8b, type: 3}
|
||||
--- !u!224 &7802229218722708586 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 2138752905301465689, guid: 3c7a97355c25a2b4da731b53876f8a8b,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 8208221870570858035}
|
||||
m_PrefabAsset: {fileID: 0}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e87e1847def3c1f41b19b7df4f0920b3
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1776
Assets/Mirror/Components/Profiling/Prefabs/NetworkGraph.prefab
Normal file
1776
Assets/Mirror/Components/Profiling/Prefabs/NetworkGraph.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c7a97355c25a2b4da731b53876f8a8b
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1776
Assets/Mirror/Components/Profiling/Prefabs/PingGraph.prefab
Normal file
1776
Assets/Mirror/Components/Profiling/Prefabs/PingGraph.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed3b4e27086dcc64b8c6605011a321e2
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
88
Assets/Mirror/Components/Profiling/StackedGraph.mat
Normal file
88
Assets/Mirror/Components/Profiling/StackedGraph.mat
Normal file
@ -0,0 +1,88 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: StackedGraph
|
||||
m_Shader: {fileID: 4800000, guid: b5b24284f35f4992bcd4cc43919267d7, type: 3}
|
||||
m_ShaderKeywords:
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Floats:
|
||||
- _BumpScale: 1
|
||||
- _CategoryCount: 0
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DataStart: 0
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _MaxValue: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _UseUIAlphaClip: 0
|
||||
- _Width: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
8
Assets/Mirror/Components/Profiling/StackedGraph.mat.meta
Normal file
8
Assets/Mirror/Components/Profiling/StackedGraph.mat.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14fba9d19cfe7f346bfb595965558722
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
15
Assets/Mirror/Components/Profiling/ToggleHotkey.cs
Normal file
15
Assets/Mirror/Components/Profiling/ToggleHotkey.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
namespace Mirror
|
||||
{
|
||||
public class ToggleHotkey : MonoBehaviour
|
||||
{
|
||||
public KeyCode Key = KeyCode.F10;
|
||||
public GameObject ToToggle;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(Key))
|
||||
ToToggle.SetActive(!ToToggle.activeSelf);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Components/Profiling/ToggleHotkey.cs.meta
Normal file
11
Assets/Mirror/Components/Profiling/ToggleHotkey.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a27b133d890c41828d3b01ffa12fe440
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -90,6 +90,12 @@ public class ShowInInspectorAttribute : Attribute {}
|
||||
/// <summary>
|
||||
/// Used to make a field readonly in the inspector
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class ReadOnlyAttribute : PropertyAttribute {}
|
||||
|
||||
/// <summary>
|
||||
/// When defining multiple Readers/Writers for the same type, indicate which one Weaver must use.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class WeaverPriorityAttribute : Attribute {}
|
||||
}
|
||||
|
@ -49,8 +49,16 @@ public static int MaxMessageOverhead(int messageSize) =>
|
||||
// => best to build batches on the fly.
|
||||
readonly Queue<NetworkWriterPooled> batches = new Queue<NetworkWriterPooled>();
|
||||
|
||||
// current batch in progress
|
||||
// current batch in progress.
|
||||
// we also store the timestamp to ensure we don't add a message from another frame,
|
||||
// as this would introduce subtle jitter!
|
||||
//
|
||||
// for example:
|
||||
// - a batch is started at t=1, another message is added at t=2 and then it's flushed
|
||||
// - NetworkTransform uses remoteTimestamp which is t=1
|
||||
// - snapshot interpolation would off by one (or multiple) frames!
|
||||
NetworkWriterPooled batch;
|
||||
double batchTimestamp;
|
||||
|
||||
public Batcher(int threshold)
|
||||
{
|
||||
@ -62,6 +70,32 @@ public Batcher(int threshold)
|
||||
// caller needs to make sure they are within max packet size.
|
||||
public void AddMessage(ArraySegment<byte> message, double timeStamp)
|
||||
{
|
||||
// safety: message timestamp is only written once.
|
||||
// make sure all messages in this batch are from the same timestamp.
|
||||
// otherwise it could silently introduce jitter.
|
||||
//
|
||||
// this happened before:
|
||||
// - NetworkEarlyUpdate @ t=1 processes transport messages
|
||||
// - a handler replies by sending a message
|
||||
// - a new batch is started @ t=1, timestamp is encoded
|
||||
// - NetworkLateUpdate @ t=2 decides it's time to broadcast
|
||||
// - NetworkTransform sends @ t=2
|
||||
// - we add to the above batch which already encoded t=1
|
||||
// - Client receives the batch which timestamp t=1
|
||||
// - NetworkTransform uses remoteTime for interpolation
|
||||
// remoteTime is the batch timestamp which is t=1
|
||||
// - the NetworkTransform message is actually t=2
|
||||
// => smooth interpolation would be impossible!
|
||||
// NT thinks the position was @ t=1 but actually it was @ t=2 !
|
||||
//
|
||||
// the solution: if timestamp changed, enqueue the existing batch
|
||||
if (batch != null && batchTimestamp != timeStamp)
|
||||
{
|
||||
batches.Enqueue(batch);
|
||||
batch = null;
|
||||
batchTimestamp = 0;
|
||||
}
|
||||
|
||||
// predict the needed size, which is varint(size) + content
|
||||
int headerSize = Compression.VarUIntSize((ulong)message.Count);
|
||||
int neededSize = headerSize + message.Count;
|
||||
@ -76,6 +110,7 @@ public void AddMessage(ArraySegment<byte> message, double timeStamp)
|
||||
{
|
||||
batches.Enqueue(batch);
|
||||
batch = null;
|
||||
batchTimestamp = 0;
|
||||
}
|
||||
|
||||
// initialize a new batch if necessary
|
||||
@ -89,6 +124,9 @@ public void AddMessage(ArraySegment<byte> message, double timeStamp)
|
||||
// -> batches are per-frame, it doesn't matter which message's
|
||||
// timestamp we use.
|
||||
batch.WriteDouble(timeStamp);
|
||||
|
||||
// remember the encoded timestamp, see safety check below.
|
||||
batchTimestamp = timeStamp;
|
||||
}
|
||||
|
||||
// add serialization to current batch. even if > threshold.
|
||||
@ -155,6 +193,7 @@ public void Clear()
|
||||
{
|
||||
NetworkWriterPool.Return(batch);
|
||||
batch = null;
|
||||
batchTimestamp = 0;
|
||||
}
|
||||
|
||||
// return all queued batches
|
||||
|
@ -29,7 +29,7 @@ public static Color ColorCode(this ConnectionQuality quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case ConnectionQuality.POOR: return Color.red;
|
||||
case ConnectionQuality.POOR: return Color.red;
|
||||
case ConnectionQuality.FAIR: return new Color(1.0f, 0.647f, 0.0f);
|
||||
case ConnectionQuality.GOOD: return Color.yellow;
|
||||
case ConnectionQuality.EXCELLENT: return Color.green;
|
||||
|
@ -14,8 +14,6 @@ public class LocalConnectionToClient : NetworkConnectionToClient
|
||||
|
||||
public LocalConnectionToClient() : base(LocalConnectionId) {}
|
||||
|
||||
public override string address => "localhost";
|
||||
|
||||
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.Reliable)
|
||||
{
|
||||
// instead of invoking it directly, we enqueue and process next update.
|
||||
|
@ -149,6 +149,35 @@ public bool authority
|
||||
// hook guard prevents that.
|
||||
ulong syncVarHookGuard;
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
// Skip if Editor is in Play mode
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
// we now allow child NetworkBehaviours.
|
||||
// we can not [RequireComponent(typeof(NetworkIdentity))] anymore.
|
||||
// instead, we need to ensure a NetworkIdentity is somewhere in the
|
||||
// parents.
|
||||
// only run this in Editor. don't add more runtime overhead.
|
||||
|
||||
// GetComponentInParent(includeInactive) is needed because Prefabs are not
|
||||
// considered active, so this check requires to scan inactive.
|
||||
#if UNITY_2021_3_OR_NEWER // 2021 has GetComponentInParent(bool includeInactive = false)
|
||||
if (GetComponent<NetworkIdentity>() == null &&
|
||||
GetComponentInParent<NetworkIdentity>(true) == null)
|
||||
{
|
||||
Debug.LogError($"{GetType()} on {name} requires a NetworkIdentity. Please add a NetworkIdentity component to {name} or it's parents.", this);
|
||||
}
|
||||
#elif UNITY_2020_3_OR_NEWER // 2020 only has GetComponentsInParent(bool includeInactive = false), we can use this too
|
||||
NetworkIdentity[] parentsIds = GetComponentsInParent<NetworkIdentity>(true);
|
||||
int parentIdsCount = parentsIds != null ? parentsIds.Length : 0;
|
||||
if (GetComponent<NetworkIdentity>() == null && parentIdsCount == 0)
|
||||
{
|
||||
Debug.LogError($"{GetType()} on {name} requires a NetworkIdentity. Please add a NetworkIdentity component to {name} or it's parents.", this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// USED BY WEAVER to set syncvars in host mode without deadlocking
|
||||
protected bool GetSyncVarHookGuard(ulong dirtyBit) =>
|
||||
(syncVarHookGuard & dirtyBit) != 0UL;
|
||||
@ -302,34 +331,6 @@ protected void InitSyncObject(SyncObject syncObject)
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
// we now allow child NetworkBehaviours.
|
||||
// we can not [RequireComponent(typeof(NetworkIdentity))] anymore.
|
||||
// instead, we need to ensure a NetworkIdentity is somewhere in the
|
||||
// parents.
|
||||
// only run this in Editor. don't add more runtime overhead.
|
||||
|
||||
// GetComponentInParent(includeInactive) is needed because Prefabs are not
|
||||
// considered active, so this check requires to scan inactive.
|
||||
#if UNITY_EDITOR
|
||||
#if UNITY_2021_3_OR_NEWER // 2021 has GetComponentInParent(bool includeInactive = false)
|
||||
if (GetComponent<NetworkIdentity>() == null &&
|
||||
GetComponentInParent<NetworkIdentity>(true) == null)
|
||||
{
|
||||
Debug.LogError($"{GetType()} on {name} requires a NetworkIdentity. Please add a NetworkIdentity component to {name} or it's parents.", this);
|
||||
}
|
||||
#elif UNITY_2020_3_OR_NEWER // 2020 only has GetComponentsInParent(bool includeInactive = false), we can use this too
|
||||
NetworkIdentity[] parentsIds = GetComponentsInParent<NetworkIdentity>(true);
|
||||
int parentIdsCount = parentsIds != null ? parentsIds.Length : 0;
|
||||
if (GetComponent<NetworkIdentity>() == null && parentIdsCount == 0)
|
||||
{
|
||||
Debug.LogError($"{GetType()} on {name} requires a NetworkIdentity. Please add a NetworkIdentity component to {name} or it's parents.", this);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||
protected void SendCommandInternal(string functionFullName, int functionHashCode, NetworkWriter writer, int channelId, bool requiresAuthority = true)
|
||||
{
|
||||
@ -373,6 +374,12 @@ protected void SendCommandInternal(string functionFullName, int functionHashCode
|
||||
return;
|
||||
}
|
||||
|
||||
if (netId == 0)
|
||||
{
|
||||
Debug.LogWarning($"Command {functionFullName} called on {name} with netId=0. Maybe it wasn't spawned yet?", gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
// construct the message
|
||||
CommandMessage message = new CommandMessage
|
||||
{
|
||||
@ -1122,7 +1129,7 @@ public virtual void OnDeserialize(NetworkReader reader, bool initialState)
|
||||
|
||||
void SerializeSyncObjects(NetworkWriter writer, bool initialState)
|
||||
{
|
||||
// if initialState: write all SyncVars.
|
||||
// if initialState: write all SyncObjects (SyncList/Set/etc)
|
||||
// otherwise write dirtyBits+dirty SyncVars
|
||||
if (initialState)
|
||||
SerializeObjectsAll(writer);
|
||||
|
@ -52,7 +52,7 @@ public static partial class NetworkClient
|
||||
new Dictionary<uint, NetworkIdentity>();
|
||||
|
||||
/// <summary>Client's NetworkConnection to server.</summary>
|
||||
public static NetworkConnection connection { get; internal set; }
|
||||
public static NetworkConnectionToServer connection { get; internal set; }
|
||||
|
||||
/// <summary>True if client is ready (= joined world).</summary>
|
||||
// TODO redundant state. point it to .connection.isReady instead (& test)
|
||||
@ -519,7 +519,7 @@ internal static void RegisterMessageHandlers(bool hostMode)
|
||||
}
|
||||
|
||||
// These handlers are the same for host and remote clients
|
||||
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage);
|
||||
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, false); // unreliable may arrive before reliable authority went through
|
||||
RegisterHandler<ChangeOwnerMessage>(OnChangeOwner);
|
||||
RegisterHandler<RpcMessage>(OnRPCMessage);
|
||||
}
|
||||
@ -565,23 +565,6 @@ public static void RegisterHandler<T>(Action<T, int> handler, bool requireAuthen
|
||||
handlers[msgType] = NetworkMessages.WrapHandler((Action<NetworkConnection, T, int>)HandlerWrapped, requireAuthentication, exceptionsDisconnect);
|
||||
}
|
||||
|
||||
// Deprecated 2024-01-21
|
||||
[Obsolete("Use ReplaceHandler without the NetworkConnection parameter instead. This version is obsolete and will be removed soon.")]
|
||||
public static void ReplaceHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true)
|
||||
where T : struct, NetworkMessage
|
||||
{
|
||||
// we use the same WrapHandler function for server and client.
|
||||
// so let's wrap it to ignore the NetworkConnection parameter.
|
||||
// it's not needed on client. it's always NetworkClient.connection.
|
||||
ushort msgType = NetworkMessageId<T>.Id;
|
||||
|
||||
// register Id <> Type in lookup for debugging.
|
||||
NetworkMessages.Lookup[msgType] = typeof(T);
|
||||
|
||||
void HandlerWrapped(NetworkConnection _, T value) => handler(_, value);
|
||||
handlers[msgType] = NetworkMessages.WrapHandler((Action<NetworkConnection, T>)HandlerWrapped, requireAuthentication, exceptionsDisconnect);
|
||||
}
|
||||
|
||||
/// <summary>Replace a handler for a particular message type. Should require authentication by default.</summary>
|
||||
// RegisterHandler throws a warning (as it should) if a handler is assigned twice
|
||||
// Use of ReplaceHandler makes it clear the user intended to replace the handler
|
||||
@ -1391,6 +1374,47 @@ internal static void OnHostClientSpawn(SpawnMessage message)
|
||||
}
|
||||
}
|
||||
|
||||
// configure flags & invoke callbacks
|
||||
static void BootstrapIdentity(NetworkIdentity identity)
|
||||
{
|
||||
InitializeIdentityFlags(identity);
|
||||
InvokeIdentityCallbacks(identity);
|
||||
}
|
||||
|
||||
// set up NetworkIdentity flags on the client.
|
||||
// needs to be separate from invoking callbacks.
|
||||
// cleaner, and some places need to set flags first.
|
||||
static void InitializeIdentityFlags(NetworkIdentity identity)
|
||||
{
|
||||
// initialize flags before invoking callbacks.
|
||||
// this way isClient/isLocalPlayer is correct during callbacks.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3362
|
||||
identity.isClient = true;
|
||||
identity.isLocalPlayer = localPlayer == identity;
|
||||
|
||||
// .connectionToServer is only available for local players.
|
||||
// set it here, before invoking any callbacks.
|
||||
// this way it's available in _all_ callbacks.
|
||||
if (identity.isLocalPlayer)
|
||||
identity.connectionToServer = connection;
|
||||
}
|
||||
|
||||
// invoke NetworkIdentity callbacks on the client.
|
||||
// needs to be separate from configuring flags.
|
||||
// cleaner, and some places need to set flags first.
|
||||
static void InvokeIdentityCallbacks(NetworkIdentity identity)
|
||||
{
|
||||
// invoke OnStartClient
|
||||
identity.OnStartClient();
|
||||
|
||||
// invoke OnStartAuthority
|
||||
identity.NotifyAuthority();
|
||||
|
||||
// invoke OnStartLocalPlayer
|
||||
if (identity.isLocalPlayer)
|
||||
identity.OnStartLocalPlayer();
|
||||
}
|
||||
|
||||
// client-only mode callbacks //////////////////////////////////////////
|
||||
static void OnEntityStateMessage(EntityStateMessage message)
|
||||
{
|
||||
@ -1480,99 +1504,6 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me
|
||||
}
|
||||
}
|
||||
|
||||
// set up NetworkIdentity flags on the client.
|
||||
// needs to be separate from invoking callbacks.
|
||||
// cleaner, and some places need to set flags first.
|
||||
static void InitializeIdentityFlags(NetworkIdentity identity)
|
||||
{
|
||||
// initialize flags before invoking callbacks.
|
||||
// this way isClient/isLocalPlayer is correct during callbacks.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3362
|
||||
identity.isClient = true;
|
||||
identity.isLocalPlayer = localPlayer == identity;
|
||||
|
||||
// .connectionToServer is only available for local players.
|
||||
// set it here, before invoking any callbacks.
|
||||
// this way it's available in _all_ callbacks.
|
||||
if (identity.isLocalPlayer)
|
||||
identity.connectionToServer = connection;
|
||||
}
|
||||
|
||||
// invoke NetworkIdentity callbacks on the client.
|
||||
// needs to be separate from configuring flags.
|
||||
// cleaner, and some places need to set flags first.
|
||||
static void InvokeIdentityCallbacks(NetworkIdentity identity)
|
||||
{
|
||||
// invoke OnStartAuthority
|
||||
identity.NotifyAuthority();
|
||||
|
||||
// invoke OnStartClient
|
||||
identity.OnStartClient();
|
||||
|
||||
// invoke OnStartLocalPlayer
|
||||
if (identity.isLocalPlayer)
|
||||
identity.OnStartLocalPlayer();
|
||||
}
|
||||
|
||||
// configure flags & invoke callbacks
|
||||
static void BootstrapIdentity(NetworkIdentity identity)
|
||||
{
|
||||
InitializeIdentityFlags(identity);
|
||||
InvokeIdentityCallbacks(identity);
|
||||
}
|
||||
|
||||
// broadcast ///////////////////////////////////////////////////////////
|
||||
static void BroadcastTimeSnapshot()
|
||||
{
|
||||
Send(new TimeSnapshotMessage(), Channels.Unreliable);
|
||||
}
|
||||
|
||||
// make sure Broadcast() is only called every sendInterval.
|
||||
// calling it every update() would require too much bandwidth.
|
||||
static void Broadcast()
|
||||
{
|
||||
// joined the world yet?
|
||||
if (!connection.isReady) return;
|
||||
|
||||
// nothing to do in host mode. server already knows the state.
|
||||
if (NetworkServer.active) return;
|
||||
|
||||
// send time snapshot every sendInterval.
|
||||
BroadcastTimeSnapshot();
|
||||
|
||||
// for each entity that the client owns
|
||||
foreach (NetworkIdentity identity in connection.owned)
|
||||
{
|
||||
// make sure it's not null or destroyed.
|
||||
// (which can happen if someone uses
|
||||
// GameObject.Destroy instead of
|
||||
// NetworkServer.Destroy)
|
||||
if (identity != null)
|
||||
{
|
||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||
{
|
||||
// get serialization for this entity viewed by this connection
|
||||
// (if anything was serialized this time)
|
||||
identity.SerializeClient(writer);
|
||||
if (writer.Position > 0)
|
||||
{
|
||||
// send state update message
|
||||
EntityStateMessage message = new EntityStateMessage
|
||||
{
|
||||
netId = identity.netId,
|
||||
payload = writer.ToArraySegment()
|
||||
};
|
||||
Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// spawned list should have no null entries because we
|
||||
// always call Remove in OnObjectDestroy everywhere.
|
||||
// if it does have null then we missed something.
|
||||
else Debug.LogWarning($"Found 'null' entry in owned list for client. This is unexpected behaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
// update //////////////////////////////////////////////////////////////
|
||||
// NetworkEarlyUpdate called before any Update/FixedUpdate
|
||||
// (we add this to the UnityEngine in NetworkLoop)
|
||||
@ -1608,8 +1539,8 @@ internal static void NetworkLateUpdate()
|
||||
// snapshots _but_ not every single tick.
|
||||
//
|
||||
// Unity 2019 doesn't have Time.timeAsDouble yet
|
||||
if (!Application.isPlaying ||
|
||||
AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime))
|
||||
bool sendIntervalElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime);
|
||||
if (!Application.isPlaying || sendIntervalElapsed)
|
||||
{
|
||||
Broadcast();
|
||||
}
|
||||
@ -1672,6 +1603,61 @@ void UpdateConnectionQuality()
|
||||
Transport.active.ClientLateUpdate();
|
||||
}
|
||||
|
||||
// broadcast ///////////////////////////////////////////////////////////
|
||||
// make sure Broadcast() is only called every sendInterval.
|
||||
// calling it every update() would require too much bandwidth.
|
||||
static void Broadcast()
|
||||
{
|
||||
// joined the world yet?
|
||||
if (!connection.isReady) return;
|
||||
|
||||
// nothing to do in host mode. server already knows the state.
|
||||
if (NetworkServer.active) return;
|
||||
|
||||
// send time snapshot every sendInterval.
|
||||
Send(new TimeSnapshotMessage(), Channels.Unreliable);
|
||||
|
||||
// broadcast client state to server
|
||||
BroadcastToServer();
|
||||
}
|
||||
|
||||
// NetworkServer has BroadcastToConnection.
|
||||
// NetworkClient has BroadcastToServer.
|
||||
static void BroadcastToServer()
|
||||
{
|
||||
// for each entity that the client owns
|
||||
foreach (NetworkIdentity identity in connection.owned)
|
||||
{
|
||||
// make sure it's not null or destroyed.
|
||||
// (which can happen if someone uses
|
||||
// GameObject.Destroy instead of
|
||||
// NetworkServer.Destroy)
|
||||
if (identity != null)
|
||||
{
|
||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||
{
|
||||
// get serialization for this entity viewed by this connection
|
||||
// (if anything was serialized this time)
|
||||
identity.SerializeClient(writer);
|
||||
if (writer.Position > 0)
|
||||
{
|
||||
// send state update message
|
||||
EntityStateMessage message = new EntityStateMessage
|
||||
{
|
||||
netId = identity.netId,
|
||||
payload = writer.ToArraySegment()
|
||||
};
|
||||
Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// spawned list should have no null entries because we
|
||||
// always call Remove in OnObjectDestroy everywhere.
|
||||
// if it does have null then we missed something.
|
||||
else Debug.LogWarning($"Found 'null' entry in owned list for client. This is unexpected behaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
// destroy /////////////////////////////////////////////////////////////
|
||||
/// <summary>Destroys all networked objects on the client.</summary>
|
||||
// Note: NetworkServer.CleanupNetworkIdentities does the same on server.
|
||||
|
@ -39,8 +39,6 @@ public static partial class NetworkClient
|
||||
internal static double localTimescale = 1;
|
||||
|
||||
// catchup /////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// we use EMA to average the last second worth of snapshot time diffs.
|
||||
// manually averaging the last second worth of values with a for loop
|
||||
// would be the same, but a moving average is faster because we only
|
||||
@ -48,31 +46,16 @@ public static partial class NetworkClient
|
||||
static ExponentialMovingAverage driftEma;
|
||||
|
||||
// dynamic buffer time adjustment //////////////////////////////////////
|
||||
// dynamically adjusts bufferTimeMultiplier for smooth results.
|
||||
// to understand how this works, try this manually:
|
||||
//
|
||||
// - disable dynamic adjustment
|
||||
// - set jitter = 0.2 (20% is a lot!)
|
||||
// - notice some stuttering
|
||||
// - disable interpolation to see just how much jitter this really is(!)
|
||||
// - enable interpolation again
|
||||
// - manually increase bufferTimeMultiplier to 3-4
|
||||
// ... the cube slows down (blue) until it's smooth
|
||||
// - with dynamic adjustment enabled, it will set 4 automatically
|
||||
// ... the cube slows down (blue) until it's smooth as well
|
||||
//
|
||||
// note that 20% jitter is extreme.
|
||||
// for this to be perfectly smooth, set the safety tolerance to '2'.
|
||||
// but realistically this is not necessary, and '1' is enough.
|
||||
[Header("Snapshot Interpolation: Dynamic Adjustment")]
|
||||
[Tooltip("Automatically adjust bufferTimeMultiplier for smooth results.\nSets a low multiplier on stable connections, and a high multiplier on jittery connections.")]
|
||||
public static bool dynamicAdjustment = true;
|
||||
// DEPRECATED 2024-10-08
|
||||
[Obsolete("NeworkClient.dynamicAdjustment was moved to NetworkClient.snapshotSettings.dynamicAdjustment")]
|
||||
public static bool dynamicAdjustment => snapshotSettings.dynamicAdjustment;
|
||||
// DEPRECATED 2024-10-08
|
||||
[Obsolete("NeworkClient.dynamicAdjustmentTolerance was moved to NetworkClient.snapshotSettings.dynamicAdjustmentTolerance")]
|
||||
public static float dynamicAdjustmentTolerance => snapshotSettings.dynamicAdjustmentTolerance;
|
||||
// DEPRECATED 2024-10-08
|
||||
[Obsolete("NeworkClient.dynamicAdjustment was moved to NetworkClient.snapshotSettings.dynamicAdjustment")]
|
||||
public static int deliveryTimeEmaDuration => snapshotSettings.deliveryTimeEmaDuration;
|
||||
|
||||
[Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")]
|
||||
public static float dynamicAdjustmentTolerance = 1; // 1 is realistically just fine, 2 is very very safe even for 20% jitter. can be half a frame too. (see above comments)
|
||||
|
||||
[Tooltip("Dynamic adjustment is computed over n-second exponential moving average standard deviation.")]
|
||||
public static int deliveryTimeEmaDuration = 2; // 1-2s recommended to capture average delivery time
|
||||
static ExponentialMovingAverage deliveryTimeEma; // average delivery time (standard deviation gives average jitter)
|
||||
|
||||
// OnValidate: see NetworkClient.cs
|
||||
|
@ -14,7 +14,7 @@ public class NetworkConnectionToClient : NetworkConnection
|
||||
readonly NetworkWriter reliableRpcs = new NetworkWriter();
|
||||
readonly NetworkWriter unreliableRpcs = new NetworkWriter();
|
||||
|
||||
public virtual string address => Transport.active.ServerGetClientAddress(connectionId);
|
||||
public virtual string address { get; private set; }
|
||||
|
||||
/// <summary>NetworkIdentities that this connection can see</summary>
|
||||
// TODO move to server's NetworkConnectionToClient?
|
||||
@ -50,9 +50,11 @@ public class NetworkConnectionToClient : NetworkConnection
|
||||
/// <summary>Round trip time (in seconds) that it takes a message to go server->client->server.</summary>
|
||||
public double rtt => _rtt.Value;
|
||||
|
||||
public NetworkConnectionToClient(int networkConnectionId)
|
||||
public NetworkConnectionToClient(int networkConnectionId, string clientAddress = "localhost")
|
||||
: base(networkConnectionId)
|
||||
{
|
||||
address = clientAddress;
|
||||
|
||||
// initialize EMA with 'emaDuration' seconds worth of history.
|
||||
// 1 second holds 'sendRate' worth of values.
|
||||
// multiplied by emaDuration gives n-seconds.
|
||||
@ -204,10 +206,9 @@ internal void DestroyOwnedObjects()
|
||||
{
|
||||
if (netIdentity != null)
|
||||
{
|
||||
// unspawn scene objects, destroy instantiated objects.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3538
|
||||
// disown scene objects, destroy instantiated objects.
|
||||
if (netIdentity.sceneId != 0)
|
||||
NetworkServer.UnSpawn(netIdentity.gameObject);
|
||||
NetworkServer.RemovePlayerForConnection(this, RemovePlayerOptions.KeepActive);
|
||||
else
|
||||
NetworkServer.Destroy(netIdentity.gameObject);
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ public struct NetworkIdentitySerialization
|
||||
public int tick;
|
||||
public NetworkWriter ownerWriter;
|
||||
public NetworkWriter observersWriter;
|
||||
|
||||
public void ResetWriters()
|
||||
{
|
||||
ownerWriter.Position = 0;
|
||||
observersWriter.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>NetworkIdentity identifies objects across the network.</summary>
|
||||
@ -201,15 +207,6 @@ internal set
|
||||
[FormerlySerializedAs("visible")]
|
||||
public Visibility visibility = Visibility.Default;
|
||||
|
||||
// Deprecated 2024-01-21
|
||||
[HideInInspector]
|
||||
[Obsolete("Deprecated - Use .visibility instead. This will be removed soon.")]
|
||||
public Visibility visible
|
||||
{
|
||||
get => visibility;
|
||||
set => visibility = value;
|
||||
}
|
||||
|
||||
// broadcasting serializes all entities around a player for each player.
|
||||
// we don't want to serialize one entity twice in the same tick.
|
||||
// so we cache the last serialization and remember the timestamp so we
|
||||
@ -676,6 +673,11 @@ void OnDestroy()
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3324
|
||||
NetworkClient.spawned.Remove(netId);
|
||||
}
|
||||
|
||||
// workaround for cyclid NI<->NB reference causing memory leaks
|
||||
// after Destroy. [Credits: BigBoxVR/R.S.]
|
||||
// TODO report this to Unity!
|
||||
this.NetworkBehaviours = null;
|
||||
}
|
||||
|
||||
internal void OnStartServer()
|
||||
@ -844,9 +846,9 @@ internal void OnStopLocalPlayer()
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
{
|
||||
NetworkBehaviour component = components[i];
|
||||
ulong nthBit = (1u << i);
|
||||
|
||||
bool dirty = component.IsDirty();
|
||||
ulong nthBit = (1u << i);
|
||||
|
||||
// owner needs to be considered for both SyncModes, because
|
||||
// Observers mode always includes the Owner.
|
||||
@ -857,14 +859,17 @@ internal void OnStopLocalPlayer()
|
||||
if (initialState || (component.syncDirection == SyncDirection.ServerToClient && dirty))
|
||||
ownerMask |= nthBit;
|
||||
|
||||
// observers need to be considered only in Observers mode
|
||||
//
|
||||
// for initial, it should always sync to observers.
|
||||
// for delta, only if dirty.
|
||||
// SyncDirection is irrelevant, as both are broadcast to
|
||||
// observers which aren't the owner.
|
||||
if (component.syncMode == SyncMode.Observers && (initialState || dirty))
|
||||
observerMask |= nthBit;
|
||||
// observers need to be considered only in Observers mode,
|
||||
// otherwise they receive no sync data of this component ever.
|
||||
if (component.syncMode == SyncMode.Observers)
|
||||
{
|
||||
// for initial, it should always sync to observers.
|
||||
// for delta, only if dirty.
|
||||
// SyncDirection is irrelevant, as both are broadcast to
|
||||
// observers which aren't the owner.
|
||||
if (initialState || dirty)
|
||||
observerMask |= nthBit;
|
||||
}
|
||||
}
|
||||
|
||||
return (ownerMask, observerMask);
|
||||
@ -888,11 +893,13 @@ ulong ClientDirtyMask()
|
||||
|
||||
// on client, only consider owned components with SyncDirection to server
|
||||
NetworkBehaviour component = components[i];
|
||||
ulong nthBit = (1u << i);
|
||||
|
||||
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||
{
|
||||
// set the n-th bit if dirty
|
||||
// shifting from small to large numbers is varint-efficient.
|
||||
if (component.IsDirty()) mask |= (1u << i);
|
||||
if (component.IsDirty()) mask |= nthBit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -910,8 +917,6 @@ internal static bool IsDirty(ulong mask, int index)
|
||||
|
||||
// serialize components into writer on the server.
|
||||
// check ownerWritten/observersWritten to know if anything was written
|
||||
// We pass dirtyComponentsMask into this function so that we can check
|
||||
// if any Components are dirty before creating writers
|
||||
internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
|
||||
{
|
||||
// ensure NetworkBehaviours are valid before usage
|
||||
@ -1126,8 +1131,7 @@ internal NetworkIdentitySerialization GetServerSerializationAtTick(int tick)
|
||||
)
|
||||
{
|
||||
// reset
|
||||
lastSerialization.ownerWriter.Position = 0;
|
||||
lastSerialization.observersWriter.Position = 0;
|
||||
lastSerialization.ResetWriters();
|
||||
|
||||
// serialize
|
||||
SerializeServer(false,
|
||||
|
@ -42,22 +42,6 @@ public class NetworkManager : MonoBehaviour
|
||||
[FormerlySerializedAs("serverTickRate")]
|
||||
public int sendRate = 60;
|
||||
|
||||
// Deprecated 2023-11-25
|
||||
// Using SerializeField and HideInInspector to self-correct for being
|
||||
// replaced by headlessStartMode. This can be removed in the future.
|
||||
// See OnValidate() for how we handle this.
|
||||
[Obsolete("Deprecated - Use headlessStartMode instead.")]
|
||||
[FormerlySerializedAs("autoStartServerBuild"), SerializeField, HideInInspector]
|
||||
public bool autoStartServerBuild = true;
|
||||
|
||||
// Deprecated 2023-11-25
|
||||
// Using SerializeField and HideInInspector to self-correct for being
|
||||
// replaced by headlessStartMode. This can be removed in the future.
|
||||
// See OnValidate() for how we handle this.
|
||||
[Obsolete("Deprecated - Use headlessStartMode instead.")]
|
||||
[FormerlySerializedAs("autoConnectClientBuild"), SerializeField, HideInInspector]
|
||||
public bool autoConnectClientBuild;
|
||||
|
||||
// client send rate follows server send rate to avoid errors for now
|
||||
/// <summary>Client Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.</summary>
|
||||
// [Tooltip("Client broadcasts 'sendRate' times per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
|
||||
@ -76,6 +60,9 @@ public class NetworkManager : MonoBehaviour
|
||||
[Tooltip("Scene that Mirror will switch to when the server is started. Clients will recieve a Scene Message to load the server's current scene when they connect.")]
|
||||
public string onlineScene = "";
|
||||
|
||||
[Range(0, 60), Tooltip("Optional delay that can be used after disconnecting to show a 'Connection lost...' message or similar before loading the offline scene, which may take a long time in big projects.")]
|
||||
public float offlineSceneLoadDelay = 0;
|
||||
|
||||
// transport layer
|
||||
[Header("Network Info")]
|
||||
[Tooltip("Transport component attached to this object that server and client will use to connect")]
|
||||
@ -178,24 +165,6 @@ public class NetworkManager : MonoBehaviour
|
||||
// virtual so that inheriting classes' OnValidate() can call base.OnValidate() too
|
||||
public virtual void OnValidate()
|
||||
{
|
||||
#pragma warning disable 618
|
||||
// autoStartServerBuild and autoConnectClientBuild are now obsolete, but to avoid
|
||||
// a breaking change we'll set headlessStartMode to what the user had set before.
|
||||
//
|
||||
// headlessStartMode defaults to DoNothing, so if the user had neither of these
|
||||
// set, then it will remain as DoNothing, and if they set headlessStartMode to
|
||||
// any selection in the inspector it won't get changed back.
|
||||
if (autoStartServerBuild)
|
||||
headlessStartMode = HeadlessStartOptions.AutoStartServer;
|
||||
else if (autoConnectClientBuild)
|
||||
headlessStartMode = HeadlessStartOptions.AutoStartClient;
|
||||
|
||||
// Setting both to false here prevents this code from fighting with user
|
||||
// selection in the inspector, and they're both SerialisedField's.
|
||||
autoStartServerBuild = false;
|
||||
autoConnectClientBuild = false;
|
||||
#pragma warning restore 618
|
||||
|
||||
// always >= 0
|
||||
maxConnections = Mathf.Max(maxConnections, 0);
|
||||
|
||||
@ -397,11 +366,6 @@ void SetupClient()
|
||||
{
|
||||
InitializeSingleton();
|
||||
|
||||
#pragma warning disable 618
|
||||
// Remove when OnConnectionQualityChanged is removed.
|
||||
NetworkClient.onConnectionQualityChanged += OnConnectionQualityChanged;
|
||||
#pragma warning restore 618
|
||||
|
||||
// apply settings before initializing anything
|
||||
NetworkClient.exceptionsDisconnect = exceptionsDisconnect;
|
||||
// NetworkClient.sendRate = clientSendRate;
|
||||
@ -668,16 +632,6 @@ public void StopClient()
|
||||
// NetworkClient.OnTransportDisconnect
|
||||
// NetworkManager.OnClientDisconnect
|
||||
NetworkClient.Disconnect();
|
||||
|
||||
#pragma warning disable 618
|
||||
// Remove when OnConnectionQualityChanged is removed.
|
||||
NetworkClient.onConnectionQualityChanged -= OnConnectionQualityChanged;
|
||||
#pragma warning restore 618
|
||||
|
||||
// UNET invoked OnDisconnected cleanup immediately.
|
||||
// let's keep it for now, in case any projects depend on it.
|
||||
// TODO simply remove this in the future.
|
||||
OnClientDisconnectInternal();
|
||||
}
|
||||
|
||||
// called when quitting the application by closing the window / pressing
|
||||
@ -1332,13 +1286,15 @@ void OnClientDisconnectInternal()
|
||||
// Check loadingSceneAsync to ensure we don't double-invoke the scene change.
|
||||
// Check if NetworkServer.active because we can get here via Disconnect before server has started to change scenes.
|
||||
if (!string.IsNullOrWhiteSpace(offlineScene) && !Utils.IsSceneActive(offlineScene) && loadingSceneAsync == null && !NetworkServer.active)
|
||||
{
|
||||
ClientChangeScene(offlineScene, SceneOperation.Normal);
|
||||
}
|
||||
Invoke(nameof(ClientChangeOfflineScene), offlineSceneLoadDelay);
|
||||
|
||||
networkSceneName = "";
|
||||
}
|
||||
|
||||
// wrap ClientChangeScene call without parameters for use in Invoke.
|
||||
void ClientChangeOfflineScene() =>
|
||||
ClientChangeScene(offlineScene, SceneOperation.Normal);
|
||||
|
||||
void OnClientNotReadyMessageInternal(NotReadyMessage msg)
|
||||
{
|
||||
//Debug.Log("NetworkManager.OnClientNotReadyMessageInternal");
|
||||
@ -1354,9 +1310,7 @@ void OnClientSceneInternal(SceneMessage msg)
|
||||
|
||||
// This needs to run for host client too. NetworkServer.active is checked there
|
||||
if (NetworkClient.isConnected)
|
||||
{
|
||||
ClientChangeScene(msg.sceneName, msg.sceneOperation, msg.customHandling);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called on the server when a new client connects.</summary>
|
||||
@ -1432,24 +1386,6 @@ public virtual void OnClientConnect()
|
||||
/// <summary>Called on clients when disconnected from a server.</summary>
|
||||
public virtual void OnClientDisconnect() { }
|
||||
|
||||
// Deprecated 2023-12-05
|
||||
/// <summary>Deprecated: NetworkClient handles this now.</summary>
|
||||
[Obsolete("NetworkClient handles this now.")]
|
||||
public virtual void CalculateConnectionQuality()
|
||||
{
|
||||
// Moved to NetworkClient
|
||||
}
|
||||
|
||||
// Deprecated 2023-12-05
|
||||
/// <summary>Deprecated: NetworkClient handles this now.</summary>
|
||||
[Obsolete("This will be removed. Subscribe to NetworkClient.onConnectionQualityChanged in your own code")]
|
||||
public virtual void OnConnectionQualityChanged(ConnectionQuality previous, ConnectionQuality current)
|
||||
{
|
||||
// logging the change is very useful to track down user's lag reports.
|
||||
// we want to include as much detail as possible for debugging.
|
||||
//Debug.Log($"[Mirror] Connection Quality changed from {previous} to {current}:\n rtt={(NetworkTime.rtt * 1000):F1}ms\n rttVar={(NetworkTime.rttVariance * 1000):F1}ms\n bufferTime={(NetworkClient.bufferTime * 1000):F1}ms");
|
||||
}
|
||||
|
||||
/// <summary>Called on client when transport raises an exception.</summary>
|
||||
public virtual void OnClientError(TransportError error, string reason) { }
|
||||
|
||||
|
@ -45,6 +45,14 @@ public static class NetworkReaderExtensions
|
||||
public static ulong ReadULong(this NetworkReader reader) => reader.ReadBlittable<ulong>();
|
||||
public static ulong? ReadULongNullable(this NetworkReader reader) => reader.ReadBlittableNullable<ulong>();
|
||||
|
||||
// ReadInt/UInt/Long/ULong writes full bytes by default.
|
||||
// define additional "VarInt" versions that Weaver will automatically prefer.
|
||||
// 99% of the time [SyncVar] ints are small values, which makes this very much worth it.
|
||||
[WeaverPriority] public static int ReadVarInt(this NetworkReader reader) => (int)Compression.DecompressVarInt(reader);
|
||||
[WeaverPriority] public static uint ReadVarUInt(this NetworkReader reader) => (uint)Compression.DecompressVarUInt(reader);
|
||||
[WeaverPriority] public static long ReadVarLong(this NetworkReader reader) => Compression.DecompressVarInt(reader);
|
||||
[WeaverPriority] public static ulong ReadVarULong(this NetworkReader reader) => Compression.DecompressVarUInt(reader);
|
||||
|
||||
public static float ReadFloat(this NetworkReader reader) => reader.ReadBlittable<float>();
|
||||
public static float? ReadFloatNullable(this NetworkReader reader) => reader.ReadBlittableNullable<float>();
|
||||
|
||||
@ -97,9 +105,13 @@ public static byte[] ReadBytes(this NetworkReader reader, int count)
|
||||
/// <exception cref="T:OverflowException">if count is invalid</exception>
|
||||
public static byte[] ReadBytesAndSize(this NetworkReader reader)
|
||||
{
|
||||
// count = 0 means the array was null
|
||||
// otherwise count -1 is the length of the array
|
||||
uint count = reader.ReadUInt();
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
|
||||
// most sizes are small, read size as VarUInt!
|
||||
uint count = (uint)Compression.DecompressVarUInt(reader);
|
||||
// uint count = reader.ReadUInt();
|
||||
// Use checked() to force it to throw OverflowException if data is invalid
|
||||
return count == 0 ? null : reader.ReadBytes(checked((int)(count - 1u)));
|
||||
}
|
||||
@ -107,9 +119,13 @@ public static byte[] ReadBytesAndSize(this NetworkReader reader)
|
||||
/// <exception cref="T:OverflowException">if count is invalid</exception>
|
||||
public static ArraySegment<byte> ReadArraySegmentAndSize(this NetworkReader reader)
|
||||
{
|
||||
// count = 0 means the array was null
|
||||
// otherwise count - 1 is the length of the array
|
||||
uint count = reader.ReadUInt();
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
|
||||
// most sizes are small, read size as VarUInt!
|
||||
uint count = (uint)Compression.DecompressVarUInt(reader);
|
||||
// uint count = reader.ReadUInt();
|
||||
// Use checked() to force it to throw OverflowException if data is invalid
|
||||
return count == 0 ? default : reader.ReadBytesSegment(checked((int)(count - 1u)));
|
||||
}
|
||||
@ -264,10 +280,15 @@ public static GameObject ReadGameObject(this NetworkReader reader)
|
||||
// note that Weaver/Readers/GenerateReader() handles this manually.
|
||||
public static List<T> ReadList<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadInt();
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
|
||||
// 'null' is encoded as '-1'
|
||||
if (length < 0) return null;
|
||||
// most sizes are small, read size as VarUInt!
|
||||
uint length = (uint)Compression.DecompressVarUInt(reader);
|
||||
// uint length = reader.ReadUInt();
|
||||
if (length == 0) return null;
|
||||
length -= 1;
|
||||
|
||||
// prevent allocation attacks with a reasonable limit.
|
||||
// server shouldn't allocate too much on client devices.
|
||||
@ -278,7 +299,7 @@ public static List<T> ReadList<T>(this NetworkReader reader)
|
||||
throw new EndOfStreamException($"NetworkReader attempted to allocate a List<{typeof(T)}> {length} elements, which is larger than the allowed limit of {NetworkReader.AllocationLimit}.");
|
||||
}
|
||||
|
||||
List<T> result = new List<T>(length);
|
||||
List<T> result = new List<T>((checked((int)length)));
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result.Add(reader.Read<T>());
|
||||
@ -294,9 +315,16 @@ public static List<T> ReadList<T>(this NetworkReader reader)
|
||||
/*
|
||||
public static HashSet<T> ReadHashSet<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadInt();
|
||||
if (length < 0)
|
||||
return null;
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
|
||||
// most sizes are small, read size as VarUInt!
|
||||
uint length = (uint)Compression.DecompressVarUInt(reader);
|
||||
//uint length = reader.ReadUInt();
|
||||
if (length == 0) return null;
|
||||
length -= 1;
|
||||
|
||||
HashSet<T> result = new HashSet<T>();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
@ -308,10 +336,15 @@ public static HashSet<T> ReadHashSet<T>(this NetworkReader reader)
|
||||
|
||||
public static T[] ReadArray<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadInt();
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
|
||||
// 'null' is encoded as '-1'
|
||||
if (length < 0) return null;
|
||||
// most sizes are small, read size as VarUInt!
|
||||
uint length = (uint)Compression.DecompressVarUInt(reader);
|
||||
//uint length = reader.ReadUInt();
|
||||
if (length == 0) return null;
|
||||
length -= 1;
|
||||
|
||||
// prevent allocation attacks with a reasonable limit.
|
||||
// server shouldn't allocate too much on client devices.
|
||||
|
@ -1,12 +1,9 @@
|
||||
// "NetworkReaderPooled" instead of "PooledNetworkReader" to group files, for
|
||||
// easier IDE workflow and more elegant code.
|
||||
using System;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>Pooled NetworkReader, automatically returned to pool when using 'using'</summary>
|
||||
// TODO make sealed again after removing obsolete NetworkReaderPooled!
|
||||
public class NetworkReaderPooled : NetworkReader, IDisposable
|
||||
public sealed class NetworkReaderPooled : NetworkReader, IDisposable
|
||||
{
|
||||
internal NetworkReaderPooled(byte[] bytes) : base(bytes) {}
|
||||
internal NetworkReaderPooled(ArraySegment<byte> segment) : base(segment) {}
|
||||
|
@ -6,6 +6,18 @@
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public enum ReplacePlayerOptions
|
||||
{
|
||||
/// <summary>Player Object remains active on server and clients. Ownership is not removed</summary>
|
||||
KeepAuthority,
|
||||
/// <summary>Player Object remains active on server and clients. Only ownership is removed</summary>
|
||||
KeepActive,
|
||||
/// <summary>Player Object is unspawned on clients but remains on server</summary>
|
||||
Unspawn,
|
||||
/// <summary>Player Object is destroyed on server and clients</summary>
|
||||
Destroy
|
||||
}
|
||||
|
||||
public enum RemovePlayerOptions
|
||||
{
|
||||
/// <summary>Player Object remains active on server and clients. Only ownership is removed</summary>
|
||||
@ -191,7 +203,10 @@ static void Initialize()
|
||||
static void AddTransportHandlers()
|
||||
{
|
||||
// += so that other systems can also hook into it (i.e. statistics)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Transport.active.OnServerConnected += OnTransportConnected;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Transport.active.OnServerConnectedWithAddress += OnTransportConnectedWithAddress;
|
||||
Transport.active.OnServerDataReceived += OnTransportData;
|
||||
Transport.active.OnServerDisconnected += OnTransportDisconnected;
|
||||
Transport.active.OnServerError += OnTransportError;
|
||||
@ -263,7 +278,10 @@ public static void Shutdown()
|
||||
static void RemoveTransportHandlers()
|
||||
{
|
||||
// -= so that other systems can also hook into it (i.e. statistics)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Transport.active.OnServerConnected -= OnTransportConnected;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Transport.active.OnServerConnectedWithAddress -= OnTransportConnectedWithAddress;
|
||||
Transport.active.OnServerDataReceived -= OnTransportData;
|
||||
Transport.active.OnServerDisconnected -= OnTransportDisconnected;
|
||||
Transport.active.OnServerError -= OnTransportError;
|
||||
@ -294,7 +312,7 @@ internal static void RegisterMessageHandlers()
|
||||
RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
|
||||
RegisterHandler<NetworkPongMessage>(NetworkTime.OnServerPong, false);
|
||||
RegisterHandler<EntityStateMessage>(OnEntityStateMessage, true);
|
||||
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, true);
|
||||
RegisterHandler<TimeSnapshotMessage>(OnTimeSnapshotMessage, false); // unreliable may arrive before reliable authority went through
|
||||
}
|
||||
|
||||
// remote calls ////////////////////////////////////////////////////////
|
||||
@ -636,25 +654,39 @@ public static void SendToReadyObservers<T>(NetworkIdentity identity, T message,
|
||||
// transport events ////////////////////////////////////////////////////
|
||||
// called by transport
|
||||
static void OnTransportConnected(int connectionId)
|
||||
{
|
||||
// Debug.Log($"Server accepted client:{connectionId}");
|
||||
=> OnTransportConnectedWithAddress(connectionId, Transport.active.ServerGetClientAddress(connectionId));
|
||||
|
||||
static void OnTransportConnectedWithAddress(int connectionId, string clientAddress)
|
||||
{
|
||||
if (IsConnectionAllowed(connectionId))
|
||||
{
|
||||
// create a connection
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId, clientAddress);
|
||||
OnConnected(conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// kick the client immediately
|
||||
Transport.active.ServerDisconnect(connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsConnectionAllowed(int connectionId)
|
||||
{
|
||||
// connectionId needs to be != 0 because 0 is reserved for local player
|
||||
// note that some transports like kcp generate connectionId by
|
||||
// hashing which can be < 0 as well, so we need to allow < 0!
|
||||
if (connectionId == 0)
|
||||
{
|
||||
Debug.LogError($"Server.HandleConnect: invalid connectionId: {connectionId} . Needs to be != 0, because 0 is reserved for local player.");
|
||||
Transport.active.ServerDisconnect(connectionId);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// connectionId not in use yet?
|
||||
if (connections.ContainsKey(connectionId))
|
||||
{
|
||||
Transport.active.ServerDisconnect(connectionId);
|
||||
// Debug.Log($"Server connectionId {connectionId} already in use...kicked client");
|
||||
return;
|
||||
Debug.LogError($"Server connectionId {connectionId} already in use...client will be kicked");
|
||||
return false;
|
||||
}
|
||||
|
||||
// are more connections allowed? if not, kick
|
||||
@ -662,18 +694,13 @@ static void OnTransportConnected(int connectionId)
|
||||
// less code and third party transport might not do that anyway)
|
||||
// (this way we could also send a custom 'tooFull' message later,
|
||||
// Transport can't do that)
|
||||
if (connections.Count < maxConnections)
|
||||
if (connections.Count >= maxConnections)
|
||||
{
|
||||
// add connection
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId);
|
||||
OnConnected(conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// kick
|
||||
Transport.active.ServerDisconnect(connectionId);
|
||||
// Debug.Log($"Server full, kicked client {connectionId}");
|
||||
Debug.LogError($"Server full, client {connectionId} will be kicked");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void OnConnected(NetworkConnectionToClient conn)
|
||||
@ -1085,10 +1112,36 @@ public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameOb
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Replaces connection's player object. The old object is not destroyed.</summary>
|
||||
// This does NOT change the ready state of the connection, so it can
|
||||
// safely be used while changing scenes.
|
||||
// Deprecated 2024-008-09
|
||||
[Obsolete("Use ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, ReplacePlayerOptions replacePlayerOptions) instead")]
|
||||
public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, bool keepAuthority = false)
|
||||
{
|
||||
if (GetNetworkIdentity(player, out NetworkIdentity identity))
|
||||
identity.assetId = assetId;
|
||||
|
||||
return ReplacePlayerForConnection(conn, player, keepAuthority ? ReplacePlayerOptions.KeepAuthority : ReplacePlayerOptions.KeepActive);
|
||||
}
|
||||
|
||||
// Deprecated 2024-008-09
|
||||
[Obsolete("Use ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, ReplacePlayerOptions replacePlayerOptions) instead")]
|
||||
public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, bool keepAuthority = false)
|
||||
{
|
||||
return ReplacePlayerForConnection(conn, player, keepAuthority ? ReplacePlayerOptions.KeepAuthority : ReplacePlayerOptions.KeepActive);
|
||||
}
|
||||
|
||||
/// <summary>Replaces connection's player object. The old object is not destroyed.</summary>
|
||||
// This does NOT change the ready state of the connection, so it can safely be used while changing scenes.
|
||||
public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, ReplacePlayerOptions replacePlayerOptions)
|
||||
{
|
||||
if (GetNetworkIdentity(player, out NetworkIdentity identity))
|
||||
identity.assetId = assetId;
|
||||
|
||||
return ReplacePlayerForConnection(conn, player, replacePlayerOptions);
|
||||
}
|
||||
|
||||
/// <summary>Replaces connection's player object. The old object is not destroyed.</summary>
|
||||
// This does NOT change the ready state of the connection, so it can safely be used while changing scenes.
|
||||
public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, ReplacePlayerOptions replacePlayerOptions)
|
||||
{
|
||||
if (!player.TryGetComponent(out NetworkIdentity identity))
|
||||
{
|
||||
@ -1130,33 +1183,28 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga
|
||||
|
||||
Respawn(identity);
|
||||
|
||||
if (keepAuthority)
|
||||
switch (replacePlayerOptions)
|
||||
{
|
||||
// This needs to be sent to clear isLocalPlayer on
|
||||
// client while keeping hasAuthority true
|
||||
SendChangeOwnerMessage(previousPlayer, conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This clears both isLocalPlayer and hasAuthority on client
|
||||
previousPlayer.RemoveClientAuthority();
|
||||
case ReplacePlayerOptions.KeepAuthority:
|
||||
// This needs to be sent to clear isLocalPlayer on
|
||||
// client while keeping hasAuthority true
|
||||
SendChangeOwnerMessage(previousPlayer, conn);
|
||||
break;
|
||||
case ReplacePlayerOptions.KeepActive:
|
||||
// This clears both isLocalPlayer and hasAuthority on client
|
||||
previousPlayer.RemoveClientAuthority();
|
||||
break;
|
||||
case ReplacePlayerOptions.Unspawn:
|
||||
UnSpawn(previousPlayer.gameObject);
|
||||
break;
|
||||
case ReplacePlayerOptions.Destroy:
|
||||
Destroy(previousPlayer.gameObject);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Replaces connection's player object. The old object is not destroyed.</summary>
|
||||
// This does NOT change the ready state of the connection, so it can
|
||||
// safely be used while changing scenes.
|
||||
public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, uint assetId, bool keepAuthority = false)
|
||||
{
|
||||
if (GetNetworkIdentity(player, out NetworkIdentity identity))
|
||||
{
|
||||
identity.assetId = assetId;
|
||||
}
|
||||
return ReplacePlayerForConnection(conn, player, keepAuthority);
|
||||
}
|
||||
|
||||
/// <summary>Removes the player object from the connection</summary>
|
||||
// destroyServerObject: Indicates whether the server object should be destroyed
|
||||
// Deprecated 2024-06-06
|
||||
@ -1613,14 +1661,12 @@ static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
|
||||
RebuildObservers(identity, true);
|
||||
}
|
||||
|
||||
/// <summary>This takes an object that has been spawned and un-spawns it.</summary>
|
||||
// The object will be removed from clients that it was spawned on, or
|
||||
// the custom spawn handler function on the client will be called for
|
||||
// the object.
|
||||
// Unlike when calling NetworkServer.Destroy(), on the server the object
|
||||
// will NOT be destroyed. This allows the server to re-use the object,
|
||||
// even spawn it again later.
|
||||
public static void UnSpawn(GameObject obj)
|
||||
// internal Unspawn function which has the 'resetState' parameter.
|
||||
// resetState calls .ResetState() on the object after unspawning.
|
||||
// this is necessary for scene objects, but not for prefabs since we
|
||||
// don't want to reset their isServer flags etc.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3832
|
||||
static void UnSpawnInternal(GameObject obj, bool resetState)
|
||||
{
|
||||
// Debug.Log($"DestroyObject instance:{identity.netId}");
|
||||
|
||||
@ -1694,10 +1740,22 @@ public static void UnSpawn(GameObject obj)
|
||||
identity.OnStopServer();
|
||||
|
||||
// finally reset the state and deactivate it
|
||||
identity.ResetState();
|
||||
identity.gameObject.SetActive(false);
|
||||
if (resetState)
|
||||
{
|
||||
identity.ResetState();
|
||||
identity.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>This takes an object that has been spawned and un-spawns it.</summary>
|
||||
// The object will be removed from clients that it was spawned on, or
|
||||
// the custom spawn handler function on the client will be called for
|
||||
// the object.
|
||||
// Unlike when calling NetworkServer.Destroy(), on the server the object
|
||||
// will NOT be destroyed. This allows the server to re-use the object,
|
||||
// even spawn it again later.
|
||||
public static void UnSpawn(GameObject obj) => UnSpawnInternal(obj, resetState: true);
|
||||
|
||||
// destroy /////////////////////////////////////////////////////////////
|
||||
/// <summary>Destroys this object and corresponding objects on all clients.</summary>
|
||||
// In some cases it is useful to remove an object but not delete it on
|
||||
@ -1719,17 +1777,31 @@ public static void Destroy(GameObject obj)
|
||||
return;
|
||||
}
|
||||
|
||||
// first, we unspawn it on clients and server
|
||||
UnSpawn(obj);
|
||||
// get the NetworkIdentity component first
|
||||
if (!GetNetworkIdentity(obj, out NetworkIdentity identity))
|
||||
{
|
||||
Debug.LogWarning($"NetworkServer.Destroy() called on {obj.name} which doesn't have a NetworkIdentity component.");
|
||||
return;
|
||||
}
|
||||
|
||||
// additionally, if it's a prefab then we destroy it completely.
|
||||
// is this a scene object?
|
||||
// then we simply unspawn & reset it so it can still be spawned again.
|
||||
// we never destroy scene objects on server or on client, since once
|
||||
// they are gone, they are gone forever and can't be instantiate again.
|
||||
// for example, server may Destroy() a scene object and once a match
|
||||
// restarts, the scene objects would be gone from the new match.
|
||||
if (GetNetworkIdentity(obj, out NetworkIdentity identity) &&
|
||||
identity.sceneId == 0)
|
||||
if (identity.sceneId != 0)
|
||||
{
|
||||
UnSpawnInternal(obj, resetState: true);
|
||||
}
|
||||
// is this a prefab?
|
||||
// then we destroy it completely.
|
||||
else
|
||||
{
|
||||
// unspawn without calling ResetState.
|
||||
// otherwise isServer/isClient flags might be reset in OnDestroy.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3832
|
||||
UnSpawnInternal(obj, resetState: false);
|
||||
identity.destroyCalled = true;
|
||||
|
||||
// Destroy if application is running
|
||||
@ -1958,30 +2030,6 @@ static void Broadcast()
|
||||
// update connection to flush out batched messages
|
||||
connection.Update();
|
||||
}
|
||||
|
||||
// TODO this is way too slow because we iterate ALL spawned :/
|
||||
// TODO this is way too complicated :/
|
||||
// to understand what this tries to prevent, consider this example:
|
||||
// monster has health=100
|
||||
// we change health=200, dirty bit is set
|
||||
// player comes in range, gets full serialization spawn packet.
|
||||
// next Broadcast(), player gets the health=200 change because dirty bit was set.
|
||||
//
|
||||
// this code clears all dirty bits if no players are around to prevent it.
|
||||
// BUT there are two issues:
|
||||
// 1. what if a playerB was around the whole time?
|
||||
// 2. why don't we handle broadcast and spawn packets both HERE?
|
||||
// handling spawn separately is why we need this complex magic
|
||||
//
|
||||
// see test: DirtyBitsAreClearedForSpawnedWithoutObservers()
|
||||
// see test: SyncObjectChanges_DontGrowWithoutObservers()
|
||||
//
|
||||
// PAUL: we also do this to avoid ever growing SyncList .changes
|
||||
//ClearSpawnedDirtyBits();
|
||||
//
|
||||
// this was moved to NetworkIdentity.AddObserver!
|
||||
// same result, but no more O(N) loop in here!
|
||||
// TODO remove this comment after moving spawning into Broadcast()!
|
||||
}
|
||||
|
||||
// update //////////////////////////////////////////////////////////////
|
||||
@ -2029,7 +2077,8 @@ internal static void NetworkLateUpdate()
|
||||
// NetworkTransform, so they can sync on same interval as time
|
||||
// snapshots _but_ not every single tick.
|
||||
// Unity 2019 doesn't have Time.timeAsDouble yet
|
||||
if (!Application.isPlaying || AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime))
|
||||
bool sendIntervalElapsed = AccurateInterval.Elapsed(NetworkTime.localTime, sendInterval, ref lastSendTime);
|
||||
if (!Application.isPlaying || sendIntervalElapsed)
|
||||
Broadcast();
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,14 @@ public static class NetworkWriterExtensions
|
||||
public static void WriteULong(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value);
|
||||
public static void WriteULongNullable(this NetworkWriter writer, ulong? value) => writer.WriteBlittableNullable(value);
|
||||
|
||||
// WriteInt/UInt/Long/ULong writes full bytes by default.
|
||||
// define additional "VarInt" versions that Weaver will automatically prefer.
|
||||
// 99% of the time [SyncVar] ints are small values, which makes this very much worth it.
|
||||
[WeaverPriority] public static void WriteVarInt(this NetworkWriter writer, int value) => Compression.CompressVarInt(writer, value);
|
||||
[WeaverPriority] public static void WriteVarUInt(this NetworkWriter writer, uint value) => Compression.CompressVarUInt(writer, value);
|
||||
[WeaverPriority] public static void WriteVarLong(this NetworkWriter writer, long value) => Compression.CompressVarInt(writer, value);
|
||||
[WeaverPriority] public static void WriteVarULong(this NetworkWriter writer, ulong value) => Compression.CompressVarUInt(writer, value);
|
||||
|
||||
public static void WriteFloat(this NetworkWriter writer, float value) => writer.WriteBlittable(value);
|
||||
public static void WriteFloatNullable(this NetworkWriter writer, float? value) => writer.WriteBlittableNullable(value);
|
||||
|
||||
@ -51,10 +59,9 @@ public static class NetworkWriterExtensions
|
||||
|
||||
public static void WriteString(this NetworkWriter writer, string value)
|
||||
{
|
||||
// write 0 for null support, increment real size by 1
|
||||
// (note: original HLAPI would write "" for null strings, but if a
|
||||
// string is null on the server then it should also be null
|
||||
// on the client)
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteUShort(0);
|
||||
@ -94,15 +101,20 @@ public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
|
||||
// (like an inventory with different items etc.)
|
||||
public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
|
||||
{
|
||||
// null is supported because [SyncVar]s might be structs with null byte[] arrays
|
||||
// write 0 for null array, increment normal size by 1 to save bandwidth
|
||||
// (using size=-1 for null would limit max size to 32kb instead of 64kb)
|
||||
// null is supported because [SyncVar]s might be structs with null byte[] arrays.
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
if (buffer == null)
|
||||
{
|
||||
writer.WriteUInt(0u);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, 0u);
|
||||
// writer.WriteUInt(0u);
|
||||
return;
|
||||
}
|
||||
writer.WriteUInt(checked((uint)count) + 1u);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, checked((uint)count) + 1u);
|
||||
// writer.WriteUInt(checked((uint)count) + 1u);
|
||||
writer.WriteBytes(buffer, offset, count);
|
||||
}
|
||||
|
||||
@ -115,9 +127,19 @@ public static void WriteArraySegmentAndSize(this NetworkWriter writer, ArraySegm
|
||||
// writes ArraySegment of any type, and size header
|
||||
public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
|
||||
{
|
||||
int length = segment.Count;
|
||||
writer.WriteInt(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
//
|
||||
// ArraySegment technically can't be null, but users may call:
|
||||
// - WriteArraySegment
|
||||
// - ReadArray
|
||||
// in which case ReadArray needs null support. both need to be compatible.
|
||||
int count = segment.Count;
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, checked((uint)count) + 1u);
|
||||
// writer.WriteUInt(checked((uint)count) + 1u);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
writer.Write(segment.Array[segment.Offset + i]);
|
||||
}
|
||||
@ -315,10 +337,14 @@ public static void WriteGameObject(this NetworkWriter writer, GameObject value)
|
||||
// note that Weaver/Writers/GenerateWriter() handles this manually.
|
||||
public static void WriteList<T>(this NetworkWriter writer, List<T> list)
|
||||
{
|
||||
// 'null' is encoded as '-1'
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
if (list is null)
|
||||
{
|
||||
writer.WriteInt(-1);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, 0u);
|
||||
// writer.WriteUInt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -326,7 +352,9 @@ public static void WriteList<T>(this NetworkWriter writer, List<T> list)
|
||||
if (list.Count > NetworkReader.AllocationLimit)
|
||||
throw new IndexOutOfRangeException($"NetworkWriter.WriteList - List<{typeof(T)}> too big: {list.Count} elements. Limit: {NetworkReader.AllocationLimit}");
|
||||
|
||||
writer.WriteInt(list.Count);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, checked((uint)list.Count) + 1u);
|
||||
// writer.WriteUInt(checked((uint)list.Count) + 1u);
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
writer.Write(list[i]);
|
||||
}
|
||||
@ -336,26 +364,38 @@ public static void WriteList<T>(this NetworkWriter writer, List<T> list)
|
||||
// fully serialize for NetworkMessages etc.
|
||||
// note that Weaver/Writers/GenerateWriter() handles this manually.
|
||||
// TODO writer not found. need to adjust weaver first. see tests.
|
||||
/*
|
||||
public static void WriteHashSet<T>(this NetworkWriter writer, HashSet<T> hashSet)
|
||||
{
|
||||
if (hashSet is null)
|
||||
{
|
||||
writer.WriteInt(-1);
|
||||
return;
|
||||
}
|
||||
writer.WriteInt(hashSet.Count);
|
||||
foreach (T item in hashSet)
|
||||
writer.Write(item);
|
||||
}
|
||||
*/
|
||||
// /*
|
||||
// public static void WriteHashSet<T>(this NetworkWriter writer, HashSet<T> hashSet)
|
||||
// {
|
||||
// // we offset count by '1' to easily support null without writing another byte.
|
||||
// // encoding null as '0' instead of '-1' also allows for better compression
|
||||
// // (ushort vs. short / varuint vs. varint) etc.
|
||||
// if (hashSet is null)
|
||||
// {
|
||||
// // most sizes are small, write size as VarUInt!
|
||||
// Compression.CompressVarUInt(writer, 0u);
|
||||
// //writer.WriteUInt(0);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // most sizes are small, write size as VarUInt!
|
||||
// Compression.CompressVarUInt(writer, checked((uint)hashSet.Count) + 1u);
|
||||
// //writer.WriteUInt(checked((uint)hashSet.Count) + 1u);
|
||||
// foreach (T item in hashSet)
|
||||
// writer.Write(item);
|
||||
// }
|
||||
// */
|
||||
|
||||
public static void WriteArray<T>(this NetworkWriter writer, T[] array)
|
||||
{
|
||||
// 'null' is encoded as '-1'
|
||||
// we offset count by '1' to easily support null without writing another byte.
|
||||
// encoding null as '0' instead of '-1' also allows for better compression
|
||||
// (ushort vs. short / varuint vs. varint) etc.
|
||||
if (array is null)
|
||||
{
|
||||
writer.WriteInt(-1);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, 0u);
|
||||
// writer.WriteUInt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -363,7 +403,9 @@ public static void WriteArray<T>(this NetworkWriter writer, T[] array)
|
||||
if (array.Length > NetworkReader.AllocationLimit)
|
||||
throw new IndexOutOfRangeException($"NetworkWriter.WriteArray - Array<{typeof(T)}> too big: {array.Length} elements. Limit: {NetworkReader.AllocationLimit}");
|
||||
|
||||
writer.WriteInt(array.Length);
|
||||
// most sizes are small, write size as VarUInt!
|
||||
Compression.CompressVarUInt(writer, checked((uint)array.Length) + 1u);
|
||||
// writer.WriteUInt(checked((uint)array.Length) + 1u);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
writer.Write(array[i]);
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
// "NetworkWriterPooled" instead of "PooledNetworkWriter" to group files, for
|
||||
// easier IDE workflow and more elegant code.
|
||||
using System;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>Pooled NetworkWriter, automatically returned to pool when using 'using'</summary>
|
||||
// TODO make sealed again after removing obsolete NetworkWriterPooled!
|
||||
public class NetworkWriterPooled : NetworkWriter, IDisposable
|
||||
public sealed class NetworkWriterPooled : NetworkWriter, IDisposable
|
||||
{
|
||||
public void Dispose() => NetworkWriterPool.Return(this);
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ public static void InsertAndAdjust<T>(
|
||||
// make sure to only call this is we have > 0 snapshots.
|
||||
public static void Sample<T>(
|
||||
SortedList<double, T> buffer, // snapshot buffer
|
||||
double localTimeline, // local interpolation time based on server time
|
||||
double localTimeline, // local interpolation time based on server time. this is basically remoteTime-bufferTime.
|
||||
out int from, // the snapshot <= time
|
||||
out int to, // the snapshot >= time
|
||||
out double t) // interpolation factor
|
||||
@ -336,7 +336,7 @@ public static void StepTime(
|
||||
// besides, passing "Func Interpolate" would allocate anyway.
|
||||
public static void StepInterpolation<T>(
|
||||
SortedList<double, T> buffer, // snapshot buffer
|
||||
double localTimeline, // local interpolation time based on server time
|
||||
double localTimeline, // local interpolation time based on server time. this is basically remoteTime-bufferTime.
|
||||
out T fromSnapshot, // we interpolate 'from' this snapshot
|
||||
out T toSnapshot, // 'to' this snapshot
|
||||
out double t) // at ratio 't' [0,1]
|
||||
|
@ -26,10 +26,6 @@ public class SyncIDictionary<TKey, TValue> : SyncObject, IDictionary<TKey, TValu
|
||||
/// <summary>This is called before the data is cleared</summary>
|
||||
public Action OnClear;
|
||||
|
||||
// Deprecated 2024-03-22
|
||||
[Obsolete("Use individual Actions, which pass OLD values where appropriate, instead.")]
|
||||
public Action<Operation, TKey, TValue> Callback;
|
||||
|
||||
protected readonly IDictionary<TKey, TValue> objects;
|
||||
|
||||
public SyncIDictionary(IDictionary<TKey, TValue> objects)
|
||||
@ -343,10 +339,6 @@ void AddOperation(Operation op, TKey key, TValue item, TValue oldItem, bool chec
|
||||
OnChange?.Invoke(op, default, default);
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Callback?.Invoke(op, key, item);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => objects.GetEnumerator();
|
||||
|
@ -38,10 +38,6 @@ public enum Operation : byte
|
||||
/// <summary>This is called before the list is cleared so the list can be iterated</summary>
|
||||
public Action OnClear;
|
||||
|
||||
// Deprecated 2024-03-23
|
||||
[Obsolete("Use individual Actions, which pass OLD values where appropriate, instead.")]
|
||||
public Action<Operation, int, T, T> Callback;
|
||||
|
||||
readonly IList<T> objects;
|
||||
readonly IEqualityComparer<T> comparer;
|
||||
|
||||
@ -133,10 +129,6 @@ void AddOperation(Operation op, int itemIndex, T oldItem, T newItem, bool checkA
|
||||
OnChange?.Invoke(op, itemIndex, default);
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Callback?.Invoke(op, itemIndex, oldItem, newItem);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public override void OnSerializeAll(NetworkWriter writer)
|
||||
|
@ -23,10 +23,6 @@ public class SyncSet<T> : SyncObject, ISet<T>
|
||||
/// <summary>This is called BEFORE the data is cleared</summary>
|
||||
public Action OnClear;
|
||||
|
||||
// Deprecated 2024-03-22
|
||||
[Obsolete("Use individual Actions, which pass OLD value where appropriate, instead.")]
|
||||
public Action<Operation, T> Callback;
|
||||
|
||||
protected readonly ISet<T> objects;
|
||||
|
||||
public int Count => objects.Count;
|
||||
@ -116,23 +112,14 @@ void AddOperation(Operation op, T oldItem, T newItem, bool checkAccess)
|
||||
case Operation.OP_ADD:
|
||||
OnAdd?.Invoke(newItem);
|
||||
OnChange?.Invoke(op, newItem);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Callback?.Invoke(op, newItem);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
break;
|
||||
case Operation.OP_REMOVE:
|
||||
OnRemove?.Invoke(oldItem);
|
||||
OnChange?.Invoke(op, oldItem);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Callback?.Invoke(op, oldItem);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
break;
|
||||
case Operation.OP_CLEAR:
|
||||
OnClear?.Invoke();
|
||||
OnChange?.Invoke(op, default);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Callback?.Invoke(op, default);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@
|
||||
namespace Mirror
|
||||
{
|
||||
/// <summary>Pooled (not threadsafe) NetworkWriter used from Concurrent pool (thread safe). Automatically returned to concurrent pool when using 'using'</summary>
|
||||
// TODO make sealed again after removing obsolete NetworkWriterPooled!
|
||||
public class ConcurrentNetworkWriterPooled : NetworkWriter, IDisposable
|
||||
public sealed class ConcurrentNetworkWriterPooled : NetworkWriter, IDisposable
|
||||
{
|
||||
public void Dispose() => ConcurrentNetworkWriterPool.Return(this);
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ public class WorkerThread
|
||||
// callbacks need to be set after constructor.
|
||||
// inheriting classes can't pass their member funcs to base ctor.
|
||||
// don't set them while the thread is running!
|
||||
// -> Tick() returns a bool so it can easily stop the thread
|
||||
// without needing to throw InterruptExceptions or similar.
|
||||
public Action Init;
|
||||
public Action Tick;
|
||||
public Func<bool> Tick;
|
||||
public Action Cleanup;
|
||||
|
||||
public WorkerThread(string identifier)
|
||||
@ -104,7 +106,7 @@ public bool StopBlocking(float timeout)
|
||||
// always define them, and make them call actions.
|
||||
// those can be set at any time.
|
||||
void OnInit() => Init?.Invoke();
|
||||
void OnTick() => Tick?.Invoke();
|
||||
bool OnTick() => Tick?.Invoke() ?? false;
|
||||
void OnCleanup() => Cleanup?.Invoke();
|
||||
|
||||
// guarded wrapper for thread code.
|
||||
@ -128,7 +130,9 @@ public void Guard(string identifier)
|
||||
// run thread func while active
|
||||
while (active)
|
||||
{
|
||||
OnTick();
|
||||
// Tick() returns a bool so it can easily stop the thread
|
||||
// without needing to throw InterruptExceptions or similar.
|
||||
if (!OnTick()) break;
|
||||
}
|
||||
}
|
||||
// Thread.Interrupt() will gracefully raise a InterruptedException.
|
||||
|
@ -71,6 +71,24 @@ public static bool ScaleToLong(Vector3 value, float precision, out long x, out l
|
||||
return result;
|
||||
}
|
||||
|
||||
// returns
|
||||
// 'true' if scaling was possible within 'long' bounds.
|
||||
// 'false' if clamping was necessary.
|
||||
// never throws. checking result is optional.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ScaleToLong(Quaternion value, float precision, out long x, out long y, out long z, out long w)
|
||||
{
|
||||
// attempt to convert every component.
|
||||
// do not return early if one conversion returned 'false'.
|
||||
// the return value is optional. always attempt to convert all.
|
||||
bool result = true;
|
||||
result &= ScaleToLong(value.x, precision, out x);
|
||||
result &= ScaleToLong(value.y, precision, out y);
|
||||
result &= ScaleToLong(value.z, precision, out z);
|
||||
result &= ScaleToLong(value.w, precision, out w);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ScaleToLong(Vector3 value, float precision, out Vector3Long quantized)
|
||||
{
|
||||
@ -78,6 +96,13 @@ public static bool ScaleToLong(Vector3 value, float precision, out Vector3Long q
|
||||
return ScaleToLong(value, precision, out quantized.x, out quantized.y, out quantized.z);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ScaleToLong(Quaternion value, float precision, out Vector4Long quantized)
|
||||
{
|
||||
quantized = Vector4Long.zero;
|
||||
return ScaleToLong(value, precision, out quantized.x, out quantized.y, out quantized.z, out quantized.w);
|
||||
}
|
||||
|
||||
// multiple by precision.
|
||||
// for example, 0.1 cm precision converts '50' long to '5.0f' float.
|
||||
public static float ScaleToFloat(long value, float precision)
|
||||
@ -103,10 +128,25 @@ public static Vector3 ScaleToFloat(long x, long y, long z, float precision)
|
||||
return v;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion ScaleToFloat(long x, long y, long z, long w, float precision)
|
||||
{
|
||||
Quaternion v;
|
||||
v.x = ScaleToFloat(x, precision);
|
||||
v.y = ScaleToFloat(y, precision);
|
||||
v.z = ScaleToFloat(z, precision);
|
||||
v.w = ScaleToFloat(w, precision);
|
||||
return v;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector3 ScaleToFloat(Vector3Long value, float precision) =>
|
||||
ScaleToFloat(value.x, value.y, value.z, precision);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion ScaleToFloat(Vector4Long value, float precision) =>
|
||||
ScaleToFloat(value.x, value.y, value.z, value.w, precision);
|
||||
|
||||
// scale a float within min/max range to an ushort between min/max range
|
||||
// note: can also use this for byte range from byte.MinValue to byte.MaxValue
|
||||
public static ushort ScaleFloatToUShort(float value, float minValue, float maxValue, ushort minTarget, ushort maxTarget)
|
||||
|
@ -26,6 +26,16 @@ public static void Compress(NetworkWriter writer, Vector3Long last, Vector3Long
|
||||
Compress(writer, last.z, current.z);
|
||||
}
|
||||
|
||||
// delta (usually small), then zigzag varint to support +- changes
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Compress(NetworkWriter writer, Vector4Long last, Vector4Long current)
|
||||
{
|
||||
Compress(writer, last.x, current.x);
|
||||
Compress(writer, last.y, current.y);
|
||||
Compress(writer, last.z, current.z);
|
||||
Compress(writer, last.w, current.w);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector3Long Decompress(NetworkReader reader, Vector3Long last)
|
||||
{
|
||||
@ -34,5 +44,15 @@ public static Vector3Long Decompress(NetworkReader reader, Vector3Long last)
|
||||
long z = Decompress(reader, last.z);
|
||||
return new Vector3Long(x, y, z);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long Decompress(NetworkReader reader, Vector4Long last)
|
||||
{
|
||||
long x = Decompress(reader, last.x);
|
||||
long y = Decompress(reader, last.y);
|
||||
long z = Decompress(reader, last.z);
|
||||
long w = Decompress(reader, last.w);
|
||||
return new Vector4Long(x, y, z, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ public static ushort GetStableHashCode16(this string text)
|
||||
|
||||
// previously in DotnetCompatibility.cs
|
||||
// leftover from the UNET days. supposedly for windows store?
|
||||
internal static string GetMethodName(this Delegate func)
|
||||
public static string GetMethodName(this Delegate func)
|
||||
{
|
||||
#if NETFX_CORE
|
||||
return func.GetMethodInfo().Name;
|
||||
@ -101,13 +101,19 @@ public static void Clear<T>(this ConcurrentQueue<T> source)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_2022_0_OR_NEWER
|
||||
#if !UNITY_2021_3_OR_NEWER
|
||||
// Some patch versions of Unity 2021.3 and earlier don't have transform.GetPositionAndRotation which we use for performance in some places
|
||||
public static void GetPositionAndRotation(this Transform transform, out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
position = transform.position;
|
||||
rotation = transform.rotation;
|
||||
}
|
||||
|
||||
public static void SetPositionAndRotation(this Transform transform, Vector3 position, Quaternion rotation)
|
||||
{
|
||||
transform.position = position;
|
||||
transform.rotation = rotation;
|
||||
}
|
||||
#endif
|
||||
|
||||
// IPEndPoint address only to pretty string.
|
||||
|
126
Assets/Mirror/Core/Tools/Vector4Long.cs
Normal file
126
Assets/Mirror/Core/Tools/Vector4Long.cs
Normal file
@ -0,0 +1,126 @@
|
||||
#pragma warning disable CS0659 // 'Vector4Long' overrides Object.Equals(object o) but does not override Object.GetHashCode()
|
||||
#pragma warning disable CS0661 // 'Vector4Long' defines operator == or operator != but does not override Object.GetHashCode()
|
||||
|
||||
// Vector4Long by mischa (based on game engine project)
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
public struct Vector4Long
|
||||
{
|
||||
public long x;
|
||||
public long y;
|
||||
public long z;
|
||||
public long w;
|
||||
|
||||
public static readonly Vector4Long zero = new Vector4Long(0, 0, 0, 0);
|
||||
public static readonly Vector4Long one = new Vector4Long(1, 1, 1, 1);
|
||||
|
||||
// constructor /////////////////////////////////////////////////////////
|
||||
public Vector4Long(long x, long y, long z, long w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
// operators ///////////////////////////////////////////////////////////
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long operator +(Vector4Long a, Vector4Long b) =>
|
||||
new Vector4Long(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long operator -(Vector4Long a, Vector4Long b) =>
|
||||
new Vector4Long(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long operator -(Vector4Long v) =>
|
||||
new Vector4Long(-v.x, -v.y, -v.z, -v.w);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long operator *(Vector4Long a, long n) =>
|
||||
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4Long operator *(long n, Vector4Long a) =>
|
||||
new Vector4Long(a.x * n, a.y * n, a.z * n, a.w * n);
|
||||
|
||||
// == returns true if approximately equal (with epsilon).
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(Vector4Long a, Vector4Long b) =>
|
||||
a.x == b.x &&
|
||||
a.y == b.y &&
|
||||
a.z == b.z &&
|
||||
a.w == b.w;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(Vector4Long a, Vector4Long b) => !(a == b);
|
||||
|
||||
// NO IMPLICIT System.Numerics.Vector4Long conversion because double<->float
|
||||
// would silently lose precision in large worlds.
|
||||
|
||||
// [i] component index. useful for iterating all components etc.
|
||||
public long this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return x;
|
||||
case 1: return y;
|
||||
case 2: return z;
|
||||
case 3: return w;
|
||||
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
x = value;
|
||||
break;
|
||||
case 1:
|
||||
y = value;
|
||||
break;
|
||||
case 2:
|
||||
z = value;
|
||||
break;
|
||||
case 3:
|
||||
w = value;
|
||||
break;
|
||||
default: throw new IndexOutOfRangeException($"Vector4Long[{index}] out of range.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// instance functions //////////////////////////////////////////////////
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override string ToString() => $"({x} {y} {z} {w})";
|
||||
|
||||
// equality ////////////////////////////////////////////////////////////
|
||||
// implement Equals & HashCode explicitly for performance.
|
||||
// calling .Equals (instead of "==") checks for exact equality.
|
||||
// (API compatibility)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Vector4Long other) =>
|
||||
x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
|
||||
// Equals(object) can reuse Equals(Vector4)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object other) =>
|
||||
other is Vector4Long vector4 && Equals(vector4);
|
||||
|
||||
#if UNITY_2021_3_OR_NEWER
|
||||
// Unity 2019/2020 don't have HashCode.Combine yet.
|
||||
// this is only to avoid reflection. without defining, it works too.
|
||||
// default generated by rider
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||
#endif
|
||||
}
|
||||
}
|
3
Assets/Mirror/Core/Tools/Vector4Long.cs.meta
Normal file
3
Assets/Mirror/Core/Tools/Vector4Long.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f69bf6b6d73476ab3f31dd635bb6497
|
||||
timeCreated: 1726777293
|
@ -36,7 +36,7 @@ public abstract class Transport : MonoBehaviour
|
||||
/// <summary>Is this transport available in the current platform?</summary>
|
||||
public abstract bool Available();
|
||||
|
||||
/// <summary>Is this transported encrypted for secure communication?</summary>
|
||||
/// <summary>Is this transport encrypted for secure communication?</summary>
|
||||
public virtual bool IsEncrypted => false;
|
||||
|
||||
/// <summary>If encrypted, which cipher is used?</summary>
|
||||
@ -66,9 +66,14 @@ public abstract class Transport : MonoBehaviour
|
||||
public Action OnClientDisconnected;
|
||||
|
||||
// server //////////////////////////////////////////////////////////////
|
||||
/// <summary>Called by Transport when a new client connected to the server.</summary>
|
||||
|
||||
// Deprecated 2024-07-20
|
||||
[Obsolete("Use OnServerConnectedWithAddress and pass the remote client address instead")]
|
||||
public Action<int> OnServerConnected;
|
||||
|
||||
/// <summary>Called by Transport when a new client connected to the server.</summary>
|
||||
public Action<int, string> OnServerConnectedWithAddress;
|
||||
|
||||
/// <summary>Called by Transport when the server received a message from a client.</summary>
|
||||
public Action<int, ArraySegment<byte>, int> OnServerDataReceived;
|
||||
|
||||
|
@ -50,6 +50,15 @@ public override void OnInspectorGUI()
|
||||
{
|
||||
ScanForNetworkIdentities();
|
||||
}
|
||||
|
||||
// clicking the Populate button in a large project can add hundreds of entries.
|
||||
// have a clear button in case that wasn't intended.
|
||||
GUI.enabled = networkManager.spawnPrefabs.Count > 0;
|
||||
if (GUILayout.Button("Clear Spawnable Prefabs"))
|
||||
{
|
||||
ClearNetworkIdentities();
|
||||
}
|
||||
GUI.enabled = true;
|
||||
}
|
||||
|
||||
void ScanForNetworkIdentities()
|
||||
@ -117,6 +126,19 @@ void ScanForNetworkIdentities()
|
||||
}
|
||||
}
|
||||
|
||||
void ClearNetworkIdentities()
|
||||
{
|
||||
// RecordObject is needed for "*" to show up in Scene.
|
||||
// however, this only saves List.Count without the entries.
|
||||
Undo.RecordObject(networkManager, "NetworkManager: cleared prefabs");
|
||||
|
||||
// add the entries
|
||||
networkManager.spawnPrefabs.Clear();
|
||||
|
||||
// SetDirty is required to save the individual entries properly.
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
static void DrawHeader(Rect headerRect)
|
||||
{
|
||||
GUI.Label(headerRect, "Registered Spawnable Prefabs:");
|
||||
|
@ -22,19 +22,47 @@ public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2503
|
||||
//
|
||||
// find NetworkReader/Writer extensions in referenced assemblies
|
||||
// save a copy of the collection enumerator since it appears to be modified at some point during iteration
|
||||
IEnumerable<AssemblyNameReference> assemblyReferences = CurrentAssembly.MainModule.AssemblyReferences.ToList();
|
||||
foreach (AssemblyNameReference assemblyNameReference in assemblyReferences)
|
||||
IEnumerable<AssemblyDefinition> assemblyReferences = FindProcessTargetAssemblies(CurrentAssembly, resolver)
|
||||
.Where(assembly => assembly != null && assembly != CurrentAssembly);
|
||||
|
||||
foreach (AssemblyDefinition referencedAssembly in assemblyReferences)
|
||||
ProcessAssemblyClasses(CurrentAssembly, referencedAssembly, writers, readers, ref WeavingFailed);
|
||||
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
|
||||
// look for assembly instead of relying on CurrentAssembly.MainModule.
|
||||
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3816
|
||||
static List<AssemblyDefinition> FindProcessTargetAssemblies(AssemblyDefinition assembly, IAssemblyResolver resolver)
|
||||
{
|
||||
HashSet<string> processedAssemblies = new HashSet<string>();
|
||||
List<AssemblyDefinition> assemblies = new List<AssemblyDefinition>();
|
||||
ProcessAssembly(assembly);
|
||||
return assemblies;
|
||||
|
||||
void ProcessAssembly(AssemblyDefinition current)
|
||||
{
|
||||
AssemblyDefinition referencedAssembly = resolver.Resolve(assemblyNameReference);
|
||||
if (referencedAssembly != null)
|
||||
// If the assembly has already been processed, we skip it
|
||||
if (current.FullName == Weaver.MirrorAssemblyName || !processedAssemblies.Add(current.FullName))
|
||||
return;
|
||||
|
||||
IEnumerable<AssemblyNameReference> references = current.MainModule.AssemblyReferences;
|
||||
|
||||
// If there is no Mirror reference, there will be no ReaderWriter or NetworkMessage, so skip
|
||||
if (references.All(reference => reference.Name != Weaver.MirrorAssemblyName))
|
||||
return;
|
||||
|
||||
// Add the assembly to the processed set and list
|
||||
assemblies.Add(current);
|
||||
|
||||
// Process the references of the current assembly
|
||||
foreach (AssemblyNameReference reference in references)
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, referencedAssembly, writers, readers, ref WeavingFailed);
|
||||
AssemblyDefinition referencedAssembly = resolver.Resolve(reference);
|
||||
if (referencedAssembly != null)
|
||||
ProcessAssembly(referencedAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
// find readers/writers in the assembly we are in right now.
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||
}
|
||||
|
||||
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||
|
@ -33,12 +33,24 @@ public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinit
|
||||
|
||||
internal void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
if (readFuncs.ContainsKey(dataType))
|
||||
// sometimes we define multiple read methods for the same type.
|
||||
// for example:
|
||||
// ReadInt() // alwasy writes 4 bytes: should be available to the user for binary protocols etc.
|
||||
// ReadVarInt() // varint compression: we may want Weaver to always use this for minimal bandwidth
|
||||
// give the user a way to define the weaver prefered one if two exists:
|
||||
// "[WeaverPriority]" attribute is automatically detected and prefered.
|
||||
MethodDefinition methodDefinition = methodReference.Resolve();
|
||||
bool priority = methodDefinition.HasCustomAttribute<WeaverPriorityAttribute>();
|
||||
// if (priority) Log.Warning($"Weaver: Registering priority Read<{dataType.FullName}> with {methodReference.FullName}.", methodReference);
|
||||
|
||||
// Weaver sometimes calls Register for <T> multiple times because we resolve assemblies multiple times.
|
||||
// if the function name is the same: always use the latest one.
|
||||
// if the function name differes: use the priority one.
|
||||
if (readFuncs.TryGetValue(dataType, out MethodReference existingMethod) && // if it was already defined
|
||||
existingMethod.FullName != methodReference.FullName && // and this one is a different name
|
||||
!priority) // and it's not the priority one
|
||||
{
|
||||
// TODO enable this again later.
|
||||
// Reader has some obsolete functions that were renamed.
|
||||
// Don't want weaver warnings for all of them.
|
||||
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
|
||||
return; // then skip
|
||||
}
|
||||
|
||||
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
|
||||
|
@ -33,12 +33,24 @@ public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinit
|
||||
|
||||
public void Register(TypeReference dataType, MethodReference methodReference)
|
||||
{
|
||||
if (writeFuncs.ContainsKey(dataType))
|
||||
// sometimes we define multiple write methods for the same type.
|
||||
// for example:
|
||||
// WriteInt() // alwasy writes 4 bytes: should be available to the user for binary protocols etc.
|
||||
// WriteVarInt() // varint compression: we may want Weaver to always use this for minimal bandwidth
|
||||
// give the user a way to define the weaver prefered one if two exists:
|
||||
// "[WeaverPriority]" attribute is automatically detected and prefered.
|
||||
MethodDefinition methodDefinition = methodReference.Resolve();
|
||||
bool priority = methodDefinition.HasCustomAttribute<WeaverPriorityAttribute>();
|
||||
// if (priority) Log.Warning($"Weaver: Registering priority Write<{dataType.FullName}> with {methodReference.FullName}.", methodReference);
|
||||
|
||||
// Weaver sometimes calls Register for <T> multiple times because we resolve assemblies multiple times.
|
||||
// if the function name is the same: always use the latest one.
|
||||
// if the function name differes: use the priority one.
|
||||
if (writeFuncs.TryGetValue(dataType, out MethodReference existingMethod) && // if it was already defined
|
||||
existingMethod.FullName != methodReference.FullName && // and this one is a different name
|
||||
!priority) // and it's not the priority one
|
||||
{
|
||||
// TODO enable this again later.
|
||||
// Writer has some obsolete functions that were renamed.
|
||||
// Don't want weaver warnings for all of them.
|
||||
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
|
||||
return; // then skip
|
||||
}
|
||||
|
||||
// we need to import type when we Initialize Writers so import here in case it is used anywhere else
|
||||
|
@ -15,7 +15,7 @@ static void OnInitializeOnLoad()
|
||||
if (!SessionState.GetBool("MIRROR_WELCOME", false))
|
||||
{
|
||||
SessionState.SetBool("MIRROR_WELCOME", true);
|
||||
Debug.Log("Mirror | mirror-networking.com | discord.gg/N9QVxbM");
|
||||
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Mirror | mirror-networking.com | discord.gg/N9QVxbM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public void ProcessMessageQueue(MonoBehaviour behaviour)
|
||||
}
|
||||
}
|
||||
if (receiveQueue.Count > 0)
|
||||
Log.Warn($"[SWT-SimpleWebClient]: ProcessMessageQueue has {receiveQueue.Count} remaining.");
|
||||
Log.Warn("[SWT-SimpleWebClient]: ProcessMessageQueue has {0} remaining.", receiveQueue.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public bool TryHandshake(Connection conn, Uri uri)
|
||||
string key = Convert.ToBase64String(keyBuffer);
|
||||
string keySum = key + Constants.HandshakeGUID;
|
||||
byte[] keySumBytes = Encoding.ASCII.GetBytes(keySum);
|
||||
Log.Verbose($"[SWT-ClientHandshake]: Handshake Hashing {Encoding.ASCII.GetString(keySumBytes)}");
|
||||
Log.Verbose("[SWT-ClientHandshake]: Handshake Hashing {0}", Encoding.ASCII.GetString(keySumBytes));
|
||||
|
||||
// SHA-1 is the websocket standard:
|
||||
// https://www.rfc-editor.org/rfc/rfc6455
|
||||
@ -55,14 +55,14 @@ public bool TryHandshake(Connection conn, Uri uri)
|
||||
}
|
||||
|
||||
string responseString = Encoding.ASCII.GetString(responseBuffer, 0, lengthOrNull.Value);
|
||||
Log.Verbose($"[SWT-ClientHandshake]: Handshake Response {responseString}");
|
||||
Log.Verbose("[SWT-ClientHandshake]: Handshake Response {0}", responseString);
|
||||
|
||||
string acceptHeader = "Sec-WebSocket-Accept: ";
|
||||
int startIndex = responseString.IndexOf(acceptHeader, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
Log.Error($"[SWT-ClientHandshake]: Unexpected Handshake Response {responseString}");
|
||||
Log.Error("[SWT-ClientHandshake]: Unexpected Handshake Response {0}", responseString);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -72,10 +72,7 @@ public bool TryHandshake(Connection conn, Uri uri)
|
||||
|
||||
if (responseKey != expectedResponse)
|
||||
{
|
||||
Log.Error($"[SWT-ClientHandshake]: Response key incorrect\n" +
|
||||
$"Expected:{expectedResponse}\n" +
|
||||
$"Response:{responseKey}\n" +
|
||||
$"This can happen if Websocket Protocol is not installed in Windows Server Roles.");
|
||||
Log.Error("[SWT-ClientHandshake]: Response key incorrect\nExpected:{0}\nResponse:{1}\nThis can happen if Websocket Protocol is not installed in Windows Server Roles.", expectedResponse, responseKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ internal class ClientSslHelper
|
||||
internal bool TryCreateStream(Connection conn, Uri uri)
|
||||
{
|
||||
NetworkStream stream = conn.client.GetStream();
|
||||
if (uri.Scheme != "wss")
|
||||
if (uri.Scheme != "wss" && uri.Scheme != "https")
|
||||
{
|
||||
conn.stream = stream;
|
||||
return true;
|
||||
@ -24,7 +24,7 @@ internal bool TryCreateStream(Connection conn, Uri uri)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"[SWT-ClientSslHelper]: Create SSLStream Failed: {e.Message}\n{e.StackTrace}\n\n");
|
||||
Log.Error("[SWT-ClientSslHelper]: Create SSLStream Failed: {0}\n{1}\n\n", e.Message, e.StackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ void ConnectAndReceiveLoop(Uri serverAddress)
|
||||
bool success = sslHelper.TryCreateStream(conn, serverAddress);
|
||||
if (!success)
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketClientStandAlone]: Failed to create Stream with {serverAddress}");
|
||||
Log.Warn("[SWT-WebSocketClientStandAlone]: Failed to create Stream with {0}", serverAddress);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
@ -68,12 +68,12 @@ void ConnectAndReceiveLoop(Uri serverAddress)
|
||||
success = handshake.TryHandshake(conn, serverAddress);
|
||||
if (!success)
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketClientStandAlone]: Failed Handshake with {serverAddress}");
|
||||
Log.Warn("[SWT-WebSocketClientStandAlone]: Failed Handshake with {0}", serverAddress);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info($"[SWT-WebSocketClientStandAlone]: HandShake Successful with {serverAddress}");
|
||||
Log.Info("[SWT-WebSocketClientStandAlone]: HandShake Successful with {0}", serverAddress);
|
||||
|
||||
state = ClientState.Connected;
|
||||
|
||||
@ -101,7 +101,7 @@ void ConnectAndReceiveLoop(Uri serverAddress)
|
||||
ReceiveLoop.Loop(config);
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-WebSocketClientStandAlone]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
finally
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ public override void Send(ArraySegment<byte> segment)
|
||||
{
|
||||
if (segment.Count > maxMessageSize)
|
||||
{
|
||||
Log.Error($"[SWT-WebSocketClientWebGl]: Cant send message with length {segment.Count} because it is over the max size of {maxMessageSize}");
|
||||
Log.Error("[SWT-WebSocketClientWebGl]: Cant send message with length {0} because it is over the max size of {1}", segment.Count, maxMessageSize);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ void onMessage(IntPtr bufferPtr, int count)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"[SWT-WebSocketClientWebGl]: onMessage {e.GetType()}: {e.Message}\n{e.StackTrace}");
|
||||
Log.Error("[SWT-WebSocketClientWebGl]: onMessage {0}: {1}\n{2}", e.GetType(), e.Message, e.StackTrace);
|
||||
receiveQueue.Enqueue(new Message(e));
|
||||
}
|
||||
}
|
||||
|
@ -49,23 +49,21 @@ function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackP
|
||||
const index = SimpleWeb.AddNextSocket(webSocket);
|
||||
|
||||
// Connection opened
|
||||
webSocket.addEventListener('open', function (event)
|
||||
webSocket.onopen = function(event)
|
||||
{
|
||||
console.log("Connected to " + address);
|
||||
Runtime.dynCall('vi', openCallbackPtr, [index]);
|
||||
});
|
||||
webSocket.addEventListener('close', function (event)
|
||||
};
|
||||
|
||||
webSocket.onclose = function(event)
|
||||
{
|
||||
console.log("Disconnected from " + address);
|
||||
Runtime.dynCall('vi', closeCallBackPtr, [index]);
|
||||
});
|
||||
};
|
||||
|
||||
// Listen for messages
|
||||
webSocket.addEventListener('message', function (event)
|
||||
webSocket.onmessage = function(event)
|
||||
{
|
||||
if (event.data instanceof ArrayBuffer)
|
||||
{
|
||||
// TODO dont alloc each time
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
var array = new Uint8Array(event.data);
|
||||
var arrayLength = array.length;
|
||||
|
||||
@ -80,13 +78,13 @@ function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackP
|
||||
{
|
||||
console.error("message type not supported")
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
webSocket.addEventListener('error', function (event)
|
||||
webSocket.onerror = function(event)
|
||||
{
|
||||
console.error('Socket Error', event);
|
||||
Runtime.dynCall('vi', errorCallbackPtr, [index]);
|
||||
});
|
||||
};
|
||||
|
||||
return index;
|
||||
}
|
||||
|
@ -226,12 +226,12 @@ public BufferPool(int bucketCount, int smallest, int largest)
|
||||
void Validate()
|
||||
{
|
||||
if (buckets[0].arraySize != smallest)
|
||||
Log.Error($"[SWT-BufferPool]: BufferPool Failed to create bucket for smallest. bucket:{buckets[0].arraySize} smallest:{smallest}");
|
||||
Log.Error("[SWT-BufferPool]: BufferPool Failed to create bucket for smallest. bucket:{0} smallest:{1}", buckets[0].arraySize, smallest);
|
||||
|
||||
int largestBucket = buckets[bucketCount - 1].arraySize;
|
||||
// rounded using Ceiling, so allowed to be 1 more that largest
|
||||
if (largestBucket != largest && largestBucket != largest + 1)
|
||||
Log.Error($"[SWT-BufferPool]: BufferPool Failed to create bucket for largest. bucket:{largestBucket} smallest:{largest}");
|
||||
Log.Error("[SWT-BufferPool]: BufferPool Failed to create bucket for largest. bucket:{0} smallest:{1}", largestBucket, largest);
|
||||
}
|
||||
|
||||
public ArrayBuffer Take(int size)
|
||||
|
@ -48,12 +48,12 @@ public Connection(TcpClient client, Action<Connection> onDispose)
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Log.Verbose($"[SWT-Connection]: Dispose {ToString()}");
|
||||
Log.Verbose("[SWT-Connection]: Dispose {0}", ToString());
|
||||
|
||||
// check hasDisposed first to stop ThreadInterruptedException on lock
|
||||
if (hasDisposed) return;
|
||||
|
||||
Log.Verbose($"[SWT-Connection]: Connection Close: {ToString()}");
|
||||
Log.Verbose("[SWT-Connection]: Connection Close: {0}", ToString());
|
||||
|
||||
lock (disposedLock)
|
||||
{
|
||||
|
@ -67,10 +67,10 @@ public static void Flood(string msg)
|
||||
|
||||
#if UNITY_SERVER || UNITY_WEBGL
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
logger.Log(LogType.Log, msg);
|
||||
logger.Log(LogType.Log, msg.Trim());
|
||||
Console.ResetColor();
|
||||
#else
|
||||
logger.Log(LogType.Log, msg);
|
||||
logger.Log(LogType.Log, msg.Trim());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -126,35 +126,57 @@ public static void Verbose(string msg)
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Log, msg);
|
||||
logger.Log(LogType.Log, msg.Trim());
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Blue;
|
||||
Console.WriteLine(msg);
|
||||
Console.WriteLine(msg.Trim());
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Verbose<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Verbose) return;
|
||||
Verbose(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
public static void Verbose<T1, T2>(string msg, T1 arg1, T2 arg2)
|
||||
{
|
||||
if (minLogLevel > Levels.Verbose) return;
|
||||
Verbose(String.Format(msg, arg1, arg2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Info or lower
|
||||
/// </summary>
|
||||
/// <param name="msg">Message text to log</param>
|
||||
/// <param name="consoleColor">Default Cyan works in server and browser consoles</param>
|
||||
public static void Info(string msg, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
static void Info(string msg, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Log, msg);
|
||||
logger.Log(LogType.Log, msg.Trim());
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = consoleColor;
|
||||
Console.WriteLine(msg);
|
||||
Console.WriteLine(msg.Trim());
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Info<T>(string msg, T arg1, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
Info(String.Format(msg, arg1), consoleColor);
|
||||
}
|
||||
|
||||
public static void Info<T1, T2>(string msg, T1 arg1, T2 arg2, ConsoleColor consoleColor = ConsoleColor.Cyan)
|
||||
{
|
||||
if (minLogLevel > Levels.Info) return;
|
||||
Info(String.Format(msg, arg1, arg2), consoleColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Info or lower
|
||||
/// </summary>
|
||||
@ -184,15 +206,21 @@ public static void Warn(string msg)
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Warning, msg);
|
||||
logger.Log(LogType.Warning, msg.Trim());
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(msg);
|
||||
Console.WriteLine(msg.Trim());
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Warn<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Warn) return;
|
||||
Warn(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs info to console if minLogLevel is set to Error or lower
|
||||
/// </summary>
|
||||
@ -203,15 +231,33 @@ public static void Error(string msg)
|
||||
|
||||
#if DEBUG
|
||||
// Debug builds and Unity Editor
|
||||
logger.Log(LogType.Error, msg);
|
||||
logger.Log(LogType.Error, msg.Trim());
|
||||
#else
|
||||
// Server or WebGL
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(msg);
|
||||
Console.WriteLine(msg.Trim());
|
||||
Console.ResetColor();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Error<T>(string msg, T arg1)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1));
|
||||
}
|
||||
|
||||
public static void Error<T1, T2>(string msg, T1 arg1, T2 arg2)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1, arg2));
|
||||
}
|
||||
|
||||
public static void Error<T1, T2, T3>(string msg, T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
if (minLogLevel > Levels.Error) return;
|
||||
Error(String.Format(msg, arg1, arg2, arg3));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the byte array starting from offset for length bytes
|
||||
/// </summary>
|
||||
|
@ -20,7 +20,7 @@ public static int Read(Stream stream, byte[] outBuffer, int outOffset, int lengt
|
||||
{
|
||||
int read = stream.Read(outBuffer, outOffset + received, length - received);
|
||||
if (read == 0)
|
||||
throw new ReadHelperException("returned 0");
|
||||
throw new ReadHelperException("[SWT-ReadHelper]: Read returned 0");
|
||||
|
||||
received += read;
|
||||
}
|
||||
@ -35,7 +35,7 @@ public static int Read(Stream stream, byte[] outBuffer, int outOffset, int lengt
|
||||
}
|
||||
|
||||
if (received != length)
|
||||
throw new ReadHelperException("returned not equal to length");
|
||||
throw new ReadHelperException("[SWT-ReadHelper]: received not equal to length");
|
||||
|
||||
return outOffset + received;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public static void Loop(Config config)
|
||||
while (client.Connected)
|
||||
ReadOneMessage(config, readBuffer);
|
||||
|
||||
Log.Verbose($"[SWT-ReceiveLoop]: {conn} Not Connected");
|
||||
Log.Verbose("[SWT-ReceiveLoop]: {0} Not Connected", conn);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@ -72,27 +72,24 @@ public static void Loop(Config config)
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-ReceiveLoop]: Thread Abort Exception"); }
|
||||
catch (ObjectDisposedException e) { Log.InfoException(e); }
|
||||
catch (ReadHelperException e)
|
||||
{
|
||||
Log.InfoException(e);
|
||||
}
|
||||
catch (ReadHelperException e) { Log.InfoException(e); }
|
||||
catch (SocketException e)
|
||||
{
|
||||
// this could happen if wss client closes stream
|
||||
Log.Warn($"[SWT-ReceiveLoop]: ReceiveLoop SocketException\n{e.Message}");
|
||||
Log.Warn("[SWT-ReceiveLoop]: ReceiveLoop SocketException\n{0}", e.Message);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// this could happen if client disconnects
|
||||
Log.Warn($"[SWT-ReceiveLoop]: ReceiveLoop IOException\n{e.Message}");
|
||||
Log.Warn("[SWT-ReceiveLoop]: ReceiveLoop IOException\n{0}", e.Message);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
Log.Error($"[SWT-ReceiveLoop]: Invalid data from {conn}\n{e.Message}\n{e.StackTrace}\n\n");
|
||||
Log.Error("[SWT-ReceiveLoop]: Invalid data from {0}\n{1}\n{2}\n\n", conn, e.Message, e.StackTrace);
|
||||
queue.Enqueue(new Message(conn.connId, e));
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -239,7 +236,7 @@ static void HandleCloseMessage(Config config, byte[] buffer, int msgOffset, int
|
||||
|
||||
// dump after mask off
|
||||
Log.DumpBuffer($"[SWT-ReceiveLoop]: Message", buffer, msgOffset, payloadLength);
|
||||
Log.Verbose($"[SWT-ReceiveLoop]: Close: {GetCloseCode(buffer, msgOffset)} message:{GetCloseMessage(buffer, msgOffset, payloadLength)}");
|
||||
Log.Verbose("[SWT-ReceiveLoop]: Close: {0} message:{1}", GetCloseCode(buffer, msgOffset), GetCloseMessage(buffer, msgOffset, payloadLength));
|
||||
|
||||
conn.Dispose();
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public static void Loop(Config config)
|
||||
// check if connected before sending message
|
||||
if (!client.Connected)
|
||||
{
|
||||
Log.Verbose($"[SWT-SendLoop]: SendLoop {conn} not connected");
|
||||
Log.Verbose("[SWT-SendLoop]: SendLoop {0} not connected", conn);
|
||||
msg.Release();
|
||||
return;
|
||||
}
|
||||
@ -101,7 +101,7 @@ public static void Loop(Config config)
|
||||
// check if connected before sending message
|
||||
if (!client.Connected)
|
||||
{
|
||||
Log.Verbose($"[SWT-SendLoop]: SendLoop {conn} not connected");
|
||||
Log.Verbose("[SWT-SendLoop]: SendLoop {0} not connected", conn);
|
||||
msg.Release();
|
||||
return;
|
||||
}
|
||||
@ -113,14 +113,11 @@ public static void Loop(Config config)
|
||||
}
|
||||
}
|
||||
|
||||
Log.Verbose($"[SWT-SendLoop]: {conn} Not Connected");
|
||||
Log.Verbose("[SWT-SendLoop]: {0} Not Connected", conn);
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-SendLoop]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
finally
|
||||
{
|
||||
Profiler.EndThreadProfiling();
|
||||
|
@ -50,7 +50,7 @@ public bool TryHandshake(Connection conn)
|
||||
|
||||
if (!IsGet(getHeader.array))
|
||||
{
|
||||
Log.Warn($"[SWT-ServerHandshake]: First bytes from client was not 'GET' for handshake, instead was {Log.BufferToString(getHeader.array, 0, GetSize)}");
|
||||
Log.Warn("[SWT-ServerHandshake]: First bytes from client was not 'GET' for handshake, instead was {0}", Log.BufferToString(getHeader.array, 0, GetSize));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,7 @@ public bool TryHandshake(Connection conn)
|
||||
|
||||
conn.request = new Request(msg);
|
||||
conn.remoteAddress = conn.CalculateAddress();
|
||||
Log.Info($"[SWT-ServerHandshake]: A client connected from {conn}");
|
||||
Log.Info($"[SWT-ServerHandshake]: A client connected from {0}", conn);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -90,7 +90,7 @@ string ReadToEndForHandshake(Stream stream)
|
||||
string msg = Encoding.ASCII.GetString(readBuffer.array, 0, readCount);
|
||||
// GET isn't in the bytes we read here, so we need to add it back
|
||||
msg = $"GET{msg}";
|
||||
Log.Verbose($"[SWT-ServerHandshake]: Client Handshake Message:\r\n{msg}");
|
||||
Log.Verbose("[SWT-ServerHandshake]: Client Handshake Message:\r\n{0}", msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
@ -122,7 +122,7 @@ static void GetKey(string msg, byte[] keyBuffer)
|
||||
{
|
||||
int start = msg.IndexOf(KeyHeaderString, StringComparison.InvariantCultureIgnoreCase) + KeyHeaderString.Length;
|
||||
|
||||
Log.Verbose($"[SWT-ServerHandshake]: Handshake Key: {msg.Substring(start, KeyLength)}");
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Key: {0}", msg.Substring(start, KeyLength));
|
||||
Encoding.ASCII.GetBytes(msg, start, KeyLength, keyBuffer, 0);
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ static void AppendGuid(byte[] keyBuffer)
|
||||
|
||||
byte[] CreateHash(byte[] keyBuffer)
|
||||
{
|
||||
Log.Verbose($"[SWT-ServerHandshake]: Handshake Hashing {Encoding.ASCII.GetString(keyBuffer, 0, MergedKeyLength)}");
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Hashing {0}", Encoding.ASCII.GetString(keyBuffer, 0, MergedKeyLength));
|
||||
return sha1.ComputeHash(keyBuffer, 0, MergedKeyLength);
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ static void CreateResponse(byte[] keyHash, byte[] responseBuffer)
|
||||
"Sec-WebSocket-Accept: {0}\r\n\r\n",
|
||||
keyHashString);
|
||||
|
||||
Log.Verbose($"[SWT-ServerHandshake]: Handshake Response length {message.Length}, IsExpected {message.Length == ResponseLength}");
|
||||
Log.Verbose("[SWT-ServerHandshake]: Handshake Response length {0}, IsExpected {1}", message.Length, message.Length == ResponseLength);
|
||||
Encoding.ASCII.GetBytes(message, 0, ResponseLength, responseBuffer, 0);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public ServerSslHelper(SslConfig sslConfig)
|
||||
if (config.enabled)
|
||||
{
|
||||
certificate = new X509Certificate2(config.certPath, config.certPassword);
|
||||
Log.Info($"[SWT-ServerSslHelper]: SSL Certificate {certificate.Subject} loaded with expiration of {certificate.GetExpirationDateString()}");
|
||||
Log.Info($"[SWT-ServerSslHelper]: SSL Certificate {0} loaded with expiration of {1}", certificate.Subject, certificate.GetExpirationDateString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ internal bool TryCreateStream(Connection conn)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"[SWT-ServerSslHelper]: Create SSLStream Failed: {e.Message}");
|
||||
Log.Error("[SWT-ServerSslHelper]: Create SSLStream Failed: {0}", e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ namespace Mirror.SimpleWeb
|
||||
{
|
||||
public class SimpleWebServer
|
||||
{
|
||||
public event Action<int> onConnect;
|
||||
public event Action<int, string> onConnect;
|
||||
public event Action<int> onDisconnect;
|
||||
public event Action<int, ArraySegment<byte>> onData;
|
||||
public event Action<int, Exception> onError;
|
||||
@ -91,7 +91,7 @@ public void ProcessMessageQueue(MonoBehaviour behaviour)
|
||||
switch (next.type)
|
||||
{
|
||||
case EventType.Connected:
|
||||
onConnect?.Invoke(next.connId);
|
||||
onConnect?.Invoke(next.connId, GetClientAddress(next.connId));
|
||||
break;
|
||||
case EventType.Data:
|
||||
onData?.Invoke(next.connId, next.data.ToSegment());
|
||||
@ -108,7 +108,7 @@ public void ProcessMessageQueue(MonoBehaviour behaviour)
|
||||
|
||||
if (server.receiveQueue.Count > 0)
|
||||
{
|
||||
Log.Warn($"[SWT-SimpleWebServer]: ProcessMessageQueue has {server.receiveQueue.Count} remaining.");
|
||||
Log.Warn("[SWT-SimpleWebServer]: ProcessMessageQueue has {0} remaining.", server.receiveQueue.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public void Listen(int port)
|
||||
listener = TcpListener.Create(port);
|
||||
listener.Start();
|
||||
|
||||
Log.Verbose($"[SWT-WebSocketServer]: Server Started on {port}");
|
||||
Log.Verbose("[SWT-WebSocketServer]: Server Started on {0}", port);
|
||||
|
||||
acceptThread = new Thread(acceptLoop);
|
||||
acceptThread.IsBackground = true;
|
||||
@ -53,7 +53,7 @@ public void Stop()
|
||||
listener?.Stop();
|
||||
acceptThread = null;
|
||||
|
||||
Log.Verbose($"[SWT-WebSocketServer]: Server stopped...closing all connections.");
|
||||
Log.Verbose("[SWT-WebSocketServer]: Server stopped...closing all connections.");
|
||||
|
||||
// make copy so that foreach doesn't break if values are removed
|
||||
Connection[] connectionsCopy = connections.Values.ToArray();
|
||||
@ -78,7 +78,7 @@ void acceptLoop()
|
||||
// this might not be a problem as HandshakeAndReceiveLoop checks for stop
|
||||
// and returns/disposes before sending message to queue
|
||||
Connection conn = new Connection(client, AfterConnectionDisposed);
|
||||
Log.Verbose($"[SWT-WebSocketServer]: A client connected from {conn}");
|
||||
Log.Verbose("[SWT-WebSocketServer]: A client connected from {0}", conn);
|
||||
|
||||
// handshake needs its own thread as it needs to wait for message from client
|
||||
Thread receiveThread = new Thread(() => HandshakeAndReceiveLoop(conn));
|
||||
@ -97,7 +97,7 @@ void acceptLoop()
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-WebSocketServer]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ void HandshakeAndReceiveLoop(Connection conn)
|
||||
bool success = sslHelper.TryCreateStream(conn);
|
||||
if (!success)
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketServer]: Failed to create SSL Stream {conn}");
|
||||
Log.Warn("[SWT-WebSocketServer]: Failed to create SSL Stream {0}", conn);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
@ -116,10 +116,10 @@ void HandshakeAndReceiveLoop(Connection conn)
|
||||
success = handShake.TryHandshake(conn);
|
||||
|
||||
if (success)
|
||||
Log.Verbose($"[SWT-WebSocketServer]: Sent Handshake {conn}, false");
|
||||
Log.Verbose("[SWT-WebSocketServer]: Sent Handshake {0}, false", conn);
|
||||
else
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketServer]: Handshake Failed {conn}");
|
||||
Log.Warn("[SWT-WebSocketServer]: Handshake Failed {0}", conn);
|
||||
conn.Dispose();
|
||||
return;
|
||||
}
|
||||
@ -160,18 +160,9 @@ void HandshakeAndReceiveLoop(Connection conn)
|
||||
|
||||
ReceiveLoop.Loop(receiveConfig);
|
||||
}
|
||||
catch (ThreadInterruptedException e)
|
||||
{
|
||||
Log.Error($"[SWT-WebSocketServer]: Handshake ThreadInterruptedException {e.Message}");
|
||||
}
|
||||
catch (ThreadAbortException e)
|
||||
{
|
||||
Log.Error($"[SWT-WebSocketServer]: Handshake ThreadAbortException {e.Message}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"[SWT-WebSocketServer]: Handshake Exception {e.Message}");
|
||||
}
|
||||
catch (ThreadInterruptedException e) { Log.InfoException(e); }
|
||||
catch (ThreadAbortException) { Log.Error("[SWT-WebSocketServer]: Thread Abort Exception"); }
|
||||
catch (Exception e) { Log.Exception(e); }
|
||||
finally
|
||||
{
|
||||
// close here in case connect fails
|
||||
@ -196,20 +187,20 @@ public void Send(int id, ArrayBuffer buffer)
|
||||
conn.sendPending.Set();
|
||||
}
|
||||
else
|
||||
Log.Warn($"[SWT-WebSocketServer]: Cannot send message to {id} because connection was not found in dictionary. Maybe it disconnected.");
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot send message to {0} because connection was not found in dictionary. Maybe it disconnected.", id);
|
||||
}
|
||||
|
||||
public bool CloseConnection(int id)
|
||||
{
|
||||
if (connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Info($"[SWT-WebSocketServer]: Disconnecting connection {id}");
|
||||
Log.Info($"[SWT-WebSocketServer]: Disconnecting connection {0}", id);
|
||||
conn.Dispose();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketServer]: Failed to kick {id} because id not found.");
|
||||
Log.Warn("[SWT-WebSocketServer]: Failed to kick {0} because id not found.", id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -218,7 +209,7 @@ public string GetClientAddress(int id)
|
||||
{
|
||||
if (!connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketServer]: Cannot get address of connection {id} because connection was not found in dictionary.");
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot get address of connection {0} because connection was not found in dictionary.", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -229,7 +220,7 @@ public Request GetClientRequest(int id)
|
||||
{
|
||||
if (!connections.TryGetValue(id, out Connection conn))
|
||||
{
|
||||
Log.Warn($"[SWT-WebSocketServer]: Cannot get request of connection {id} because connection was not found in dictionary.");
|
||||
Log.Warn("[SWT-WebSocketServer]: Cannot get request of connection {0} because connection was not found in dictionary.", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ public class SimpleWebTransport : Transport, PortTransport
|
||||
|
||||
[FormerlySerializedAs("handshakeMaxSize")]
|
||||
[Tooltip("Max size for http header send as handshake for websockets")]
|
||||
public int maxHandshakeSize = 3000;
|
||||
public int maxHandshakeSize = 16 * 1024;
|
||||
|
||||
[FormerlySerializedAs("serverMaxMessagesPerTick")]
|
||||
[Tooltip("Caps the number of messages the server will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")]
|
||||
@ -51,7 +51,7 @@ public class SimpleWebTransport : Transport, PortTransport
|
||||
[Header("Server settings")]
|
||||
|
||||
[Tooltip("Port to use for server")]
|
||||
public ushort port = 7778;
|
||||
public ushort port = 27777;
|
||||
public ushort Port
|
||||
{
|
||||
get
|
||||
@ -90,7 +90,7 @@ public ushort Port
|
||||
|
||||
[Tooltip("Sets connect scheme to wss. Useful when client needs to connect using wss when TLS is outside of transport.\nNOTE: if sslEnabled is true clientUseWss is also true")]
|
||||
public bool clientUseWss;
|
||||
public ClientWebsocketSettings clientWebsocketSettings;
|
||||
public ClientWebsocketSettings clientWebsocketSettings = new ClientWebsocketSettings { ClientPortOption = WebsocketPortOption.DefaultSameAsServer, CustomClientPort = 7777 };
|
||||
|
||||
[Header("Logging")]
|
||||
|
||||
@ -300,7 +300,7 @@ public override void ServerStart()
|
||||
SslConfig config = SslConfigLoader.Load(sslEnabled, sslCertJson, sslProtocols);
|
||||
server = new SimpleWebServer(serverMaxMsgsPerTick, TcpConfig, maxMessageSize, maxHandshakeSize, config);
|
||||
|
||||
server.onConnect += OnServerConnected.Invoke;
|
||||
server.onConnect += OnServerConnectedWithAddress.Invoke;
|
||||
server.onDisconnect += OnServerDisconnected.Invoke;
|
||||
server.onData += (int connId, ArraySegment<byte> data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable);
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -1,7 +1,7 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015, Unity Technologies
|
||||
Copyright (c) 2019, vis2k, Paul and Contributors
|
||||
Copyright (c) 2019, Mischa, Paul, Chris, Robin, Stephen and Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -3,7 +3,7 @@
|
||||
--- !u!129 &1
|
||||
PlayerSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 23
|
||||
serializedVersion: 24
|
||||
productGUID: bb0f7d422b91e4aab8e0fe9838a26db0
|
||||
AndroidProfiler: 0
|
||||
AndroidFilterTouchesWhenObscured: 0
|
||||
@ -48,6 +48,7 @@ PlayerSettings:
|
||||
defaultScreenHeightWeb: 600
|
||||
m_StereoRenderingPath: 0
|
||||
m_ActiveColorSpace: 0
|
||||
unsupportedMSAAFallback: 0
|
||||
m_MTRendering: 1
|
||||
mipStripping: 0
|
||||
numberOfMipsStripped: 0
|
||||
@ -74,6 +75,7 @@ PlayerSettings:
|
||||
androidMinimumWindowWidth: 400
|
||||
androidMinimumWindowHeight: 300
|
||||
androidFullscreenMode: 1
|
||||
androidAutoRotationBehavior: 1
|
||||
defaultIsNativeResolution: 1
|
||||
macRetinaSupport: 1
|
||||
runInBackground: 1
|
||||
@ -121,6 +123,7 @@ PlayerSettings:
|
||||
switchNVNOtherPoolsGranularity: 16777216
|
||||
switchNVNMaxPublicTextureIDCount: 0
|
||||
switchNVNMaxPublicSamplerIDCount: 0
|
||||
switchMaxWorkerMultiple: 8
|
||||
stadiaPresentMode: 0
|
||||
stadiaTargetFramerate: 0
|
||||
vulkanNumSwapchainBuffers: 3
|
||||
@ -177,10 +180,10 @@ PlayerSettings:
|
||||
StripUnusedMeshComponents: 0
|
||||
VertexChannelCompressionMask: 4054
|
||||
iPhoneSdkVersion: 988
|
||||
iOSTargetOSVersionString: 11.0
|
||||
iOSTargetOSVersionString: 12.0
|
||||
tvOSSdkVersion: 0
|
||||
tvOSRequireExtendedGameController: 0
|
||||
tvOSTargetOSVersionString: 11.0
|
||||
tvOSTargetOSVersionString: 12.0
|
||||
uIPrerenderedIcon: 0
|
||||
uIRequiresPersistentWiFi: 0
|
||||
uIRequiresFullScreen: 1
|
||||
@ -346,7 +349,7 @@ PlayerSettings:
|
||||
switchSocketConcurrencyLimit: 14
|
||||
switchScreenResolutionBehavior: 2
|
||||
switchUseCPUProfiler: 0
|
||||
switchUseGOLDLinker: 0
|
||||
switchEnableFileSystemTrace: 0
|
||||
switchLTOSetting: 0
|
||||
switchApplicationID: 0x01004b9000490000
|
||||
switchNSODependencies:
|
||||
@ -423,7 +426,6 @@ PlayerSettings:
|
||||
switchReleaseVersion: 0
|
||||
switchDisplayVersion: 1.0.0
|
||||
switchStartupUserAccount: 0
|
||||
switchTouchScreenUsage: 0
|
||||
switchSupportedLanguagesMask: 0
|
||||
switchLogoType: 0
|
||||
switchApplicationErrorCodeCategory:
|
||||
@ -465,6 +467,7 @@ PlayerSettings:
|
||||
switchNativeFsCacheSize: 32
|
||||
switchIsHoldTypeHorizontal: 0
|
||||
switchSupportedNpadCount: 8
|
||||
switchEnableTouchScreen: 1
|
||||
switchSocketConfigEnabled: 0
|
||||
switchTcpInitialSendBufferSize: 32
|
||||
switchTcpInitialReceiveBufferSize: 64
|
||||
@ -475,7 +478,6 @@ PlayerSettings:
|
||||
switchSocketBufferEfficiency: 4
|
||||
switchSocketInitializeEnabled: 1
|
||||
switchNetworkInterfaceManagerInitializeEnabled: 1
|
||||
switchPlayerConnectionEnabled: 1
|
||||
switchUseNewStyleFilepaths: 0
|
||||
switchUseLegacyFmodPriorities: 1
|
||||
switchUseMicroSleepForYield: 1
|
||||
@ -579,21 +581,34 @@ PlayerSettings:
|
||||
webGLDecompressionFallback: 0
|
||||
webGLPowerPreference: 2
|
||||
scriptingDefineSymbols:
|
||||
Server: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER
|
||||
Standalone: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER
|
||||
WebGL: MIRROR;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER;MIRROR_2022_9_OR_NEWER;MIRROR_2022_10_OR_NEWER;MIRROR_70_0_OR_NEWER;MIRROR_71_0_OR_NEWER;MIRROR_73_OR_NEWER
|
||||
Server: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER
|
||||
Standalone: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER;MIRROR_86_OR_NEWER;MIRROR_89_OR_NEWER;MIRROR_90_OR_NEWER;EDGEGAP_PLUGIN_SERVERS
|
||||
WebGL: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER
|
||||
additionalCompilerArguments: {}
|
||||
platformArchitecture: {}
|
||||
scriptingBackend:
|
||||
Standalone: 1
|
||||
il2cppCompilerConfiguration: {}
|
||||
managedStrippingLevel: {}
|
||||
managedStrippingLevel:
|
||||
EmbeddedLinux: 1
|
||||
GameCoreScarlett: 1
|
||||
GameCoreXboxOne: 1
|
||||
Lumin: 1
|
||||
Nintendo Switch: 1
|
||||
PS4: 1
|
||||
PS5: 1
|
||||
Stadia: 1
|
||||
Standalone: 1
|
||||
WebGL: 1
|
||||
Windows Store Apps: 1
|
||||
XboxOne: 1
|
||||
iPhone: 1
|
||||
tvOS: 1
|
||||
incrementalIl2cppBuild: {}
|
||||
suppressCommonWarnings: 1
|
||||
allowUnsafeCode: 0
|
||||
useDeterministicCompilation: 1
|
||||
enableRoslynAnalyzers: 1
|
||||
selectedPlatform: 0
|
||||
additionalIl2CppArgs:
|
||||
scriptingRuntimeVersion: 1
|
||||
gcIncremental: 0
|
||||
|
Loading…
Reference in New Issue
Block a user