247 lines
8.6 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
} |