multiplayer refactor

This commit is contained in:
2025-07-21 09:04:43 +02:00
parent 1054061d91
commit a0a0f6303d
29 changed files with 2186 additions and 603 deletions

View File

@@ -3,66 +3,23 @@ using System.ComponentModel.DataAnnotations;
using System.Globalization;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Network;
using TriInspector;
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 Serialize(NetworkBufferWriter writer) { }
public void Deserialize(FastBufferReader reader) { }
}
public void Deserialize(NetworkBufferReader 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 int GetMaxBytes() {
return 0;
}
}
@@ -95,6 +52,8 @@ namespace RebootKit.Engine.Simulation {
}
}
// @NOTE: ActorEvent is used to send events from the server to clients and only clients.
// Server should not receive ActorEvents.
public struct ActorEvent : INetworkSerializable {
public ulong ActorID;
public ulong ClientID;
@@ -131,7 +90,7 @@ namespace RebootKit.Engine.Simulation {
DisableColliders = 1 << 1,
}
struct ActorCoreStateSnapshot : INetworkSerializable {
struct ActorCoreStateSnapshot : ISerializableEntity {
public DateTime Timestamp;
// @NOTE: Position, Rotation, and Scale are in local space.
@@ -145,15 +104,39 @@ namespace RebootKit.Engine.Simulation {
public ulong MasterActorID;
public FixedString32Bytes MasterSocketName;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref Timestamp);
serializer.SerializeValue(ref Position);
serializer.SerializeValue(ref Rotation);
serializer.SerializeValue(ref Scale);
serializer.SerializeValue(ref IsHidden);
serializer.SerializeValue(ref Flags);
serializer.SerializeValue(ref MasterActorID);
serializer.SerializeValue(ref MasterSocketName);
public void Serialize(NetworkBufferWriter writer) {
writer.Write(Timestamp.Ticks);
writer.Write(Position);
writer.Write(Rotation);
writer.Write(Scale);
writer.Write(IsHidden);
writer.Write((byte) Flags);
writer.Write(MasterActorID);
writer.Write(MasterSocketName);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out long ticks);
Timestamp = new DateTime(ticks, DateTimeKind.Utc);
reader.Read(out Position);
reader.Read(out Rotation);
reader.Read(out Scale);
reader.Read(out IsHidden);
reader.Read(out byte flagsByte);
Flags = (ActorPhysicsFlags) flagsByte;
reader.Read(out MasterActorID);
reader.Read(out MasterSocketName);
}
public int GetMaxBytes() {
return sizeof(long) + // Timestamp
sizeof(float) * 3 + // Position
sizeof(float) * 4 + // Rotation (Quaternion)
sizeof(float) * 3 + // Scale
sizeof(bool) + // IsHidden
sizeof(byte) + // Flags
sizeof(ulong) + // MasterActorID
sizeof(byte) * 32; // MasterSocketName
}
}
@@ -165,19 +148,19 @@ namespace RebootKit.Engine.Simulation {
/// - Velocity and AngularVelocity are only used if UsingRigidbody is set.
/// - When Actor is mounted to another actor, sync won't happen.
///
[Flags]
public enum ActorTransformSyncMode : byte {
None = 0,
Position = 1 << 0,
Rotation = 1 << 1,
Scale = 1 << 2,
UsingRigidbody = 1 << 3, // @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
// @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
UsingRigidbody = 1 << 3,
Velocity = 1 << 4, // @NOTE: Velocity is only used if UsingRigidbody is set.
AngularVelocity = 1 << 5 // @NOTE: AngularVelocity is only used if UsingRigidbody is set.
}
public struct ActorTransformSyncData : INetworkSerializable {
public struct ActorTransformSyncData : ISerializableEntity {
public ActorTransformSyncMode SyncMode;
public Vector3 Position;
@@ -209,6 +192,81 @@ namespace RebootKit.Engine.Simulation {
serializer.SerializeValue(ref AngularVelocity);
}
}
public void Serialize(NetworkBufferWriter writer) {
writer.Write((byte) SyncMode);
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
writer.Write(Position);
}
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
writer.Write(Rotation);
}
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
writer.Write(Scale);
}
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
writer.Write(Velocity);
}
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
writer.Write(AngularVelocity);
}
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out byte syncModeByte);
SyncMode = (ActorTransformSyncMode) syncModeByte;
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
reader.Read(out Position);
}
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
reader.Read(out Rotation);
}
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
reader.Read(out Scale);
}
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
reader.Read(out Velocity);
}
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
reader.Read(out AngularVelocity);
}
}
public int GetMaxBytes() {
int size = sizeof(byte); // SyncMode
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
size += sizeof(float) * 3; // Vector3
}
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
size += sizeof(float) * 4; // Quaternion
}
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
size += sizeof(float) * 3; // Vector3
}
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
size += sizeof(float) * 3; // Vector3
}
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
size += sizeof(float) * 3; // Vector3
}
return size;
}
}
public abstract class Actor : MonoBehaviour {
@@ -256,11 +314,12 @@ namespace RebootKit.Engine.Simulation {
internal Actor MasterActor;
internal FixedString32Bytes MasterSocketName;
internal bool IsCoreStateDirty;
public bool IsDataDirty { get; protected internal set; }
internal ActorsManager Manager;
internal DateTime LastCoreStateSyncTime = DateTime.MinValue;
//
// @MARK: Unity callbacks
//
@@ -269,11 +328,11 @@ namespace RebootKit.Engine.Simulation {
ActorID = UniqueID.NewULongFromGuid();
}
}
//
// @MARK: Callbacks to override in derived classes
//
protected abstract IActorData CreateActorData();
// @MARK: Server side
@@ -302,7 +361,7 @@ namespace RebootKit.Engine.Simulation {
}
gameObject.SetActive(shouldBeActive);
Manager.SynchronizeActorCoreStateWithOther(this);
IsCoreStateDirty = true;
}
public void MountTo(Actor actor, string slotName) {
@@ -326,7 +385,7 @@ namespace RebootKit.Engine.Simulation {
UpdateLocalPhysicsState(PhysicsFlags);
UpdateMountedTransform();
Manager.SynchronizeActorCoreStateWithOther(this);
IsCoreStateDirty = true;
}
}
@@ -348,7 +407,7 @@ namespace RebootKit.Engine.Simulation {
PhysicsFlags = PhysicsFlagsBeforeMount;
UpdateLocalPhysicsState(PhysicsFlags);
Manager.SynchronizeActorCoreStateWithOther(this);
IsCoreStateDirty = true;
}
public void SetCollidersEnabled(bool enableColliders) {
@@ -369,7 +428,7 @@ namespace RebootKit.Engine.Simulation {
}
UpdateLocalCollidersState(enableColliders);
Manager.SynchronizeActorCoreStateWithOther(this);
IsCoreStateDirty = true;
}
public void SetKinematic(bool isKinematic) {
@@ -390,7 +449,7 @@ namespace RebootKit.Engine.Simulation {
}
actorRigidbody.isKinematic = isKinematic;
Manager.SynchronizeActorCoreStateWithOther(this);
IsCoreStateDirty = true;
}
//
@@ -403,6 +462,7 @@ namespace RebootKit.Engine.Simulation {
protected void SendActorCommand<TCmdData>(ushort commandID, ref TCmdData commandData)
where TCmdData : struct, ISerializableEntity {
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
SendActorCommand(commandID, data);
}
@@ -651,11 +711,11 @@ namespace RebootKit.Engine.Simulation {
internal IActorData InternalCreateActorData() {
return CreateActorData();
}
internal void InitialSyncFinished() {
OnClientFinishedInitialSync();
}
internal void HandleActorCommand(ActorCommand actorCommand) {
if (!RR.IsServer()) {
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");