373 lines
13 KiB
C#
373 lines
13 KiB
C#
using System.Collections.Generic;
|
|
using RebootKit.Engine.Foundation;
|
|
using RebootKit.Engine.Main;
|
|
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.
|
|
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 override void OnNetworkSpawn() {
|
|
base.OnNetworkSpawn();
|
|
RR.ServerTick += OnServerTick;
|
|
}
|
|
|
|
public override void OnNetworkDespawn() {
|
|
base.OnNetworkDespawn();
|
|
RR.ServerTick -= OnServerTick;
|
|
}
|
|
|
|
void Update() {
|
|
foreach (Actor actor in m_InSceneActors) {
|
|
actor.ClientTick(Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
void OnServerTick(ulong tick) {
|
|
if (!IsServer) {
|
|
return;
|
|
}
|
|
|
|
float dt = 1.0f / RR.TickRate.IndexValue;
|
|
|
|
foreach (Actor actor in m_InSceneActors) {
|
|
actor.ServerTick(dt);
|
|
|
|
if (actor.IsDataDirty) {
|
|
actor.IsDataDirty = false;
|
|
|
|
NativeArray<byte> data = SerializeActorState(actor);
|
|
if (data.IsCreated) {
|
|
SynchronizeActorStateClientRpc(actor.ActorID, data);
|
|
} else {
|
|
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (Actor actor in m_SpawnedActors) {
|
|
actor.ServerTick(dt);
|
|
|
|
if (actor.IsDataDirty) {
|
|
actor.IsDataDirty = false;
|
|
|
|
NativeArray<byte> data = SerializeActorState(actor);
|
|
if (data.IsCreated) {
|
|
SynchronizeActorStateClientRpc(actor.ActorID, data);
|
|
} else {
|
|
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[ClientRpc(Delivery = RpcDelivery.Unreliable)]
|
|
void SynchronizeActorStateClientRpc(ulong actorID, NativeArray<byte> data) {
|
|
Actor actor = FindActorByID(actorID);
|
|
if (actor is null) {
|
|
return;
|
|
}
|
|
|
|
DeserializeActorState(actor, data);
|
|
}
|
|
|
|
NativeArray<byte> SerializeActorState(Actor actor) {
|
|
return DataSerializationUtils.Serialize(actor.Data);
|
|
}
|
|
|
|
void DeserializeActorState(Actor actor, NativeArray<byte> data) {
|
|
DataSerializationUtils.Deserialize(data, ref actor.Data);
|
|
}
|
|
|
|
internal void SynchronizeActorsForClient(ulong clientID) {
|
|
NetworkClientState clientState = RR.NetworkSystemInstance.GetClientState(clientID);
|
|
if (clientState == null) {
|
|
s_Logger.Error($"Client state for {clientID} not found. Cannot synchronize actors.");
|
|
return;
|
|
}
|
|
|
|
PrepareClientForActorsSyncRpc(RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp));
|
|
}
|
|
|
|
[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;
|
|
NetworkClientState clientState = RR.NetworkSystemInstance.GetClientState(clientID);
|
|
if (clientState == null) {
|
|
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as ready for actors sync.");
|
|
return;
|
|
}
|
|
|
|
clientState.IsReadyForActorsSync = true;
|
|
clientState.ActorsSyncPacketsLeft = m_InSceneActors.Count;
|
|
|
|
RpcSendParams sendParams = RpcTarget.Single(clientID, RpcTargetUse.Temp);
|
|
|
|
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, data, sendParams);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
SpawnActorRpc(actor.SourceActorPath,
|
|
actor.ActorID,
|
|
actor.transform.position,
|
|
actor.transform.localRotation,
|
|
data,
|
|
sendParams);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
|
void SynchronizeActorStateForClientRpc(ulong actorID, NativeArray<byte> data, RpcParams rpcParams) {
|
|
Actor actor = FindActorByID(actorID);
|
|
if (actor is null) {
|
|
return;
|
|
}
|
|
|
|
DeserializeActorState(actor, data);
|
|
ClientSynchronizedActorRpc();
|
|
}
|
|
|
|
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
|
void ClientSynchronizedActorRpc(RpcParams rpcParams = default) {
|
|
ulong clientID = rpcParams.Receive.SenderClientId;
|
|
|
|
NetworkClientState clientState = RR.NetworkSystemInstance.GetClientState(clientID);
|
|
if (clientState == null) {
|
|
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as synchronized.");
|
|
return;
|
|
}
|
|
|
|
clientState.ActorsSyncPacketsLeft--;
|
|
if (clientState.ActorsSyncPacketsLeft == 0) {
|
|
RR.NetworkSystemInstance.ClientSynchronizedActors(clientID);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
|
internal void SendActorCommandToServerRpc(ActorCommand cmd) {
|
|
if (!IsServer) {
|
|
s_Logger.Error("Only the server can handle actor events.");
|
|
return;
|
|
}
|
|
|
|
Actor actor = FindActorByID(cmd.ActorID);
|
|
if (actor is null) {
|
|
s_Logger.Error($"Actor with ID {cmd.ActorID} not found for command {cmd.CommandID}");
|
|
return;
|
|
}
|
|
|
|
actor.HandleActorCommand(cmd);
|
|
}
|
|
|
|
[Rpc(SendTo.Everyone)]
|
|
internal void SendActorEventToClientsRpc(ActorEvent actorEvent) {
|
|
Actor actor = FindActorByID(actorEvent.ActorID);
|
|
if (actor is null) {
|
|
s_Logger.Error($"Actor with ID {actorEvent.ActorID} not found for event {actorEvent.EventID}");
|
|
return;
|
|
}
|
|
|
|
actor.HandleActorEvent(actorEvent);
|
|
}
|
|
|
|
public void RegisterInSceneActor(Actor actor) {
|
|
if (actor.Data == null) {
|
|
actor.Data = actor.InternalCreateActorData();
|
|
}
|
|
|
|
actor.Manager = this;
|
|
|
|
m_InSceneActors.Add(actor);
|
|
}
|
|
|
|
public void CleanUp() {
|
|
if (IsServer) {
|
|
CleanUpRpc();
|
|
}
|
|
|
|
m_InSceneActors.Clear();
|
|
|
|
foreach (Actor actor in m_SpawnedActors) {
|
|
if (actor is not null) {
|
|
Destroy(actor.gameObject);
|
|
}
|
|
}
|
|
|
|
m_SpawnedActors.Clear();
|
|
}
|
|
|
|
[Rpc(SendTo.NotMe)]
|
|
void CleanUpRpc() {
|
|
CleanUp();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public void SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
|
if (!IsServer) {
|
|
s_Logger.Error("Only the server can spawn actors.");
|
|
return;
|
|
}
|
|
|
|
if (!assetReference.RuntimeKeyIsValid()) {
|
|
s_Logger.Error("Trying to spawn an actor with an invalid asset reference.");
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
actor.Manager = this;
|
|
actor.SourceActorPath = assetReference.AssetGUID;
|
|
actor.ActorID = UniqueID.NewULongFromGuid();
|
|
actor.Data = actor.InternalCreateActorData();
|
|
|
|
m_SpawnedActors.Add(actor);
|
|
|
|
NativeArray<byte> stateData = SerializeActorState(actor);
|
|
SpawnActorRpc(assetReference.AssetGUID, actor.ActorID, position, rotation, stateData, RpcTarget.NotMe);
|
|
}
|
|
|
|
// @NOTE: This RPC is used to spawn actors on clients.
|
|
[Rpc(SendTo.SpecifiedInParams)]
|
|
void SpawnActorRpc(string guid,
|
|
ulong actorID,
|
|
Vector3 position,
|
|
Quaternion rotation,
|
|
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(position, 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();
|
|
|
|
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 bool IsActorHidden(ulong actorID) {
|
|
Actor actor = FindActorByID(actorID);
|
|
if (actor is null) {
|
|
s_Logger.Error($"Actor with ID {actorID} not found.");
|
|
return false;
|
|
}
|
|
|
|
return !actor.gameObject.activeSelf;
|
|
}
|
|
|
|
public void SetActorHidden(ulong actorID, bool hidden) {
|
|
if (!IsServer) {
|
|
s_Logger.Error("Only the server can set actor visibility.");
|
|
return;
|
|
}
|
|
|
|
SetActorHiddenRpc(actorID, hidden, RpcTarget.Everyone);
|
|
}
|
|
|
|
[Rpc(SendTo.SpecifiedInParams)]
|
|
void SetActorHiddenRpc(ulong actorID, bool hidden, RpcParams rpcParams) {
|
|
Actor actor = FindActorByID(actorID);
|
|
if (actor is null) {
|
|
s_Logger.Error($"Actor with ID {actorID} not found.");
|
|
return;
|
|
}
|
|
|
|
if (hidden) {
|
|
actor.gameObject.SetActive(false);
|
|
} else {
|
|
actor.gameObject.SetActive(true);
|
|
}
|
|
}
|
|
}
|
|
} |