working on actors
This commit is contained in:
@@ -1,48 +1,247 @@
|
||||
using Unity.Collections;
|
||||
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 {
|
||||
bool m_IsPlaying = false;
|
||||
public bool IsPlaying {
|
||||
get {
|
||||
return m_IsPlaying;
|
||||
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;
|
||||
}
|
||||
|
||||
set {
|
||||
if (m_IsPlaying == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_IsPlaying = value;
|
||||
|
||||
if (m_IsPlaying) {
|
||||
OnBeginPlay();
|
||||
} else {
|
||||
OnEndPlay();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual void OnSpawned() {
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual void OnDespawned() {
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual void OnBeginPlay() {
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual void OnEndPlay() {
|
||||
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);
|
||||
}
|
||||
|
||||
public virtual void Tick(float deltaTime) {
|
||||
}
|
||||
|
||||
public virtual void SerializeNetworkState(ref DataStreamWriter writer) {
|
||||
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}");
|
||||
}
|
||||
|
||||
public virtual void DeserializeNetworkState(ref DataStreamReader reader) {
|
||||
void OnValidate() {
|
||||
if (ActorID == 0) {
|
||||
ActorID = UniqueID.NewULongFromGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user