410 lines
16 KiB
C#
410 lines
16 KiB
C#
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;
|
|
using UnityEngine.AddressableAssets;
|
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
|
|
|
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 {
|
|
static readonly Logger s_Logger = new Logger(nameof(ActorsManager));
|
|
|
|
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; } }
|
|
|
|
//
|
|
// @MARK: NetworkBehaviour callbacks
|
|
//
|
|
public override void OnNetworkSpawn() {
|
|
base.OnNetworkSpawn();
|
|
}
|
|
|
|
public override void OnNetworkDespawn() {
|
|
base.OnNetworkDespawn();
|
|
}
|
|
|
|
//
|
|
// @MARK: Unity callbacks
|
|
//
|
|
void Update() {
|
|
foreach (Actor actor in m_InSceneActors) {
|
|
actor.OnClientTick(Time.deltaTime);
|
|
}
|
|
|
|
foreach (Actor actor in m_SpawnedActors) {
|
|
actor.OnClientTick(Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
//
|
|
// @MARK: Server-side logic
|
|
//
|
|
public void ServerTick(float dt) {
|
|
if (!IsServer) {
|
|
return;
|
|
}
|
|
|
|
TickActorsList(m_InSceneActors, dt);
|
|
TickActorsList(m_SpawnedActors, dt);
|
|
}
|
|
|
|
void TickActorsList(List<Actor> actors, float deltaTime) {
|
|
foreach (Actor actor in actors) {
|
|
actor.OnServerTick(deltaTime);
|
|
|
|
if (actor.IsDataDirty) {
|
|
actor.IsDataDirty = false;
|
|
|
|
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();
|
|
RR.NetworkSystemInstance.WriteActorTransformState(NetworkPacketTarget.AllClients(),
|
|
actor.ActorID,
|
|
syncData);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @MARK: Server API
|
|
//
|
|
public Actor SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
|
if (!IsServer) {
|
|
s_Logger.Error("Only the server can spawn actors.");
|
|
return null;
|
|
}
|
|
|
|
if (!assetReference.RuntimeKeyIsValid()) {
|
|
s_Logger.Error("Trying to spawn an actor with an invalid asset reference.");
|
|
return null;
|
|
}
|
|
|
|
ulong actorID = UniqueID.NewULongFromGuid();
|
|
|
|
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);
|
|
return null;
|
|
}
|
|
|
|
actor.Manager = this;
|
|
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);
|
|
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();
|
|
}
|
|
|
|
[Rpc(SendTo.NotMe)]
|
|
void CleanUpRpc() {
|
|
CleanUp();
|
|
}
|
|
|
|
//
|
|
// @MARK: Common API
|
|
//
|
|
public void RegisterInSceneActor(Actor actor) {
|
|
if (actor.Data == null) {
|
|
actor.Data = actor.InternalCreateActorData();
|
|
}
|
|
|
|
actor.Manager = this;
|
|
|
|
m_InSceneActors.Add(actor);
|
|
}
|
|
|
|
public Actor FindActorByID(ulong actorID) {
|
|
foreach (Actor actor in m_InSceneActors) {
|
|
if (actor.ActorID == actorID) {
|
|
return actor;
|
|
}
|
|
}
|
|
|
|
foreach (Actor actor in m_SpawnedActors) {
|
|
if (actor.ActorID == actorID) {
|
|
return actor;
|
|
}
|
|
}
|
|
|
|
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.");
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
|
void PrepareClientForActorsSyncRpc(RpcParams rpcParams) {
|
|
foreach (Actor spawnedActor in m_SpawnedActors) {
|
|
Destroy(spawnedActor.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);
|
|
}
|
|
}
|
|
|
|
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
|
|
//
|
|
internal void SendActorEvent(ActorEvent actorEvent) {
|
|
if (!RR.IsServer()) {
|
|
s_Logger.Error("Only the server can send actor events.");
|
|
return;
|
|
}
|
|
|
|
RR.NetworkSystemInstance.WriteActorEvent(NetworkPacketTarget.AllClients(), actorEvent);
|
|
}
|
|
|
|
internal void SendActorCommand(ActorCommand actorCommand) {
|
|
RR.NetworkSystemInstance.WriteActorCommand(actorCommand);
|
|
}
|
|
}
|
|
} |