optimizing network stuff
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using RebootKit.Engine.Foundation;
|
||||
@@ -6,8 +7,9 @@ using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Network;
|
||||
using TriInspector;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootKit.Engine.Simulation {
|
||||
@@ -24,59 +26,54 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
public struct ActorCommand : ISerializableEntity {
|
||||
public ulong ActorID;
|
||||
public ulong ClientID;
|
||||
public ushort CommandID;
|
||||
public ushort ActorID;
|
||||
public byte CommandID;
|
||||
public NativeArray<byte> Data;
|
||||
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return sizeof(ulong) + // ActorID
|
||||
sizeof(ulong) + // ClientID
|
||||
sizeof(ushort) + // CommandID
|
||||
sizeof(ushort) + // Data length
|
||||
sizeof(byte) * Data.Length; // Data
|
||||
return sizeof(ushort) + // ActorID
|
||||
sizeof(byte) + // CommandID
|
||||
sizeof(byte) + // Data length
|
||||
sizeof(byte) * Data.Length; // Data
|
||||
}
|
||||
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write(ActorID);
|
||||
writer.Write(ClientID);
|
||||
writer.Write(CommandID);
|
||||
writer.Write((ushort) Data.Length);
|
||||
writer.Write((byte) Data.Length);
|
||||
if (Data.IsCreated) {
|
||||
writer.Write(Data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out ActorID);
|
||||
reader.Read(out ClientID);
|
||||
reader.Read(out CommandID);
|
||||
reader.Read(out ushort dataLength);
|
||||
reader.Read(out byte dataLength);
|
||||
if (dataLength > 0) {
|
||||
reader.Read(out Data, dataLength, Allocator.Temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @NOTE: ActorEvent is used to send events from the server to clients and only clients.
|
||||
// Server should not receive ActorEvents.
|
||||
public struct ActorEvent : ISerializableEntity {
|
||||
public ulong ActorID;
|
||||
public ushort EventID;
|
||||
public ushort ActorID;
|
||||
public byte EventID;
|
||||
public NativeArray<byte> Data;
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return sizeof(ulong) + // ActorID
|
||||
sizeof(ushort) + // EventID
|
||||
sizeof(ushort) + // Data length
|
||||
return sizeof(ushort) + // ActorID
|
||||
sizeof(byte) + // EventID
|
||||
sizeof(byte) + // Data length
|
||||
sizeof(byte) * Data.Length; // Data
|
||||
}
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write(ActorID);
|
||||
writer.Write(EventID);
|
||||
Assert.IsTrue(Data.Length < byte.MaxValue, "Data of ActorEvent is too large to fit in a byte.");
|
||||
|
||||
writer.Write((ushort) Data.Length);
|
||||
writer.Write((byte) Data.Length);
|
||||
if (Data.IsCreated) {
|
||||
writer.Write(Data);
|
||||
}
|
||||
@@ -85,7 +82,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out ActorID);
|
||||
reader.Read(out EventID);
|
||||
reader.Read(out ushort dataLength);
|
||||
reader.Read(out byte dataLength);
|
||||
|
||||
if (dataLength > 0) {
|
||||
reader.Read(out Data, dataLength, Allocator.Temp);
|
||||
@@ -94,13 +91,14 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum ActorPhysicsFlags : byte {
|
||||
enum ActorFlags : byte {
|
||||
None = 0,
|
||||
IsKinematic = 1 << 0,
|
||||
Hidden = 1 << 0,
|
||||
DisableColliders = 1 << 1,
|
||||
}
|
||||
|
||||
struct ActorCoreStateSnapshot : ISerializableEntity {
|
||||
public ushort ActorID;
|
||||
public DateTime Timestamp;
|
||||
|
||||
// @NOTE: Position, Rotation, and Scale are in local space.
|
||||
@@ -108,102 +106,65 @@ namespace RebootKit.Engine.Simulation {
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public bool IsHidden;
|
||||
public ActorPhysicsFlags Flags;
|
||||
public ActorFlags Flags;
|
||||
|
||||
public ulong MasterActorID;
|
||||
public ushort MasterActorID;
|
||||
public FixedString32Bytes MasterSocketName;
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write(ActorID);
|
||||
writer.Write(Timestamp.Ticks);
|
||||
writer.Write(Position);
|
||||
writer.Write(Rotation);
|
||||
writer.Write(Scale);
|
||||
writer.Write(IsHidden);
|
||||
writer.Write((byte) Flags);
|
||||
writer.Write(MasterActorID);
|
||||
writer.Write(MasterSocketName);
|
||||
}
|
||||
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out ActorID);
|
||||
reader.Read(out long ticks);
|
||||
Timestamp = new DateTime(ticks, DateTimeKind.Utc);
|
||||
reader.Read(out Position);
|
||||
reader.Read(out Rotation);
|
||||
reader.Read(out Scale);
|
||||
reader.Read(out IsHidden);
|
||||
reader.Read(out byte flagsByte);
|
||||
Flags = (ActorPhysicsFlags) flagsByte;
|
||||
Flags = (ActorFlags) flagsByte;
|
||||
reader.Read(out MasterActorID);
|
||||
reader.Read(out MasterSocketName);
|
||||
}
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return sizeof(long) + // Timestamp
|
||||
return sizeof(ushort) + // ActorID
|
||||
sizeof(long) + // Timestamp
|
||||
sizeof(float) * 3 + // Position
|
||||
sizeof(float) * 4 + // Rotation (Quaternion)
|
||||
sizeof(float) * 3 + // Scale
|
||||
sizeof(bool) + // IsHidden
|
||||
sizeof(byte) + // Flags
|
||||
sizeof(ulong) + // MasterActorID
|
||||
sizeof(ushort) + // MasterActorID
|
||||
sizeof(byte) * 32; // MasterSocketName
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Represents the synchronization mode for actor transforms (and rigidbody).
|
||||
/// @TODO: Might be a good idea to keep client-side actors rigidbody as kinematic and simulate physics only on the server.
|
||||
/// IMPORTANT:
|
||||
/// - Position, Rotation, and Scale are in local space.
|
||||
/// - Velocity and AngularVelocity are only used if UsingRigidbody is set.
|
||||
/// - When Actor is mounted to another actor, sync won't happen.
|
||||
///
|
||||
[Flags]
|
||||
public enum ActorTransformSyncMode : byte {
|
||||
None = 0,
|
||||
Position = 1 << 0,
|
||||
Rotation = 1 << 1,
|
||||
Scale = 1 << 2,
|
||||
// @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
|
||||
UsingRigidbody = 1 << 3,
|
||||
Velocity = 1 << 4, // @NOTE: Velocity is only used if UsingRigidbody is set.
|
||||
AngularVelocity = 1 << 5 // @NOTE: AngularVelocity is only used if UsingRigidbody is set.
|
||||
Scale = 1 << 2
|
||||
}
|
||||
|
||||
public struct ActorTransformSyncData : ISerializableEntity {
|
||||
public ushort ActorID;
|
||||
public ActorTransformSyncMode SyncMode;
|
||||
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Rotation;
|
||||
public Vector3 Scale;
|
||||
public Vector3 Velocity;
|
||||
public Vector3 AngularVelocity;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
||||
serializer.SerializeValue(ref SyncMode);
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
serializer.SerializeValue(ref Position);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
serializer.SerializeValue(ref Rotation);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
serializer.SerializeValue(ref Scale);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
serializer.SerializeValue(ref Velocity);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
serializer.SerializeValue(ref AngularVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write(ActorID);
|
||||
writer.Write((byte) SyncMode);
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
@@ -211,23 +172,26 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
writer.Write(Rotation);
|
||||
Rotation.x = Mathf.Repeat(Rotation.x, 360.0f);
|
||||
Rotation.y = Mathf.Repeat(Rotation.y, 360.0f);
|
||||
Rotation.z = Mathf.Repeat(Rotation.z, 360.0f);
|
||||
|
||||
ushort rotX = QuantizationUtility.FloatToUShort(Rotation.x, 0.0f, 360.0f);
|
||||
ushort rotY = QuantizationUtility.FloatToUShort(Rotation.y, 0.0f, 360.0f);
|
||||
ushort rotZ = QuantizationUtility.FloatToUShort(Rotation.z, 0.0f, 360.0f);
|
||||
|
||||
writer.Write(rotX);
|
||||
writer.Write(rotY);
|
||||
writer.Write(rotZ);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
writer.Write(Scale);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
writer.Write(Velocity);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
writer.Write(AngularVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out ActorID);
|
||||
reader.Read(out byte syncModeByte);
|
||||
SyncMode = (ActorTransformSyncMode) syncModeByte;
|
||||
|
||||
@@ -236,19 +200,17 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
reader.Read(out Rotation);
|
||||
reader.Read(out ushort rotX);
|
||||
reader.Read(out ushort rotY);
|
||||
reader.Read(out ushort rotZ);
|
||||
|
||||
Rotation.x = QuantizationUtility.UShortToFloat(rotX, 0.0f, 360.0f);
|
||||
Rotation.y = QuantizationUtility.UShortToFloat(rotY, 0.0f, 360.0f);
|
||||
Rotation.z = QuantizationUtility.UShortToFloat(rotZ, 0.0f, 360.0f);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
reader.Read(out Scale);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
reader.Read(out Velocity);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
reader.Read(out AngularVelocity);
|
||||
reader.Read(out Vector3 scale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,21 +222,13 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
size += sizeof(float) * 4; // Quaternion
|
||||
size += sizeof(ushort) * 3; // Vector3 - Euler angles
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
size += sizeof(float) * 3; // Vector3
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
size += sizeof(float) * 3; // Vector3
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
size += sizeof(float) * 3; // Vector3
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -283,7 +237,8 @@ namespace RebootKit.Engine.Simulation {
|
||||
static readonly Logger s_ActorLogger = new Logger(nameof(Actor));
|
||||
|
||||
[field: SerializeField, TriInspector.ReadOnly] public string SourceActorPath { get; internal set; } = "";
|
||||
[field: SerializeField, Unity.Collections.ReadOnly] public ulong ActorID { get; internal set; }
|
||||
[field: SerializeField, TriInspector.ReadOnly] public ulong ActorStaticID { get; internal set; }
|
||||
[field: SerializeField, TriInspector.ReadOnly] public ushort ActorID { get; internal set; }
|
||||
|
||||
[NonSerialized] internal IActorData Data;
|
||||
|
||||
@@ -302,11 +257,22 @@ namespace RebootKit.Engine.Simulation {
|
||||
[SerializeField] bool m_SetKinematicOnMount = true;
|
||||
[SerializeField] bool m_DisableCollidersOnMount = true;
|
||||
|
||||
internal ActorPhysicsFlags PhysicsFlagsBeforeMount = ActorPhysicsFlags.None;
|
||||
internal ActorPhysicsFlags PhysicsFlags = ActorPhysicsFlags.None;
|
||||
internal ActorFlags Flags = ActorFlags.None;
|
||||
|
||||
// @NOTE: Sync won't happen if actor is mounted to another actor.
|
||||
[SerializeField] internal ActorTransformSyncMode transformSyncMode = ActorTransformSyncMode.None;
|
||||
[SerializeField] internal bool syncTransform = true;
|
||||
[SerializeField] internal bool syncPosition = true;
|
||||
[SerializeField] internal bool syncRotation = true;
|
||||
[SerializeField] internal bool syncScale = false;
|
||||
|
||||
class ActorClientState {
|
||||
public ulong LastSyncTick;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
}
|
||||
|
||||
readonly Dictionary<ulong, ActorClientState> m_ClientsStates = new Dictionary<ulong, ActorClientState>();
|
||||
|
||||
[Serializable]
|
||||
public struct AttachmentSocket {
|
||||
@@ -334,8 +300,8 @@ namespace RebootKit.Engine.Simulation {
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void OnValidate() {
|
||||
if (ActorID == 0) {
|
||||
ActorID = UniqueID.NewULongFromGuid();
|
||||
if (ActorStaticID == 0) {
|
||||
ActorStaticID = UniqueID.NewULongFromGuid();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +313,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
// @MARK: Server side
|
||||
public virtual void OnServerTick(float deltaTime) { }
|
||||
protected virtual void OnActorCommandServer(ActorCommand actorCommand) { }
|
||||
protected virtual void OnActorCommandServer(ulong senderID, ActorCommand actorCommand) { }
|
||||
|
||||
// Override this method to implement client-side logic
|
||||
public virtual void OnClientTick(float deltaTime) { }
|
||||
@@ -364,12 +330,18 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
bool shouldBeActive = !hidden;
|
||||
if (gameObject.activeSelf == shouldBeActive) {
|
||||
s_ActorLogger
|
||||
.Warning($"Actor {name} (ID: {ActorID}) is already in the desired visibility state: {shouldBeActive.ToString()}");
|
||||
s_ActorLogger.Warning($"Actor {name} (ID: {ActorID}) is already in the desired visibility state: {shouldBeActive.ToString()}");
|
||||
return;
|
||||
}
|
||||
|
||||
gameObject.SetActive(shouldBeActive);
|
||||
|
||||
if (hidden) {
|
||||
Flags |= ActorFlags.Hidden;
|
||||
} else {
|
||||
Flags &= ~ActorFlags.Hidden;
|
||||
}
|
||||
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
@@ -383,16 +355,15 @@ namespace RebootKit.Engine.Simulation {
|
||||
MasterActor = actor;
|
||||
MasterSocketName = new FixedString32Bytes(slotName);
|
||||
|
||||
PhysicsFlagsBeforeMount = PhysicsFlags;
|
||||
if (m_SetKinematicOnMount) {
|
||||
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
||||
}
|
||||
|
||||
if (m_DisableCollidersOnMount) {
|
||||
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
||||
Flags |= ActorFlags.DisableColliders;
|
||||
UpdateLocalCollidersState(false);
|
||||
}
|
||||
|
||||
if (m_SetKinematicOnMount) {
|
||||
actorRigidbody.isKinematic = true;
|
||||
}
|
||||
|
||||
UpdateLocalPhysicsState(PhysicsFlags);
|
||||
UpdateMountedTransform();
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
@@ -413,51 +384,15 @@ namespace RebootKit.Engine.Simulation {
|
||||
MasterSocketName = default;
|
||||
UpdateMountedTransform();
|
||||
|
||||
PhysicsFlags = PhysicsFlagsBeforeMount;
|
||||
UpdateLocalPhysicsState(PhysicsFlags);
|
||||
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
public void SetCollidersEnabled(bool enableColliders) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can enable/disable colliders. Actor: {name} (ID: {ActorID})");
|
||||
return;
|
||||
if (m_DisableCollidersOnMount) {
|
||||
UpdateLocalCollidersState(true);
|
||||
Flags &= ~ActorFlags.DisableColliders;
|
||||
}
|
||||
|
||||
if (m_SetKinematicOnMount) {
|
||||
actorRigidbody.isKinematic = false;
|
||||
}
|
||||
|
||||
if (actorRigidbody is null) {
|
||||
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set colliders on.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableColliders) {
|
||||
PhysicsFlags &= ~ActorPhysicsFlags.DisableColliders;
|
||||
} else {
|
||||
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
||||
}
|
||||
|
||||
UpdateLocalCollidersState(enableColliders);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
public void SetKinematic(bool isKinematic) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can set kinematic state. Actor: {name} (ID: {ActorID})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (actorRigidbody is null) {
|
||||
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set kinematic state on.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKinematic) {
|
||||
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
||||
} else {
|
||||
PhysicsFlags &= ~ActorPhysicsFlags.IsKinematic;
|
||||
}
|
||||
|
||||
actorRigidbody.isKinematic = isKinematic;
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
@@ -468,14 +403,14 @@ namespace RebootKit.Engine.Simulation {
|
||||
return !gameObject.activeSelf;
|
||||
}
|
||||
|
||||
protected void SendActorCommand<TCmdData>(ushort commandID, ref TCmdData commandData)
|
||||
protected void SendActorCommand<TCmdData>(byte commandID, ref TCmdData commandData)
|
||||
where TCmdData : struct, ISerializableEntity {
|
||||
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
|
||||
|
||||
SendActorCommand(commandID, data);
|
||||
}
|
||||
|
||||
protected void SendActorCommand(ushort commandID, NativeArray<byte> data = default) {
|
||||
protected void SendActorCommand(byte commandID, NativeArray<byte> data = default) {
|
||||
if (Manager is null) {
|
||||
s_ActorLogger.Error($"Cannot send command because Manager is null for actor {name} (ID: {ActorID})");
|
||||
return;
|
||||
@@ -483,7 +418,6 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
ActorCommand command = new ActorCommand {
|
||||
ActorID = ActorID,
|
||||
ClientID = NetworkManager.Singleton.LocalClientId,
|
||||
CommandID = commandID,
|
||||
Data = data
|
||||
};
|
||||
@@ -491,19 +425,19 @@ namespace RebootKit.Engine.Simulation {
|
||||
Manager.SendActorCommand(command);
|
||||
}
|
||||
|
||||
protected void SendActorEvent<TEventData>(ushort eventID, ref TEventData eventData)
|
||||
protected void SendActorEvent<TEventData>(byte eventID, ref TEventData eventData)
|
||||
where TEventData : struct, ISerializableEntity {
|
||||
NativeArray<byte> data = DataSerializationUtils.Serialize(eventData);
|
||||
SendActorEvent(eventID, data);
|
||||
}
|
||||
|
||||
protected void SendActorEvent(ushort eventID, NativeArray<byte> data = default) {
|
||||
protected void SendActorEvent(byte eventID, NativeArray<byte> data = default) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can send actor events. Actor: {name} (ID: {ActorID})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Manager is null) {
|
||||
if (Manager == null) {
|
||||
s_ActorLogger.Error($"Cannot send event because Manager is null for actor {name} (ID: {ActorID})");
|
||||
return;
|
||||
}
|
||||
@@ -540,17 +474,22 @@ namespace RebootKit.Engine.Simulation {
|
||||
//
|
||||
// @MARK: Internal
|
||||
//
|
||||
internal void InitializeOnClient() {
|
||||
if (actorRigidbody != null) {
|
||||
actorRigidbody.isKinematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal ActorCoreStateSnapshot GetCoreStateSnapshot() {
|
||||
ActorCoreStateSnapshot snapshot = new ActorCoreStateSnapshot();
|
||||
snapshot.ActorID = ActorID;
|
||||
snapshot.Timestamp = DateTime.UtcNow;
|
||||
snapshot.Position = transform.localPosition;
|
||||
snapshot.Rotation = transform.localRotation;
|
||||
snapshot.Scale = transform.localScale;
|
||||
snapshot.Flags = Flags;
|
||||
|
||||
snapshot.IsHidden = !gameObject.activeSelf;
|
||||
snapshot.Flags = PhysicsFlags;
|
||||
|
||||
snapshot.MasterActorID = MasterActor != null ? MasterActor.ActorID : 0;
|
||||
snapshot.MasterActorID = MasterActor != null ? MasterActor.ActorID : (ushort)0;
|
||||
if (snapshot.MasterActorID != 0) {
|
||||
snapshot.MasterSocketName = MasterSocketName;
|
||||
} else {
|
||||
@@ -567,7 +506,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
return;
|
||||
}
|
||||
LastCoreStateSyncTime = snapshot.Timestamp;
|
||||
PhysicsFlags = snapshot.Flags;
|
||||
Flags = snapshot.Flags;
|
||||
|
||||
if (snapshot.MasterActorID != 0) {
|
||||
MasterActor = RR.FindSpawnedActor(snapshot.MasterActorID);
|
||||
@@ -583,69 +522,78 @@ namespace RebootKit.Engine.Simulation {
|
||||
transform.localScale = snapshot.Scale;
|
||||
}
|
||||
|
||||
if (snapshot.IsHidden) {
|
||||
if ((snapshot.Flags & ActorFlags.Hidden) != 0) {
|
||||
gameObject.SetActive(false);
|
||||
} else {
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
UpdateLocalPhysicsState(PhysicsFlags);
|
||||
|
||||
bool enableColliders = (Flags & ActorFlags.DisableColliders) == 0;
|
||||
UpdateLocalCollidersState(enableColliders);
|
||||
}
|
||||
|
||||
internal ActorTransformSyncData GetTransformSyncData() {
|
||||
ActorTransformSyncData data = new ActorTransformSyncData {
|
||||
SyncMode = transformSyncMode
|
||||
ActorClientState GetActorClientState(ulong clientID) {
|
||||
if (m_ClientsStates.TryGetValue(clientID, out ActorClientState clientState)) {
|
||||
return clientState;
|
||||
}
|
||||
|
||||
clientState = new ActorClientState {
|
||||
LastSyncTick = 0,
|
||||
Position = transform.localPosition,
|
||||
Rotation = transform.localRotation,
|
||||
Scale = transform.localScale
|
||||
};
|
||||
m_ClientsStates[clientID] = clientState;
|
||||
|
||||
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
||||
return clientState;
|
||||
}
|
||||
|
||||
internal ulong GetLastSyncTick(ulong clientID) {
|
||||
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||
return actorClientState.LastSyncTick;
|
||||
}
|
||||
|
||||
if (useRigidbody && actorRigidbody == null) {
|
||||
s_ActorLogger
|
||||
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to sync transform. Ignoring transform sync.");
|
||||
data.SyncMode = ActorTransformSyncMode.None;
|
||||
internal void UpdateClientState(ulong clientID, ulong serverTick) {
|
||||
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||
actorClientState.LastSyncTick = serverTick;
|
||||
actorClientState.Position = transform.localPosition;
|
||||
actorClientState.Rotation = transform.localRotation;
|
||||
actorClientState.Scale = transform.localScale;
|
||||
}
|
||||
|
||||
internal ActorTransformSyncData GetTransformSyncDataForClient(ulong clientID) {
|
||||
ActorTransformSyncData data = new ActorTransformSyncData {
|
||||
ActorID = ActorID,
|
||||
SyncMode = ActorTransformSyncMode.None
|
||||
};
|
||||
|
||||
if (!syncTransform || MasterActor != null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data.Position = transform.localPosition;
|
||||
data.Rotation = transform.localRotation.eulerAngles;
|
||||
data.Scale = transform.localScale;
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
if (useRigidbody) {
|
||||
data.Position = actorRigidbody.position;
|
||||
} else {
|
||||
data.Position = transform.localPosition;
|
||||
}
|
||||
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||
|
||||
if (syncPosition && math.distancesq(actorClientState.Position, transform.localPosition) > 0.01f) {
|
||||
data.SyncMode |= ActorTransformSyncMode.Position;
|
||||
}
|
||||
|
||||
if (syncRotation && Quaternion.Angle(actorClientState.Rotation, transform.localRotation) > 0.01f) {
|
||||
data.SyncMode |= ActorTransformSyncMode.Rotation;
|
||||
}
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
if (useRigidbody) {
|
||||
data.Rotation = actorRigidbody.rotation;
|
||||
} else {
|
||||
data.Rotation = transform.localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
data.Scale = transform.localScale;
|
||||
}
|
||||
|
||||
if (useRigidbody && actorRigidbody != null) {
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
data.Velocity = actorRigidbody.linearVelocity;
|
||||
}
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
data.AngularVelocity = actorRigidbody.angularVelocity;
|
||||
}
|
||||
if (syncScale && math.distancesq(actorClientState.Scale, transform.localScale) > 0.01f) {
|
||||
data.SyncMode |= ActorTransformSyncMode.Scale;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
internal void RestoreTransformState(ActorTransformSyncData data) {
|
||||
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
||||
if (useRigidbody && actorRigidbody == null) {
|
||||
s_ActorLogger
|
||||
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to restore transform state. Ignoring transform sync.");
|
||||
return;
|
||||
}
|
||||
bool useRigidbody = actorRigidbody != null;
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
if (useRigidbody) {
|
||||
@@ -657,38 +605,15 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
if (useRigidbody) {
|
||||
actorRigidbody.rotation = data.Rotation;
|
||||
actorRigidbody.rotation = Quaternion.Euler(data.Rotation);
|
||||
} else {
|
||||
transform.localRotation = data.Rotation;
|
||||
transform.localRotation = Quaternion.Euler(data.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||
transform.localScale = data.Scale;
|
||||
}
|
||||
|
||||
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
||||
actorRigidbody.linearVelocity = data.Velocity;
|
||||
}
|
||||
|
||||
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
||||
actorRigidbody.angularVelocity = data.AngularVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateLocalPhysicsState(ActorPhysicsFlags flags) {
|
||||
if (actorRigidbody == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((flags & ActorPhysicsFlags.IsKinematic) != 0) {
|
||||
actorRigidbody.isKinematic = true;
|
||||
} else {
|
||||
actorRigidbody.isKinematic = false;
|
||||
}
|
||||
|
||||
bool enableColliders = (flags & ActorPhysicsFlags.DisableColliders) == 0;
|
||||
UpdateLocalCollidersState(enableColliders);
|
||||
}
|
||||
|
||||
void UpdateLocalCollidersState(bool enable) {
|
||||
@@ -720,7 +645,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
return CreateActorData();
|
||||
}
|
||||
|
||||
internal void HandleActorCommand(ActorCommand actorCommand) {
|
||||
internal void HandleActorCommand(ulong senderID, ActorCommand actorCommand) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
|
||||
return;
|
||||
@@ -737,7 +662,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
return;
|
||||
}
|
||||
|
||||
OnActorCommandServer(actorCommand);
|
||||
OnActorCommandServer(senderID, actorCommand);
|
||||
}
|
||||
|
||||
internal void HandleActorEvent(ActorEvent actorEvent) {
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using NUnit.Framework;
|
||||
using System.Linq;
|
||||
using RebootKit.Engine.Extensions;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Network;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.Assertions;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RebootKit.Engine.Simulation {
|
||||
// @TODO:
|
||||
// - Actors States might be packed into chunks to reduce the number of RPCs sent.
|
||||
// - Release addressables when they are no longer needed.
|
||||
public class ActorsManager : NetworkBehaviour {
|
||||
public class ActorsManager : IDisposable {
|
||||
static readonly Logger s_Logger = new Logger(nameof(ActorsManager));
|
||||
|
||||
readonly NetworkSystem m_Network;
|
||||
|
||||
readonly List<Actor> m_InSceneActors = new List<Actor>();
|
||||
readonly List<Actor> m_SpawnedActors = new List<Actor>();
|
||||
|
||||
public int InSceneActorsCount { get { return m_InSceneActors.Count; } }
|
||||
public int SpawnedActorsCount { get { return m_SpawnedActors.Count; } }
|
||||
public int TotalActorsCount { get { return m_InSceneActors.Count + m_SpawnedActors.Count; } }
|
||||
ushort m_ActorIDCounter = 0;
|
||||
|
||||
//
|
||||
// @MARK: NetworkBehaviour callbacks
|
||||
//
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
public ushort InSceneActorsCount { get { return (ushort) m_InSceneActors.Count; } }
|
||||
public ushort SpawnedActorsCount { get { return (ushort) m_SpawnedActors.Count; } }
|
||||
public int TotalActorsCount { get { return InSceneActorsCount + SpawnedActorsCount; } }
|
||||
|
||||
public ActorsManager(NetworkSystem networkSystem) {
|
||||
m_Network = networkSystem;
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn() {
|
||||
base.OnNetworkDespawn();
|
||||
}
|
||||
public void Dispose() { }
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
// @MARK: Update
|
||||
//
|
||||
void Update() {
|
||||
public void Tick(float deltaTime) {
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
actor.OnClientTick(Time.deltaTime);
|
||||
actor.OnClientTick(deltaTime);
|
||||
}
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
actor.OnClientTick(Time.deltaTime);
|
||||
actor.OnClientTick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
// @MARK: Server-side logic
|
||||
//
|
||||
public void ServerTick(float dt) {
|
||||
if (!IsServer) {
|
||||
if (!RR.IsServer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,23 +69,50 @@ namespace RebootKit.Engine.Simulation {
|
||||
actor.IsDataDirty = false;
|
||||
|
||||
if (actor.Data.GetMaxBytes() > 0) {
|
||||
RR.NetworkSystemInstance.WriteActorState(NetworkPacketTarget.AllClients(), actor.ActorID, actor.Data);
|
||||
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||
if (client.IsServer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Network.WriteActorState(client.ClientID, actor.ActorID, actor.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actor.IsCoreStateDirty) {
|
||||
actor.IsCoreStateDirty = false;
|
||||
|
||||
RR.NetworkSystemInstance.WriteActorCoreState(NetworkPacketTarget.AllClients(),
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot());
|
||||
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||
if (client.IsServer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Network.WriteActorCoreState(client.ClientID, actor.GetCoreStateSnapshot());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||
if (client.IsServer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actor.transformSyncMode != ActorTransformSyncMode.None && actor.MasterActor == null) {
|
||||
ActorTransformSyncData syncData = actor.GetTransformSyncData();
|
||||
RR.NetworkSystemInstance.WriteActorTransformState(NetworkPacketTarget.AllClients(),
|
||||
actor.ActorID,
|
||||
syncData);
|
||||
foreach (Actor actor in actors.OrderBy(t => t.GetLastSyncTick(client.ClientID))) {
|
||||
if (!actor.syncTransform || actor.MasterActor != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ActorTransformSyncData syncData = actor.GetTransformSyncDataForClient(client.ClientID);
|
||||
if (syncData.SyncMode == ActorTransformSyncMode.None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_Network.WriteActorTransformState(client.ClientID, syncData)) {
|
||||
actor.UpdateClientState(client.ClientID, RR.Network.TickCount);
|
||||
} else {
|
||||
// @NOTE: We ran out of space in the packet
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +121,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
// @MARK: Server API
|
||||
//
|
||||
public Actor SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
||||
if (!IsServer) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can spawn actors.");
|
||||
return null;
|
||||
}
|
||||
@@ -105,13 +131,16 @@ namespace RebootKit.Engine.Simulation {
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong actorID = UniqueID.NewULongFromGuid();
|
||||
if (!TryGenerateNextActorID(out ushort actorID)) {
|
||||
s_Logger.Error("Cannot spawn actor: Failed to generate next actor ID.");
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject actorObject = assetReference.InstantiateAsync(position, rotation).WaitForCompletion();
|
||||
Actor actor = actorObject.GetComponent<Actor>();
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||
Destroy(actorObject);
|
||||
Object.Destroy(actorObject);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -119,36 +148,44 @@ namespace RebootKit.Engine.Simulation {
|
||||
actor.SourceActorPath = assetReference.AssetGUID;
|
||||
actor.ActorID = actorID;
|
||||
actor.Data = actor.InternalCreateActorData();
|
||||
|
||||
m_SpawnedActors.Add(actor);
|
||||
|
||||
RR.NetworkSystemInstance.WriteSpawnActor(NetworkPacketTarget.AllClients(),
|
||||
assetReference.AssetGUID,
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||
if (client.IsServer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_Network.SendSpawnActor(client.ClientID,
|
||||
assetReference.AssetGUID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
}
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
public void CleanUp() {
|
||||
if (IsServer) {
|
||||
CleanUpRpc();
|
||||
}
|
||||
|
||||
m_InSceneActors.Clear();
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
if (actor.OrNull() != null) {
|
||||
Destroy(actor.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
m_SpawnedActors.Clear();
|
||||
bool TryGenerateNextActorID(out ushort actorID) {
|
||||
m_ActorIDCounter += 1;
|
||||
actorID = m_ActorIDCounter;
|
||||
return true;
|
||||
}
|
||||
|
||||
[Rpc(SendTo.NotMe)]
|
||||
void CleanUpRpc() {
|
||||
CleanUp();
|
||||
internal void AssignActorsIDs() {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Info("Only the server can assign actors IDs.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_ActorIDCounter = 0;
|
||||
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
if (!TryGenerateNextActorID(out ushort actorID)) {
|
||||
s_Logger.Error("Failed to generate actor ID. Probably reached the limit of 65535 actors.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.ActorID = actorID;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -164,7 +201,17 @@ namespace RebootKit.Engine.Simulation {
|
||||
m_InSceneActors.Add(actor);
|
||||
}
|
||||
|
||||
public Actor FindActorByID(ulong actorID) {
|
||||
Actor FindInSceneActorWithStaticID(ulong staticID) {
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
if (actor.ActorStaticID == staticID) {
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Actor FindActorByID(ushort actorID) {
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
if (actor.ActorID == actorID) {
|
||||
return actor;
|
||||
@@ -180,186 +227,112 @@ namespace RebootKit.Engine.Simulation {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Initial synchronization
|
||||
//
|
||||
internal void InitializeActorsForClient(ulong clientID) {
|
||||
if (RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
clientState.SyncState = NetworkClientSyncState.PreparingForActorsSync;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
PrepareClientForActorsSyncRpc(RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp));
|
||||
} else {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot synchronize actors.");
|
||||
}
|
||||
}
|
||||
public void CleanUp() {
|
||||
m_InSceneActors.Clear();
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void PrepareClientForActorsSyncRpc(RpcParams rpcParams) {
|
||||
foreach (Actor spawnedActor in m_SpawnedActors) {
|
||||
Destroy(spawnedActor.gameObject);
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
if (actor.OrNull() != null) {
|
||||
Object.Destroy(actor.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
m_SpawnedActors.Clear();
|
||||
|
||||
ClientIsReadyForActorsSyncRpc();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
void ClientIsReadyForActorsSyncRpc(RpcParams rpcParams = default) {
|
||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as ready for actors sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
clientState.SyncState = NetworkClientSyncState.SyncingActors;
|
||||
clientState.ActorsSyncPacketsLeft = m_InSceneActors.Count;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
s_Logger.Info($"Starting actor synchronization for client {clientID}.\n" +
|
||||
$"InScene Actors to sync: {m_InSceneActors.Count}\n" +
|
||||
$"Actors to spawn: {m_SpawnedActors.Count}");
|
||||
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
RR.NetworkSystemInstance.WriteActorSynchronize(NetworkPacketTarget.Single(clientID),
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
}
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
s_Logger.Info("Spawning actor for client synchronization: " + actor.SourceActorPath);
|
||||
RR.NetworkSystemInstance.WriteSpawnActor(NetworkPacketTarget.Single(clientID),
|
||||
actor.SourceActorPath,
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
||||
void ClientSynchronizedActorRpc(RpcParams rpcParams = default) {
|
||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
||||
|
||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as synchronized.");
|
||||
return;
|
||||
}
|
||||
|
||||
clientState.ActorsSyncPacketsLeft--;
|
||||
s_Logger.Info($"Synchronized actor for client {clientID}. Packets left: {clientState.ActorsSyncPacketsLeft}");
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
if (clientState.ActorsSyncPacketsLeft == 0) {
|
||||
RR.NetworkSystemInstance.ClientSynchronizedActors(clientID);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// @MARK: Network Data Handling
|
||||
///
|
||||
internal void OnReceivedEntity(NetworkDataHeader header, NativeArray<byte> data) {
|
||||
if (header.Type == NetworkDataType.ActorCoreState) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for core state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
||||
coreState.Deserialize(reader);
|
||||
actor.RestoreCoreState(coreState);
|
||||
} else if (header.Type == NetworkDataType.ActorTransformSync) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for transform state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
ActorTransformSyncData transformSyncData = new ActorTransformSyncData();
|
||||
transformSyncData.Deserialize(reader);
|
||||
actor.RestoreTransformState(transformSyncData);
|
||||
} else if (header.Type == NetworkDataType.ActorState) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
DataSerializationUtils.Deserialize(data, ref actor.Data);
|
||||
} else if (header.Type == NetworkDataType.ActorEvent) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for event handling.");
|
||||
return;
|
||||
}
|
||||
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
ActorEvent actorEvent = new ActorEvent();
|
||||
actorEvent.Deserialize(reader);
|
||||
|
||||
actor.HandleActorEvent(actorEvent);
|
||||
} else if (header.Type == NetworkDataType.ActorCommand) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error($"Received ActorCommand on client, but this should only be handled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for command handling.");
|
||||
return;
|
||||
}
|
||||
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
ActorCommand actorCommand = new ActorCommand();
|
||||
actorCommand.Deserialize(reader);
|
||||
|
||||
actor.HandleActorCommand(actorCommand);
|
||||
} else if (header.Type == NetworkDataType.SynchronizeActor) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for synchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
|
||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
||||
coreState.Deserialize(reader);
|
||||
|
||||
reader.Read(out ushort actorDataSize);
|
||||
reader.Read(out NativeArray<byte> stateData, actorDataSize);
|
||||
|
||||
actor.RestoreCoreState(coreState);
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
|
||||
ClientSynchronizedActorRpc();
|
||||
} else if (header.Type == NetworkDataType.SpawnActor) {
|
||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||
|
||||
reader.Read(out FixedString64Bytes value);
|
||||
string guid = value.ToString();
|
||||
|
||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
||||
coreState.Deserialize(reader);
|
||||
|
||||
reader.Read(out ushort actorDataSize);
|
||||
reader.Read(out NativeArray<byte> stateData, actorDataSize);
|
||||
|
||||
SpawnLocalActor(guid,
|
||||
header.ActorID,
|
||||
coreState,
|
||||
stateData);
|
||||
///
|
||||
internal void ProcessActorEvent(in ActorEvent actorEvent) {
|
||||
Actor actor = FindActorByID(actorEvent.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {actorEvent.ActorID} for event handling.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.HandleActorEvent(actorEvent);
|
||||
}
|
||||
|
||||
internal void ProcessActorCommand(ulong senderID, in ActorCommand actorCommand) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Received ActorCommand on client, but this should only be handled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = FindActorByID(actorCommand.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {actorCommand.ActorID} for command handling.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.HandleActorCommand(senderID, actorCommand);
|
||||
}
|
||||
|
||||
internal void ProcessActorCoreState(in ActorCoreStateSnapshot coreState) {
|
||||
if (RR.IsServer()) {
|
||||
s_Logger.Error("Received ActorCoreState on server, but this should only be handled on the client.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = FindActorByID(coreState.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {coreState.ActorID} for core state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(coreState);
|
||||
}
|
||||
|
||||
internal void ProcessActorState(ushort actorID, NativeSlice<byte> stateData) {
|
||||
if (RR.IsServer()) {
|
||||
s_Logger.Error("Received ActorState on server, but this should only be handled on the client.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {actorID} for state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
}
|
||||
|
||||
internal void ProcessActorTransformState(in ActorTransformSyncData transformData) {
|
||||
if (RR.IsServer()) {
|
||||
s_Logger.Error("Received ActorTransformSync on server, but this should only be handled on the client.");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = FindActorByID(transformData.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {transformData.ActorID} for transform state update.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreTransformState(transformData);
|
||||
}
|
||||
|
||||
internal void ProcessSpawnActor(in FixedString64Bytes assetGUID,
|
||||
in ActorCoreStateSnapshot coreState,
|
||||
in NativeSlice<byte> stateData) {
|
||||
if (RR.IsServer()) {
|
||||
s_Logger.Error("Received SpawnActor on server, but this should only be handled on the client.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (assetGUID.IsEmpty) {
|
||||
s_Logger.Error("Received SpawnActor with empty asset GUID, this should not happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnLocalActor(assetGUID.ToString(),
|
||||
coreState,
|
||||
stateData);
|
||||
}
|
||||
|
||||
void SpawnLocalActor(string guid,
|
||||
ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> stateData) {
|
||||
NativeSlice<byte> stateData) {
|
||||
AssetReferenceGameObject assetReference = new AssetReferenceGameObject(guid);
|
||||
if (!assetReference.RuntimeKeyIsValid()) {
|
||||
s_Logger.Error($"Invalid asset reference for actor with GUID {guid}");
|
||||
@@ -377,17 +350,24 @@ namespace RebootKit.Engine.Simulation {
|
||||
Actor actor = actorObject.GetComponent<Actor>();
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||
Destroy(actorObject);
|
||||
Object.Destroy(actorObject);
|
||||
return;
|
||||
}
|
||||
|
||||
actor.Manager = this;
|
||||
actor.SourceActorPath = guid;
|
||||
actor.ActorID = actorID;
|
||||
actor.ActorID = coreStateSnapshot.ActorID;
|
||||
actor.Data = actor.InternalCreateActorData();
|
||||
|
||||
if (!RR.IsServer()) {
|
||||
actor.InitializeOnClient();
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
if (stateData.Length > 0) {
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
}
|
||||
|
||||
m_SpawnedActors.Add(actor);
|
||||
}
|
||||
|
||||
@@ -399,12 +379,91 @@ namespace RebootKit.Engine.Simulation {
|
||||
s_Logger.Error("Only the server can send actor events.");
|
||||
return;
|
||||
}
|
||||
|
||||
RR.NetworkSystemInstance.WriteActorEvent(NetworkPacketTarget.AllClients(), actorEvent);
|
||||
|
||||
foreach (ulong clientID in m_Network.Clients.Keys) {
|
||||
m_Network.WriteActorEvent(clientID, actorEvent);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendActorCommand(ActorCommand actorCommand) {
|
||||
RR.NetworkSystemInstance.WriteActorCommand(actorCommand);
|
||||
m_Network.WriteActorCommand(actorCommand);
|
||||
}
|
||||
|
||||
///
|
||||
/// @MARK: Synchronization Helpers
|
||||
///
|
||||
|
||||
// @TODO: We might want to sending this in chunks because it probably wont fit into a single packet in real game.
|
||||
internal void WriteInSceneActorsStates(NetworkBufferWriter writer) {
|
||||
writer.Write((ushort) InSceneActorsCount);
|
||||
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
writer.Write(actor.ActorStaticID);
|
||||
writer.Write(actor.ActorID);
|
||||
actor.GetCoreStateSnapshot().Serialize(writer);
|
||||
|
||||
writer.Write((byte) actor.Data.GetMaxBytes());
|
||||
actor.Data.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ReadInSceneActorsStates(NetworkBufferReader reader) {
|
||||
reader.Read(out ushort count);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
reader.Read(out ulong actorStaticID);
|
||||
reader.Read(out ushort actorID);
|
||||
|
||||
s_Logger.Info($"Reading actor with StaticID {actorStaticID} and ID {actorID} during synchronization.");
|
||||
|
||||
Actor actor = FindInSceneActorWithStaticID(actorStaticID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {actorID} during synchronization.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
||||
coreState.Deserialize(reader);
|
||||
|
||||
reader.Read(out byte actorDataSize);
|
||||
reader.Read(out NativeSlice<byte> stateData, actorDataSize);
|
||||
|
||||
actor.RestoreCoreState(coreState);
|
||||
if (stateData.Length > 0) {
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
}
|
||||
|
||||
s_Logger.Info($"Assigning StaticID {actorStaticID} and ID {actorID}");
|
||||
actor.ActorID = actorID;
|
||||
s_Logger.Info("Actor id set to " + actor.ActorID);
|
||||
}
|
||||
|
||||
foreach (Actor inSceneActor in m_InSceneActors) {
|
||||
s_Logger.Info($"InSceneActor: StaticID={inSceneActor.ActorStaticID}, ID={inSceneActor.ActorID}, Path={inSceneActor.SourceActorPath}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void SpawnDynamicActorsForClient(ulong clientID) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can spawn dynamic actors for clients.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
if (actor.OrNull() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ActorCoreStateSnapshot coreState = actor.GetCoreStateSnapshot();
|
||||
m_Network.SendSpawnActor(clientID,
|
||||
actor.SourceActorPath,
|
||||
coreState,
|
||||
actor.Data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,9 +3,7 @@ using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||
@@ -59,7 +57,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
public void Dispose() {
|
||||
Unload();
|
||||
}
|
||||
|
||||
|
||||
public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) {
|
||||
await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken);
|
||||
|
||||
@@ -73,9 +71,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
await m_SceneInstance.Result.ActivateAsync();
|
||||
SceneManager.SetActiveScene(m_SceneInstance.Result.Scene);
|
||||
|
||||
// await UniTask.WaitWhile(() => RR.CoreNetworkGameSystemsInstance is null, cancellationToken: cancellationToken);
|
||||
|
||||
|
||||
foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) {
|
||||
if (root.TryGetComponent(out IWorldContext worldContext)) {
|
||||
Assert.IsNull(Context,
|
||||
@@ -84,7 +80,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
foreach (Actor actor in root.GetComponentsInChildren<Actor>()) {
|
||||
RR.NetworkSystemInstance.Actors.RegisterInSceneActor(actor);
|
||||
RR.Network.Actors.RegisterInSceneActor(actor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +92,8 @@ namespace RebootKit.Engine.Simulation {
|
||||
return;
|
||||
}
|
||||
|
||||
if (RR.NetworkSystemInstance != null) {
|
||||
RR.NetworkSystemInstance.Actors.CleanUp();
|
||||
if (RR.Network != null) {
|
||||
RR.Network.Actors.CleanUp();
|
||||
}
|
||||
|
||||
if (m_SceneInstance.IsValid()) {
|
||||
|
||||
Reference in New Issue
Block a user