Files
RebootKit/Runtime/Engine/Code/Simulation/Actor.cs
2025-07-03 05:52:25 +02:00

247 lines
8.6 KiB
C#

using System;
using NUnit.Framework;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
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 Deserialize(FastBufferReader 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 struct ActorCommand : INetworkSerializable {
public ulong ActorID;
public ulong ClientID;
public ushort CommandID;
public NativeArray<byte> Data;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref ActorID);
serializer.SerializeValue(ref ClientID);
serializer.SerializeValue(ref CommandID);
if (serializer.IsWriter) {
bool hasData = Data.IsCreated;
serializer.SerializeValue(ref hasData);
if (hasData) {
serializer.SerializeValue(ref Data, Allocator.Temp);
}
} else if (serializer.IsReader) {
bool hasData = false;
serializer.SerializeValue(ref hasData);
if (hasData) {
serializer.SerializeValue(ref Data, Allocator.Temp);
}
}
}
}
public struct ActorEvent : INetworkSerializable {
public ulong ActorID;
public ulong ClientID;
public ushort EventID;
public NativeArray<byte> Data;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref ActorID);
serializer.SerializeValue(ref ClientID);
serializer.SerializeValue(ref EventID);
if (serializer.IsWriter) {
bool hasData = Data.IsCreated;
serializer.SerializeValue(ref hasData);
if (hasData) {
serializer.SerializeValue(ref Data, Allocator.Temp);
}
} else if (serializer.IsReader) {
bool hasData = false;
serializer.SerializeValue(ref hasData);
if (hasData) {
serializer.SerializeValue(ref Data, Allocator.Temp);
}
}
}
}
public abstract class Actor : MonoBehaviour {
static readonly Logger s_ActorLogger = new Logger(nameof(Actor));
[field: SerializeField, TriInspector.ReadOnly] public string SourceActorPath { get; internal set; } = "";
[field: SerializeField, ReadOnly] public ulong ActorID { get; internal set; }
[NonSerialized] internal IActorData Data;
public bool IsDataDirty { get; protected internal set; }
internal ActorsManager Manager;
internal IActorData InternalCreateActorData() {
return CreateActorData();
}
internal void HandleActorCommand(ActorCommand actorCommand) {
if (!RR.IsServer()) {
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
return;
}
if (Manager is null) {
s_ActorLogger.Error($"Cannot handle command because Manager is null for actor {name} (ID: {ActorID})");
return;
}
if (actorCommand.ActorID != ActorID) {
s_ActorLogger
.Error($"Actor command ActorID {actorCommand.ActorID} does not match this actor's ID {ActorID}");
return;
}
OnActorCommandServer(actorCommand);
}
internal void HandleActorEvent(ActorEvent actorEvent) {
if (Manager is null) {
s_ActorLogger.Error($"Cannot handle event because Manager is null for actor {name} (ID: {ActorID})");
return;
}
if (actorEvent.ActorID != ActorID) {
s_ActorLogger
.Error($"Actor event ActorID {actorEvent.ActorID} does not match this actor's ID {ActorID}");
return;
}
OnActorEventClient(actorEvent);
}
protected abstract IActorData CreateActorData();
// Override this method to implement server-side logic
public virtual void ServerTick(float deltaTime) { }
// Override this method to implement client-side logic
public virtual void ClientTick(float deltaTime) { }
// @NOTE: Server-side method to handle actor commands
protected virtual void OnActorCommandServer(ActorCommand actorCommand) { }
// @NOTE: Client-side method to handle actor events
protected virtual void OnActorEventClient(ActorEvent actorEvent) { }
protected void SendActorCommand<TCmdData>(ushort 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) {
if (Manager is null) {
s_ActorLogger.Error($"Cannot send command because Manager is null for actor {name} (ID: {ActorID})");
return;
}
ActorCommand command = new ActorCommand {
ActorID = ActorID,
ClientID = NetworkManager.Singleton.LocalClientId,
CommandID = commandID,
Data = data
};
Manager.SendActorCommandToServerRpc(command);
}
protected void SendActorEvent(ushort 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) {
s_ActorLogger.Error($"Cannot send event because Manager is null for actor {name} (ID: {ActorID})");
return;
}
ActorEvent actorEvent = new ActorEvent {
ActorID = ActorID,
ClientID = NetworkManager.Singleton.LocalClientId,
EventID = eventID,
Data = data
};
Manager.SendActorEventToClientsRpc(actorEvent);
}
protected T DataAs<T>() where T : IActorData {
if (Data is T data) {
return data;
}
throw new System.InvalidCastException($"Actor data is not of type {typeof(T).Name}");
}
void OnValidate() {
if (ActorID == 0) {
ActorID = UniqueID.NewULongFromGuid();
}
}
}
}