Commit f6ed3c6f authored by cann-alberto's avatar cann-alberto
Browse files

first project commit

parent 4487b14f
fileFormatVersion: 2
guid: 41d809934003479f97e992eebb7ed6af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
// interest management component for custom solutions like
// distance based, spatial hashing, raycast based, etc.
// low level base class allows for low level spatial hashing etc., which is 3-5x faster.
using UnityEngine;
namespace Mirror
{
[DisallowMultipleComponent]
[HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")]
public abstract class InterestManagementBase : MonoBehaviour
{
// initialize NetworkServer/Client .aoi.
// previously we did this in Awake(), but that's called for disabled
// components too. if we do it OnEnable(), then it's not set for
// disabled components.
protected virtual void OnEnable()
{
// do not check if == null or error if already set.
// users may enabled/disable components randomly,
// causing this to be called multiple times.
NetworkServer.aoi = this;
NetworkClient.aoi = this;
}
[ServerCallback]
public virtual void ResetState() {}
// Callback used by the visibility system to determine if an observer
// (player) can see the NetworkIdentity. If this function returns true,
// the network connection will be added as an observer.
// conn: Network connection of a player.
// returns True if the player can see this object.
public abstract bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver);
// Callback used by the visibility system for objects on a host.
// Objects on a host (with a local client) cannot be disabled or
// destroyed when they are not visible to the local client. So this
// function is called to allow custom code to hide these objects. A
// typical implementation will disable renderer components on the
// object. This is only called on local clients on a host.
// => need the function in here and virtual so people can overwrite!
// => not everyone wants to hide renderers!
[ServerCallback]
public virtual void SetHostVisibility(NetworkIdentity identity, bool visible)
{
foreach (Renderer rend in identity.GetComponentsInChildren<Renderer>())
rend.enabled = visible;
}
/// <summary>Called on the server when a new networked object is spawned.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnSpawned(NetworkIdentity identity) {}
/// <summary>Called on the server when a networked object is destroyed.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnDestroyed(NetworkIdentity identity) {}
public abstract void Rebuild(NetworkIdentity identity, bool initialize);
/// <summary>Adds the specified connection to the observers of identity</summary>
protected void AddObserver(NetworkConnectionToClient connection, NetworkIdentity identity)
{
connection.AddToObserving(identity);
identity.observers.Add(connection.connectionId, connection);
}
/// <summary>Removes the specified connection from the observers of identity</summary>
protected void RemoveObserver(NetworkConnectionToClient connection, NetworkIdentity identity)
{
connection.RemoveFromObserving(identity, false);
identity.observers.Remove(connection.connectionId);
}
}
}
fileFormatVersion: 2
guid: 18bd2ffe65a444f3b13d59bdac7f2228
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: d2656015ded44e83a24f4c4776bafd40
timeCreated: 1687920405
\ No newline at end of file
namespace Mirror
{
public interface Capture
{
// server timestamp at time of capture.
double timestamp { get; set; }
// optional gizmo drawing for visual debugging.
// history is only known on the server, which usually doesn't render.
// showing Gizmos in the Editor is enough.
void DrawGizmo();
}
}
fileFormatVersion: 2
guid: 347e831952e943a49095cadd39a5aeb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
// HistoryBounds keeps a bounding box of all the object's bounds in the past N seconds.
// useful to decide which objects to rollback, instead of rolling back all of them.
// https://www.youtube.com/watch?v=zrIY0eIyqmI (37:00)
// standalone C# implementation to be engine (and language) agnostic.
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
// FakeByte: gather bounds in smaller buckets.
// for example, bucket(t0,t1,t2), bucket(t3,t4,t5), ...
// instead of removing old bounds t0, t1, ...
// we remove a whole bucket every 3 times: bucket(t0,t1,t2)
// and when building total bounds, we encapsulate a few larger buckets
// instead of many smaller bounds.
//
// => a bucket is encapsulate(bounds0, bounds1, bounds2) so we don't
// need a custom struct, simply reuse bounds but remember that each
// entry includes N timestamps.
//
// => note that simply reducing capture interval is _not_ the same.
// we want to capture in detail in case players run in zig-zag.
// but still grow larger buckets internally.
public class HistoryBounds
{
// mischa: use MinMaxBounds to avoid Unity Bounds.Encapsulate conversions.
readonly int boundsPerBucket;
readonly Queue<MinMaxBounds> fullBuckets;
// full bucket limit. older ones will be removed.
readonly int bucketLimit;
// bucket in progress, contains 0..boundsPerBucket bounds encapsulated.
MinMaxBounds? currentBucket;
int currentBucketSize;
// amount of total bounds, including bounds in full buckets + current
public int boundsCount { get; private set; }
// total bounds encapsulating all of the bounds history.
// totalMinMax is used for internal calculations.
// public total is used for Unity representation.
MinMaxBounds totalMinMax;
public Bounds total
{
get
{
Bounds bounds = new Bounds();
bounds.SetMinMax(totalMinMax.min, totalMinMax.max);
return bounds;
}
}
public HistoryBounds(int boundsLimit, int boundsPerBucket)
{
// bucketLimit via '/' cuts off remainder.
// that's what we want, since we always have a 'currentBucket'.
this.boundsPerBucket = boundsPerBucket;
this.bucketLimit = (boundsLimit / boundsPerBucket);
// initialize queue with maximum capacity to avoid runtime resizing
// capacity +1 because it makes the code easier if we insert first, and then remove.
fullBuckets = new Queue<MinMaxBounds>(bucketLimit + 1);
}
// insert new bounds into history. calculates new total bounds.
// Queue.Dequeue() always has the oldest bounds.
public void Insert(Bounds bounds)
{
// convert to MinMax representation for faster .Encapsulate()
MinMaxBounds minmax = new MinMaxBounds
{
min = bounds.min,
max = bounds.max
};
// initialize 'total' if not initialized yet.
// we don't want to call (0,0).Encapsulate(bounds).
if (boundsCount == 0)
{
totalMinMax = minmax;
}
// add to current bucket:
// either initialize new one, or encapsulate into existing one
if (currentBucket == null)
{
currentBucket = minmax;
}
else
{
currentBucket.Value.Encapsulate(minmax);
}
// current bucket has one more bounds.
// total bounds increased as well.
currentBucketSize += 1;
boundsCount += 1;
// always encapsulate into total immediately.
// this is free.
totalMinMax.Encapsulate(minmax);
// current bucket full?
if (currentBucketSize == boundsPerBucket)
{
// move it to full buckets
fullBuckets.Enqueue(currentBucket.Value);
currentBucket = null;
currentBucketSize = 0;
// full bucket capacity reached?
if (fullBuckets.Count > bucketLimit)
{
// remove oldest bucket
fullBuckets.Dequeue();
boundsCount -= boundsPerBucket;
// recompute total bounds
// instead of iterating N buckets, we iterate N / boundsPerBucket buckets.
// TODO technically we could reuse 'currentBucket' before clearing instead of encapsulating again
totalMinMax = minmax;
foreach (MinMaxBounds bucket in fullBuckets)
totalMinMax.Encapsulate(bucket);
}
}
}
public void Reset()
{
fullBuckets.Clear();
currentBucket = null;
currentBucketSize = 0;
boundsCount = 0;
totalMinMax = new MinMaxBounds();
}
}
}
fileFormatVersion: 2
guid: ca9ea58b98a34f73801b162cd5de724e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
// standalone lag compensation algorithm
// based on the Valve Networking Model:
// https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
using System.Collections.Generic;
namespace Mirror
{
public static class LagCompensation
{
// history is of <timestamp, capture>.
// Queue allows for fast 'remove first' and 'append last'.
//
// make sure to always insert in order.
// inserting out of order like [1,2,4,3] would cause issues.
// can't safeguard this because Queue doesn't have .Last access.
public static void Insert<T>(
Queue<KeyValuePair<double, T>> history,
int historyLimit,
double timestamp,
T capture)
where T : Capture
{
// make space according to history limit.
// do this before inserting, to avoid resizing past capacity.
if (history.Count >= historyLimit)
history.Dequeue();
// insert
history.Enqueue(new KeyValuePair<double, T>(timestamp, capture));
}
// get the two snapshots closest to a given timestamp.
// those can be used to interpolate the exact snapshot at that time.
// if timestamp is newer than the newest history entry, then we extrapolate.
// 't' will be between 1 and 2, before is second last, after is last.
// callers should Lerp(before, after, t=1.5) to extrapolate the hit.
// see comments below for extrapolation.
public static bool Sample<T>(
Queue<KeyValuePair<double, T>> history,
double timestamp, // current server time
double interval, // capture interval
out T before,
out T after,
out double t) // interpolation factor
where T : Capture
{
before = default;
after = default;
t = 0;
// can't sample an empty history
// interpolation needs at least one entry.
// extrapolation needs at least two entries.
// can't Lerp(A, A, 1.5). dist(A, A) * 1.5 is always 0.
if(history.Count < 2) {
return false;
}
// older than oldest
if (timestamp < history.Peek().Key) {
return false;
}
// iterate through the history
// TODO faster version: guess start index by how many 'intervals' we are behind.
// search around that area.
// should be O(1) most of the time, unless sampling was off.
KeyValuePair<double, T> prev = new KeyValuePair<double, T>();
KeyValuePair<double, T> prevPrev = new KeyValuePair<double, T>();
foreach(KeyValuePair<double, T> entry in history) {
// exact match?
if (timestamp == entry.Key) {
before = entry.Value;
after = entry.Value;
t = Mathd.InverseLerp(before.timestamp, after.timestamp, timestamp);
return true;
}
// did we check beyond timestamp? then return the previous two.
if (entry.Key > timestamp) {
before = prev.Value;
after = entry.Value;
t = Mathd.InverseLerp(before.timestamp, after.timestamp, timestamp);
return true;
}
// remember the last two for extrapolation.
// Queue doesn't have access to .Last.
prevPrev = prev;
prev = entry;
}
// newer than newest: extrapolate up to one interval.
// let's say we capture every 100 ms:
// 100, 200, 300, 400
// and the server is at 499
// if a client sends CmdFire at time 480, then there's no history entry.
// => adding the current entry every time would be too expensive.
// worst case we would capture at 401, 402, 403, 404, ... 100 times
// => not extrapolating isn't great. low latency clients would be
// punished by missing their targets since no entry at 'time' was found.
// => extrapolation is the best solution. make sure this works as
// expected and within limits.
if (prev.Key < timestamp && timestamp <= prev.Key + interval) {
// return the last two valid snapshots.
// can't just return (after, after) because we can't extrapolate
// if their distance is 0.
before = prevPrev.Value;
after = prev.Value;
// InverseLerp will give [after, after+interval].
// but we return [before, after, t].
// so add +1 for the distance from before->after
t = 1 + Mathd.InverseLerp(after.timestamp, after.timestamp + interval, timestamp);
return true;
}
return false;
}
// never trust the client.
// we estimate when a message was sent.
// don't trust the client to tell us the time.
// https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
// Command Execution Time = Current Server Time - Packet Latency - Client View Interpolation
// => lag compensation demo estimation is off by only ~6ms
public static double EstimateTime(double serverTime, double rtt, double bufferTime)
{
// packet latency is one trip from client to server, so rtt / 2
// client view interpolation is the snapshot interpolation buffer time
double latency = rtt / 2;
return serverTime - latency - bufferTime;
}
// convenience function to draw all history gizmos.
// this should be called from OnDrawGizmos.
public static void DrawGizmos<T>(Queue<KeyValuePair<double, T>> history)
where T : Capture
{
foreach (KeyValuePair<double, T> entry in history)
entry.Value.DrawGizmo();
}
}
}
fileFormatVersion: 2
guid: ad53cc7d12144d0ba3a8b0a4515e5d17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
// snapshot interpolation settings struct.
// can easily be exposed in Unity inspectors.
using System;
using UnityEngine;
namespace Mirror
{
// class so we can define defaults easily
[Serializable]
public class LagCompensationSettings
{
[Header("Buffering")]
[Tooltip("Keep this many past snapshots in the buffer. The larger this is, the further we can rewind into the past.\nMaximum rewind time := historyAmount * captureInterval")]
public int historyLimit = 6;
[Tooltip("Capture state every 'captureInterval' seconds. Larger values will space out the captures more, which gives a longer history but with possible gaps inbetween.\nSmaller values will have fewer gaps, with shorter history.")]
public float captureInterval = 0.100f; // 100 ms
}
}
fileFormatVersion: 2
guid: fa80bec245f94bf8a28ec78777992a1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
// Unity's Bounds struct is represented as (center, extents).
// HistoryBounds make heavy use of .Encapsulate(), which has to convert
// Unity's (center, extents) to (min, max) every time, and then convert back.
//
// It's faster to use a (min, max) representation directly instead.
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Mirror
{
public struct MinMaxBounds: IEquatable<Bounds>
{
public Vector3 min;
public Vector3 max;
// encapsulate a single point
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Encapsulate(Vector3 point)
{
min = Vector3.Min(this.min, point);
max = Vector3.Max(this.max, point);
}
// encapsulate another bounds
public void Encapsulate(MinMaxBounds bounds)
{
Encapsulate(bounds.min);
Encapsulate(bounds.max);
}
// convenience comparison with Unity's bounds, for unit tests etc.
public static bool operator ==(MinMaxBounds lhs, Bounds rhs) =>
lhs.min == rhs.min &&
lhs.max == rhs.max;
public static bool operator !=(MinMaxBounds lhs, Bounds rhs) =>
!(lhs == rhs);
public override bool Equals(object obj) =>
obj is MinMaxBounds other &&
min == other.min &&
max == other.max;
public bool Equals(MinMaxBounds other) =>
min.Equals(other.min) && max.Equals(other.max);
public bool Equals(Bounds other) =>
min.Equals(other.min) && max.Equals(other.max);
#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
public override int GetHashCode() => HashCode.Combine(min, max);
#else
public override int GetHashCode()
{
// return HashCode.Combine(min, max); without using .Combine for older Unity versions
unchecked
{
int hash = 17;
hash = hash * 23 + min.GetHashCode();
hash = hash * 23 + max.GetHashCode();
return hash;
}
}
#endif
// tostring
public override string ToString() => $"({min}, {max})";
}
}
fileFormatVersion: 2
guid: 4372b1e1a1cc4c669cc7bf0925f59d29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections.Generic;
namespace Mirror
{
// a server's connection TO a LocalClient.
// sending messages on this connection causes the client's handler function to be invoked directly
public class LocalConnectionToClient : NetworkConnectionToClient
{
internal LocalConnectionToServer connectionToServer;
// packet queue
internal readonly Queue<NetworkWriterPooled> queue = new Queue<NetworkWriterPooled>();
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.
// this way we can simulate a similar call flow as with remote clients.
// the closer we get to simulating host as remote, the better!
// both directions do this, so [Command] and [Rpc] behave the same way.
//Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}");
NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
connectionToServer.queue.Enqueue(writer);
}
// true because local connections never timeout
internal override bool IsAlive(float timeout) => true;
// don't ping host client in host mode
protected override void UpdatePing() {}
internal override void Update()
{
base.Update();
// process internal messages so they are applied at the correct time
while (queue.Count > 0)
{
// call receive on queued writer's content, return to pool
NetworkWriterPooled writer = queue.Dequeue();
ArraySegment<byte> message = writer.ToArraySegment();
// OnTransportData assumes a proper batch with timestamp etc.
// let's make a proper batch and pass it to OnTransportData.
Batcher batcher = GetBatchForChannelId(Channels.Reliable);
batcher.AddMessage(message, NetworkTime.localTime);
using (NetworkWriterPooled batchWriter = NetworkWriterPool.Get())
{
// make a batch with our local time (double precision)
if (batcher.GetBatch(batchWriter))
{
NetworkServer.OnTransportData(connectionId, batchWriter.ToArraySegment(), Channels.Reliable);
}
}
NetworkWriterPool.Return(writer);
}
}
internal void DisconnectInternal()
{
// set not ready and handle clientscene disconnect in any case
// (might be client or host mode here)
isReady = false;
RemoveFromObservingsObservers();
}
/// <summary>Disconnects this connection.</summary>
public override void Disconnect()
{
DisconnectInternal();
connectionToServer.DisconnectInternal();
}
}
}
fileFormatVersion: 2
guid: a88758df7db2043d6a9d926e0b6d4191
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
// a localClient's connection TO a server.
// send messages on this connection causes the server's handler function to be invoked directly.
public class LocalConnectionToServer : NetworkConnectionToServer
{
internal LocalConnectionToClient connectionToClient;
// packet queue
internal readonly Queue<NetworkWriterPooled> queue = new Queue<NetworkWriterPooled>();
// see caller for comments on why we need this
bool connectedEventPending;
bool disconnectedEventPending;
internal void QueueConnectedEvent() => connectedEventPending = true;
internal void QueueDisconnectedEvent() => disconnectedEventPending = true;
// Send stage two: serialized NetworkMessage as ArraySegment<byte>
internal override void Send(ArraySegment<byte> segment, int channelId = Channels.Reliable)
{
if (segment.Count == 0)
{
Debug.LogError("LocalConnection.SendBytes cannot send zero bytes");
return;
}
// instead of invoking it directly, we enqueue and process next update.
// this way we can simulate a similar call flow as with remote clients.
// the closer we get to simulating host as remote, the better!
// both directions do this, so [Command] and [Rpc] behave the same way.
//Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}");
NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
connectionToClient.queue.Enqueue(writer);
}
internal override void Update()
{
base.Update();
// should we still process a connected event?
if (connectedEventPending)
{
connectedEventPending = false;
NetworkClient.OnConnectedEvent?.Invoke();
}
// process internal messages so they are applied at the correct time
while (queue.Count > 0)
{
// call receive on queued writer's content, return to pool
NetworkWriterPooled writer = queue.Dequeue();
ArraySegment<byte> message = writer.ToArraySegment();
// OnTransportData assumes a proper batch with timestamp etc.
// let's make a proper batch and pass it to OnTransportData.
Batcher batcher = GetBatchForChannelId(Channels.Reliable);
batcher.AddMessage(message, NetworkTime.localTime);
using (NetworkWriterPooled batchWriter = NetworkWriterPool.Get())
{
// make a batch with our local time (double precision)
if (batcher.GetBatch(batchWriter))
{
NetworkClient.OnTransportData(batchWriter.ToArraySegment(), Channels.Reliable);
}
}
NetworkWriterPool.Return(writer);
}
// should we still process a disconnected event?
if (disconnectedEventPending)
{
disconnectedEventPending = false;
NetworkClient.OnDisconnectedEvent?.Invoke();
}
}
/// <summary>Disconnects this connection.</summary>
internal void DisconnectInternal()
{
// set not ready and handle clientscene disconnect in any case
// (might be client or host mode here)
// TODO remove redundant state. have one source of truth for .ready!
isReady = false;
NetworkClient.ready = false;
}
/// <summary>Disconnects this connection.</summary>
public override void Disconnect()
{
connectionToClient.DisconnectInternal();
DisconnectInternal();
// simulate what a true remote connection would do:
// first, the server should remove it:
// TODO should probably be in connectionToClient.DisconnectInternal
// because that's the NetworkServer's connection!
NetworkServer.RemoveLocalConnection();
// then call OnTransportDisconnected for proper disconnect handling,
// callbacks & cleanups.
// => otherwise OnClientDisconnected() is never called!
// => see NetworkClientTests.DisconnectCallsOnClientDisconnect_HostMode()
NetworkClient.OnTransportDisconnected();
}
// true because local connections never timeout
internal override bool IsAlive(float timeout) => true;
}
}
fileFormatVersion: 2
guid: cdfff390c3504158a269e8b8662e2a40
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
using System;
using UnityEngine;
namespace Mirror
{
// need to send time every sendInterval.
// batching automatically includes remoteTimestamp.
// all we need to do is ensure that an empty message is sent.
// and react to it.
// => we don't want to insert a snapshot on every batch.
// => do it exactly every sendInterval on every TimeSnapshotMessage.
public struct TimeSnapshotMessage : NetworkMessage {}
public struct ReadyMessage : NetworkMessage {}
public struct NotReadyMessage : NetworkMessage {}
public struct AddPlayerMessage : NetworkMessage {}
public struct SceneMessage : NetworkMessage
{
public string sceneName;
// Normal = 0, LoadAdditive = 1, UnloadAdditive = 2
public SceneOperation sceneOperation;
public bool customHandling;
}
public enum SceneOperation : byte
{
Normal,
LoadAdditive,
UnloadAdditive
}
public struct CommandMessage : NetworkMessage
{
public uint netId;
public byte componentIndex;
public ushort functionHash;
// the parameters for the Cmd function
// -> ArraySegment to avoid unnecessary allocations
public ArraySegment<byte> payload;
}
public struct RpcMessage : NetworkMessage
{
public uint netId;
public byte componentIndex;
public ushort functionHash;
// the parameters for the Cmd function
// -> ArraySegment to avoid unnecessary allocations
public ArraySegment<byte> payload;
}
public struct SpawnMessage : NetworkMessage
{
// netId of new or existing object
public uint netId;
public bool isLocalPlayer;
// Sets hasAuthority on the spawned object
public bool isOwner;
public ulong sceneId;
// If sceneId != 0 then it is used instead of assetId
public uint assetId;
// Local position
public Vector3 position;
// Local rotation
public Quaternion rotation;
// Local scale
public Vector3 scale;
// serialized component data
// ArraySegment to avoid unnecessary allocations
public ArraySegment<byte> payload;
}
public struct ChangeOwnerMessage : NetworkMessage
{
public uint netId;
public bool isOwner;
public bool isLocalPlayer;
}
public struct ObjectSpawnStartedMessage : NetworkMessage {}
public struct ObjectSpawnFinishedMessage : NetworkMessage {}
public struct ObjectDestroyMessage : NetworkMessage
{
public uint netId;
}
public struct ObjectHideMessage : NetworkMessage
{
public uint netId;
}
public struct EntityStateMessage : NetworkMessage
{
public uint netId;
// the serialized component data
// -> ArraySegment to avoid unnecessary allocations
public ArraySegment<byte> payload;
}
// whoever wants to measure rtt, sends this to the other end.
public struct NetworkPingMessage : NetworkMessage
{
// local time is used to calculate round trip time,
// and to calculate the predicted time offset.
public double localTime;
// predicted time is sent to compare the final error, for debugging only
public double predictedTimeAdjusted;
public NetworkPingMessage(double localTime, double predictedTimeAdjusted)
{
this.localTime = localTime;
this.predictedTimeAdjusted = predictedTimeAdjusted;
}
}
// the other end responds with this message.
// we can use this to calculate rtt.
public struct NetworkPongMessage : NetworkMessage
{
// local time is used to calculate round trip time.
public double localTime;
// predicted error is used to adjust the predicted timeline.
public double predictionErrorUnadjusted;
public double predictionErrorAdjusted; // for debug purposes
public NetworkPongMessage(double localTime, double predictionErrorUnadjusted, double predictionErrorAdjusted)
{
this.localTime = localTime;
this.predictionErrorUnadjusted = predictionErrorUnadjusted;
this.predictionErrorAdjusted = predictionErrorAdjusted;
}
}
}
fileFormatVersion: 2
guid: 938f6f28a6c5b48a0bbd7782342d763b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment