multiplayer refactor
This commit is contained in:
@@ -3,66 +3,23 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Network;
|
||||
using TriInspector;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootKit.Engine.Simulation {
|
||||
public interface ISerializableEntity {
|
||||
void Serialize(FastBufferWriter writer);
|
||||
void Deserialize(FastBufferReader reader);
|
||||
|
||||
// @NOTE: -1 means use the default size and have hope it will fit.
|
||||
int MinimumSizeInBytes() { return -1; }
|
||||
}
|
||||
|
||||
public interface IActorData : ISerializableEntity { }
|
||||
|
||||
public class NoActorData : IActorData {
|
||||
public void Serialize(FastBufferWriter writer) { }
|
||||
public void Serialize(NetworkBufferWriter writer) { }
|
||||
|
||||
public void Deserialize(FastBufferReader reader) { }
|
||||
}
|
||||
public void Deserialize(NetworkBufferReader reader) { }
|
||||
|
||||
public static class DataSerializationUtils {
|
||||
public const int k_DefaultMessageSize = 256;
|
||||
|
||||
public static NativeArray<byte> Serialize<TEntity>(TEntity entity,
|
||||
Allocator allocator = Allocator.Temp)
|
||||
where TEntity : ISerializableEntity {
|
||||
int size = entity.MinimumSizeInBytes();
|
||||
if (size < 0) {
|
||||
size = k_DefaultMessageSize;
|
||||
}
|
||||
|
||||
using FastBufferWriter writer = new FastBufferWriter(size, allocator);
|
||||
if (writer.TryBeginWrite(size)) {
|
||||
entity.Serialize(writer);
|
||||
|
||||
int length = writer.Length;
|
||||
NativeArray<byte> data = new NativeArray<byte>(length, allocator);
|
||||
|
||||
unsafe {
|
||||
void* dst = data.GetUnsafePtr();
|
||||
void* src = writer.GetUnsafePtr();
|
||||
Buffer.MemoryCopy(src, dst, length, length);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static void Deserialize<TEntity>(NativeArray<byte> data, ref TEntity entity)
|
||||
where TEntity : ISerializableEntity {
|
||||
using FastBufferReader reader = new FastBufferReader(data, Allocator.Temp);
|
||||
if (reader.TryBeginRead(data.Length)) {
|
||||
entity.Deserialize(reader);
|
||||
}
|
||||
public int GetMaxBytes() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +52,8 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
}
|
||||
|
||||
// @NOTE: ActorEvent is used to send events from the server to clients and only clients.
|
||||
// Server should not receive ActorEvents.
|
||||
public struct ActorEvent : INetworkSerializable {
|
||||
public ulong ActorID;
|
||||
public ulong ClientID;
|
||||
@@ -131,7 +90,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
DisableColliders = 1 << 1,
|
||||
}
|
||||
|
||||
struct ActorCoreStateSnapshot : INetworkSerializable {
|
||||
struct ActorCoreStateSnapshot : ISerializableEntity {
|
||||
public DateTime Timestamp;
|
||||
|
||||
// @NOTE: Position, Rotation, and Scale are in local space.
|
||||
@@ -145,15 +104,39 @@ namespace RebootKit.Engine.Simulation {
|
||||
public ulong MasterActorID;
|
||||
public FixedString32Bytes MasterSocketName;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
||||
serializer.SerializeValue(ref Timestamp);
|
||||
serializer.SerializeValue(ref Position);
|
||||
serializer.SerializeValue(ref Rotation);
|
||||
serializer.SerializeValue(ref Scale);
|
||||
serializer.SerializeValue(ref IsHidden);
|
||||
serializer.SerializeValue(ref Flags);
|
||||
serializer.SerializeValue(ref MasterActorID);
|
||||
serializer.SerializeValue(ref MasterSocketName);
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
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 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;
|
||||
reader.Read(out MasterActorID);
|
||||
reader.Read(out MasterSocketName);
|
||||
}
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return 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(byte) * 32; // MasterSocketName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,19 +148,19 @@ namespace RebootKit.Engine.Simulation {
|
||||
/// - 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,
|
||||
UsingRigidbody = 1 << 3, // @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
|
||||
// @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.
|
||||
}
|
||||
|
||||
public struct ActorTransformSyncData : INetworkSerializable {
|
||||
public struct ActorTransformSyncData : ISerializableEntity {
|
||||
public ActorTransformSyncMode SyncMode;
|
||||
|
||||
public Vector3 Position;
|
||||
@@ -209,6 +192,81 @@ namespace RebootKit.Engine.Simulation {
|
||||
serializer.SerializeValue(ref AngularVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write((byte) SyncMode);
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
writer.Write(Position);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
writer.Write(Rotation);
|
||||
}
|
||||
|
||||
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 byte syncModeByte);
|
||||
SyncMode = (ActorTransformSyncMode) syncModeByte;
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
reader.Read(out Position);
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
reader.Read(out Rotation);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetMaxBytes() {
|
||||
int size = sizeof(byte); // SyncMode
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||
size += sizeof(float) * 3; // Vector3
|
||||
}
|
||||
|
||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||
size += sizeof(float) * 4; // Quaternion
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Actor : MonoBehaviour {
|
||||
@@ -256,11 +314,12 @@ namespace RebootKit.Engine.Simulation {
|
||||
internal Actor MasterActor;
|
||||
internal FixedString32Bytes MasterSocketName;
|
||||
|
||||
internal bool IsCoreStateDirty;
|
||||
public bool IsDataDirty { get; protected internal set; }
|
||||
|
||||
internal ActorsManager Manager;
|
||||
internal DateTime LastCoreStateSyncTime = DateTime.MinValue;
|
||||
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
@@ -269,11 +328,11 @@ namespace RebootKit.Engine.Simulation {
|
||||
ActorID = UniqueID.NewULongFromGuid();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// @MARK: Callbacks to override in derived classes
|
||||
//
|
||||
|
||||
|
||||
protected abstract IActorData CreateActorData();
|
||||
|
||||
// @MARK: Server side
|
||||
@@ -302,7 +361,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
gameObject.SetActive(shouldBeActive);
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
public void MountTo(Actor actor, string slotName) {
|
||||
@@ -326,7 +385,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
UpdateLocalPhysicsState(PhysicsFlags);
|
||||
UpdateMountedTransform();
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +407,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
PhysicsFlags = PhysicsFlagsBeforeMount;
|
||||
UpdateLocalPhysicsState(PhysicsFlags);
|
||||
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
public void SetCollidersEnabled(bool enableColliders) {
|
||||
@@ -369,7 +428,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
UpdateLocalCollidersState(enableColliders);
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
public void SetKinematic(bool isKinematic) {
|
||||
@@ -390,7 +449,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
actorRigidbody.isKinematic = isKinematic;
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
IsCoreStateDirty = true;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -403,6 +462,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
protected void SendActorCommand<TCmdData>(ushort commandID, ref TCmdData commandData)
|
||||
where TCmdData : struct, ISerializableEntity {
|
||||
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
|
||||
|
||||
SendActorCommand(commandID, data);
|
||||
}
|
||||
|
||||
@@ -651,11 +711,11 @@ namespace RebootKit.Engine.Simulation {
|
||||
internal IActorData InternalCreateActorData() {
|
||||
return CreateActorData();
|
||||
}
|
||||
|
||||
|
||||
internal void InitialSyncFinished() {
|
||||
OnClientFinishedInitialSync();
|
||||
}
|
||||
|
||||
|
||||
internal void HandleActorCommand(ActorCommand actorCommand) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using NUnit.Framework;
|
||||
using RebootKit.Engine.Extensions;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Network;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
@@ -27,12 +31,10 @@ namespace RebootKit.Engine.Simulation {
|
||||
//
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
RR.ServerTick += OnServerTick;
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn() {
|
||||
base.OnNetworkDespawn();
|
||||
RR.ServerTick -= OnServerTick;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -51,13 +53,11 @@ namespace RebootKit.Engine.Simulation {
|
||||
//
|
||||
// @MARK: Server-side logic
|
||||
//
|
||||
void OnServerTick(ulong tick) {
|
||||
public void ServerTick(float dt) {
|
||||
if (!IsServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
float dt = 1.0f / RR.TickRate.IndexValue;
|
||||
|
||||
TickActorsList(m_InSceneActors, dt);
|
||||
TickActorsList(m_SpawnedActors, dt);
|
||||
}
|
||||
@@ -69,78 +69,28 @@ namespace RebootKit.Engine.Simulation {
|
||||
if (actor.IsDataDirty) {
|
||||
actor.IsDataDirty = false;
|
||||
|
||||
NativeArray<byte> data = SerializeActorState(actor);
|
||||
if (data.IsCreated) {
|
||||
SendActorStateToClients(actor.ActorID, data);
|
||||
} else {
|
||||
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
||||
if (actor.Data.GetMaxBytes() > 0) {
|
||||
RR.NetworkSystemInstance.WriteActorState(NetworkPacketTarget.AllClients(), actor.ActorID, actor.Data);
|
||||
}
|
||||
}
|
||||
|
||||
if (actor.IsCoreStateDirty) {
|
||||
actor.IsCoreStateDirty = false;
|
||||
|
||||
RR.NetworkSystemInstance.WriteActorCoreState(NetworkPacketTarget.AllClients(),
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot());
|
||||
}
|
||||
|
||||
if (actor.transformSyncMode != ActorTransformSyncMode.None && actor.MasterActor == null) {
|
||||
ActorTransformSyncData syncData = actor.GetTransformSyncData();
|
||||
|
||||
foreach ((ulong _, NetworkClientState state) in RR.NetworkSystemInstance.Clients) {
|
||||
if (state.IsReady) {
|
||||
SynchronizeActorTransformStateRpc(actor.ActorID, syncData, RpcTarget.NotMe);
|
||||
}
|
||||
}
|
||||
RR.NetworkSystemInstance.WriteActorTransformState(NetworkPacketTarget.AllClients(),
|
||||
actor.ActorID,
|
||||
syncData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SynchronizeActorCoreStateWithOther(Actor actor) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can synchronize actor core states.");
|
||||
return;
|
||||
}
|
||||
|
||||
SynchronizeCoreActorStateRpc(actor.ActorID, actor.GetCoreStateSnapshot(), RpcTarget.NotMe);
|
||||
}
|
||||
|
||||
void SendActorStateToClients(ulong actorID, NativeArray<byte> data) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can synchronize actor states with clients.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((ulong clientID, NetworkClientState state) in RR.NetworkSystemInstance.Clients) {
|
||||
if (state.IsReady) {
|
||||
SynchronizeActorStateRpc(actorID, data, RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Unreliable)]
|
||||
void SynchronizeActorStateRpc(ulong actorID, NativeArray<byte> data, RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Synchronizing actor state for {actor.name} with ID {actorID}");
|
||||
DeserializeActorState(actor, data);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Unreliable)]
|
||||
void SynchronizeActorTransformStateRpc(ulong actorID, ActorTransformSyncData syncData, RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"Actor with ID {actorID} not found for transform synchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreTransformState(syncData);
|
||||
}
|
||||
|
||||
NativeArray<byte> SerializeActorState(Actor actor) {
|
||||
return DataSerializationUtils.Serialize(actor.Data);
|
||||
}
|
||||
|
||||
void DeserializeActorState(Actor actor, NativeArray<byte> data) {
|
||||
DataSerializationUtils.Deserialize(data, ref actor.Data);
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Server API
|
||||
//
|
||||
@@ -172,73 +122,14 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
m_SpawnedActors.Add(actor);
|
||||
|
||||
NativeArray<byte> stateData = SerializeActorState(actor);
|
||||
SpawnActorRpc(assetReference.AssetGUID,
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
stateData,
|
||||
RpcTarget.NotMe);
|
||||
RR.NetworkSystemInstance.WriteSpawnActor(NetworkPacketTarget.AllClients(),
|
||||
assetReference.AssetGUID,
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
return actor;
|
||||
}
|
||||
|
||||
// @NOTE: This RPC is used to spawn actors on clients.
|
||||
[Rpc(SendTo.SpecifiedInParams)]
|
||||
void SpawnActorRpc(string guid,
|
||||
ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> stateData,
|
||||
RpcParams rpcParams) {
|
||||
AssetReferenceGameObject assetReference = new AssetReferenceGameObject(guid);
|
||||
if (!assetReference.RuntimeKeyIsValid()) {
|
||||
s_Logger.Error($"Invalid asset reference for actor with GUID {guid}");
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject actorObject = assetReference
|
||||
.InstantiateAsync(coreStateSnapshot.Position, coreStateSnapshot.Rotation)
|
||||
.WaitForCompletion();
|
||||
if (actorObject == null) {
|
||||
s_Logger.Error($"Failed to instantiate actor with GUID {guid}");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = actorObject.GetComponent<Actor>();
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||
Destroy(actorObject);
|
||||
return;
|
||||
}
|
||||
|
||||
actor.Manager = this;
|
||||
actor.SourceActorPath = guid;
|
||||
actor.ActorID = actorID;
|
||||
actor.Data = actor.InternalCreateActorData();
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DeserializeActorState(actor, stateData);
|
||||
m_SpawnedActors.Add(actor);
|
||||
}
|
||||
|
||||
public void KillActor(Actor actor) {
|
||||
if (!IsServer) {
|
||||
s_Logger.Error("Only the server can kill actors.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (actor is null) {
|
||||
s_Logger.Error("Trying to kill a null actor.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_SpawnedActors.Remove(actor)) {
|
||||
s_Logger.Error($"Trying to kill an actor that is not registered: {actor.name}. " +
|
||||
"Remember you can only kill actors spawned that are dynamically created");
|
||||
return;
|
||||
}
|
||||
|
||||
Destroy(actor.gameObject);
|
||||
}
|
||||
|
||||
public void CleanUp() {
|
||||
if (IsServer) {
|
||||
CleanUpRpc();
|
||||
@@ -259,7 +150,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
void CleanUpRpc() {
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// @MARK: Common API
|
||||
//
|
||||
@@ -325,60 +216,27 @@ namespace RebootKit.Engine.Simulation {
|
||||
clientState.ActorsSyncPacketsLeft = m_InSceneActors.Count;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
RpcSendParams sendParams = RpcTarget.Single(clientID, RpcTargetUse.Temp);
|
||||
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) {
|
||||
NativeArray<byte> data = SerializeActorState(actor);
|
||||
if (!data.IsCreated) {
|
||||
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
SynchronizeActorStateForClientRpc(actor.ActorID, actor.GetCoreStateSnapshot(), data, sendParams);
|
||||
RR.NetworkSystemInstance.WriteActorSynchronize(NetworkPacketTarget.Single(clientID),
|
||||
actor.ActorID,
|
||||
actor.GetCoreStateSnapshot(),
|
||||
actor.Data);
|
||||
}
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
NativeArray<byte> data = SerializeActorState(actor);
|
||||
if (!data.IsCreated) {
|
||||
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
ActorCoreStateSnapshot coreStateSnapshot = actor.GetCoreStateSnapshot();
|
||||
SpawnActorRpc(actor.SourceActorPath,
|
||||
actor.ActorID,
|
||||
coreStateSnapshot,
|
||||
data,
|
||||
sendParams);
|
||||
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.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeActorStateForClientRpc(ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> data,
|
||||
RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DeserializeActorState(actor, data);
|
||||
ClientSynchronizedActorRpc();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeCoreActorStateRpc(ulong actorID, ActorCoreStateSnapshot snapshot, RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"Actor with ID {actorID} not found for core state synchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(snapshot);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
||||
void ClientSynchronizedActorRpc(RpcParams rpcParams = default) {
|
||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
||||
@@ -389,6 +247,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
clientState.ActorsSyncPacketsLeft--;
|
||||
s_Logger.Info($"Synchronized actor for client {clientID}. Packets left: {clientState.ActorsSyncPacketsLeft}");
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
if (clientState.ActorsSyncPacketsLeft == 0) {
|
||||
@@ -396,6 +255,129 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// @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;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
} else if (header.Type == NetworkDataType.ActorCommand) {
|
||||
Actor actor = FindActorByID(header.ActorID);
|
||||
if (actor == null) {
|
||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for command handling.");
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnLocalActor(string guid,
|
||||
ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> stateData) {
|
||||
AssetReferenceGameObject assetReference = new AssetReferenceGameObject(guid);
|
||||
if (!assetReference.RuntimeKeyIsValid()) {
|
||||
s_Logger.Error($"Invalid asset reference for actor with GUID {guid}");
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject actorObject = assetReference
|
||||
.InstantiateAsync(coreStateSnapshot.Position, coreStateSnapshot.Rotation)
|
||||
.WaitForCompletion();
|
||||
if (actorObject == null) {
|
||||
s_Logger.Error($"Failed to instantiate actor with GUID {guid}");
|
||||
return;
|
||||
}
|
||||
|
||||
Actor actor = actorObject.GetComponent<Actor>();
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||
Destroy(actorObject);
|
||||
return;
|
||||
}
|
||||
|
||||
actor.Manager = this;
|
||||
actor.SourceActorPath = guid;
|
||||
actor.ActorID = actorID;
|
||||
actor.Data = actor.InternalCreateActorData();
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||
m_SpawnedActors.Add(actor);
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Actor Commands and Events
|
||||
//
|
||||
@@ -422,6 +404,10 @@ namespace RebootKit.Engine.Simulation {
|
||||
}
|
||||
|
||||
foreach ((ulong clientID, NetworkClientState state) in RR.NetworkSystemInstance.Clients) {
|
||||
if (NetworkManager.Singleton.LocalClientId == clientID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state.IsReady) {
|
||||
SendActorEventRpc(actorEvent, RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user