refactor
This commit is contained in:
@@ -74,8 +74,8 @@ namespace RebootKit.Engine.Console {
|
||||
|
||||
ConfigVar.StateChanged -= OnCVarStateChanged;
|
||||
|
||||
m_LogFileWriter.Dispose();
|
||||
m_LogFileStream.Dispose();
|
||||
m_LogFileWriter?.Dispose();
|
||||
m_LogFileStream?.Dispose();
|
||||
|
||||
m_LogFileStream = null;
|
||||
m_LogFileWriter = null;
|
||||
|
||||
@@ -1,55 +1,122 @@
|
||||
using System.Text;
|
||||
using RebootKit.Engine.Main;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootKit.Engine.UI;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RebootKit.Engine.Development {
|
||||
public class DebugOverlayView : UIDocumentView {
|
||||
const string k_DebugLabelClassName = "rr__debug-label";
|
||||
const string k_DebugLabelClassName = "rr__debug-overlay-label";
|
||||
|
||||
string m_HeaderText;
|
||||
|
||||
VisualElement m_RootElement;
|
||||
|
||||
Label m_FPSLabel;
|
||||
Label m_NetworkStatsLabel;
|
||||
Label m_Label;
|
||||
|
||||
readonly StringBuilder m_StringBuilder = new StringBuilder();
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void Update() {
|
||||
if (m_RootElement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Resolution resolution = Screen.currentResolution;
|
||||
m_FPSLabel.text = $"fps: {Mathf.RoundToInt(1f / Time.deltaTime)} | dt: {Time.deltaTime:F4}ms | runtime: {Time.time:F4}s | resolution: {resolution.width}x{resolution.height}@{resolution.refreshRateRatio}Hz";
|
||||
m_StringBuilder.AppendLine(m_HeaderText);
|
||||
AppendFPSInfo();
|
||||
AppendNetworkStateInfo();
|
||||
AppendActorsStateInfo();
|
||||
|
||||
NetworkManager nm = NetworkManager.Singleton;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append("Network: ");
|
||||
sb.Append($"IsServer: {nm.IsServer.ToString()}");
|
||||
sb.Append($" | IsClient: {nm.IsClient.ToString()}");
|
||||
sb.Append($" | IsHost: {nm.IsHost.ToString()}");
|
||||
|
||||
m_NetworkStatsLabel.text = sb.ToString();
|
||||
m_Label.text = m_StringBuilder.ToString();
|
||||
m_StringBuilder.Clear();
|
||||
}
|
||||
|
||||
void AppendFPSInfo() {
|
||||
Resolution resolution = Screen.currentResolution;
|
||||
|
||||
m_StringBuilder.Append("fps: ");
|
||||
m_StringBuilder.Append(Mathf.RoundToInt(1f / Time.deltaTime));
|
||||
m_StringBuilder.Append(" | dt: ");
|
||||
m_StringBuilder.Append(Time.deltaTime.ToString("F4"));
|
||||
m_StringBuilder.Append("ms | runtime: ");
|
||||
m_StringBuilder.Append(Time.time.ToString("F4"));
|
||||
m_StringBuilder.Append("s | resolution: ");
|
||||
m_StringBuilder.Append(resolution.width);
|
||||
m_StringBuilder.Append("x");
|
||||
m_StringBuilder.Append(resolution.height);
|
||||
m_StringBuilder.Append("@");
|
||||
m_StringBuilder.Append(resolution.refreshRateRatio);
|
||||
m_StringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
void AppendNetworkStateInfo() {
|
||||
NetworkSystem network = RR.NetworkSystemInstance;
|
||||
|
||||
if (network == null) {
|
||||
m_StringBuilder.AppendLine("NetworkSystem not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
m_StringBuilder.Append($"IsServer: {RR.IsServer().ToString()}");
|
||||
m_StringBuilder.Append($" | IsClient: {RR.IsClient().ToString()}");
|
||||
m_StringBuilder.Append($" | WorldID: {network.WorldID.ToString()}");
|
||||
m_StringBuilder.Append($" | Clients: {network.Clients.Count.ToString()}");
|
||||
m_StringBuilder.Append($" | ReadyClientsCount: {network.GetReadyClientsCount().ToString()}");
|
||||
|
||||
m_StringBuilder.AppendLine();
|
||||
if (network.TryGetClientState(network.LocalClientID, out NetworkClientState clientState)) {
|
||||
m_StringBuilder.Append($"LocalClientID: {clientState.ClientID.ToString()}");
|
||||
m_StringBuilder.Append($" | SyncState: {clientState.SyncState.ToString()}");
|
||||
m_StringBuilder.Append($" | ActorsSyncPacketsLeft: {clientState.ActorsSyncPacketsLeft.ToString()}");
|
||||
m_StringBuilder.Append($" | IsReady: {clientState.IsReady.ToString()}");
|
||||
} else {
|
||||
m_StringBuilder.Append("ClientState not found for LocalClientID: ");
|
||||
m_StringBuilder.Append(network.LocalClientID.ToString());
|
||||
}
|
||||
|
||||
m_StringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
void AppendActorsStateInfo() {
|
||||
NetworkSystem network = RR.NetworkSystemInstance;
|
||||
if (network == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActorsManager actorsManager = network.Actors;
|
||||
if (actorsManager == null) {
|
||||
m_StringBuilder.Append("ActorsManager not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
m_StringBuilder.Append("InScene Actors Count: ");
|
||||
m_StringBuilder.Append(actorsManager.InSceneActorsCount.ToString());
|
||||
m_StringBuilder.Append(" | Dynamic Actors Count: ");
|
||||
m_StringBuilder.Append(actorsManager.SpawnedActorsCount.ToString());
|
||||
m_StringBuilder.Append(" | Total Actors Count: ");
|
||||
m_StringBuilder.Append(actorsManager.TotalActorsCount.ToString());
|
||||
|
||||
m_StringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: UIDocumentView
|
||||
//
|
||||
public override VisualElement Build() {
|
||||
if (m_HeaderText == null) {
|
||||
m_HeaderText =
|
||||
$"Toggle Overlay [F3] | RebootKit | game: {Application.productName}, version: {Application.version}";
|
||||
}
|
||||
|
||||
m_RootElement = new VisualElement();
|
||||
|
||||
CreateLabel($"Toggle Overlay [F3] | RebootKit | game: {Application.productName}, version: {Application.version}");
|
||||
m_FPSLabel = CreateLabel($"FPS: {Application.targetFrameRate.ToString()}");
|
||||
|
||||
m_NetworkStatsLabel = CreateLabel("Network Stats");
|
||||
m_Label = (Label)LabelBuilder.New("").Build();
|
||||
m_Label.AddToClassList(k_DebugLabelClassName);
|
||||
m_RootElement.Add(m_Label);
|
||||
|
||||
return m_RootElement;
|
||||
}
|
||||
|
||||
Label CreateLabel(string text) {
|
||||
Label label = (Label)LabelBuilder.New(text).Build();
|
||||
label.AddToClassList(k_DebugLabelClassName);
|
||||
|
||||
m_RootElement.Add(label);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using Unity.Multiplayer.Tools.NetStatsMonitor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@@ -18,7 +19,7 @@ namespace RebootKit.Engine.Development {
|
||||
public class DevToolsService : ServiceMonoBehaviour {
|
||||
[SerializeField] DebugOverlayView m_DebugOverlayView;
|
||||
[SerializeField] GameVersionOverlay m_GameVersionOverlay;
|
||||
[SerializeField] GameObject m_NetworkStatsOverlay;
|
||||
[SerializeField] RuntimeNetStatsMonitor m_NetworkStatsOverlay;
|
||||
|
||||
IDisposable m_CVarChangedListener;
|
||||
|
||||
@@ -41,6 +42,10 @@ namespace RebootKit.Engine.Development {
|
||||
if (InputSystem.GetDevice<Keyboard>().f3Key.wasReleasedThisFrame) {
|
||||
DebugCVars.OverlayMode.Set(DebugCVars.OverlayMode.IndexValue == 1 ? 0 : 1);
|
||||
}
|
||||
|
||||
if (InputSystem.GetDevice<Keyboard>().f4Key.wasReleasedThisFrame) {
|
||||
DebugCVars.ShowNetworkStats.Set(DebugCVars.ShowNetworkStats.IndexValue == 1 ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
void OnOverlayModeChanged(int mode) {
|
||||
@@ -57,7 +62,7 @@ namespace RebootKit.Engine.Development {
|
||||
} else if (cvar == DebugCVars.ShowGameVersion) {
|
||||
m_GameVersionOverlay.gameObject.SetActive(cvar.IndexValue > 0);
|
||||
} else if (cvar == DebugCVars.ShowNetworkStats) {
|
||||
m_NetworkStatsOverlay.SetActive(cvar.IndexValue > 0);
|
||||
m_NetworkStatsOverlay.Visible = cvar.IndexValue > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ZLinq;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)]
|
||||
public class InjectAttribute : Attribute {
|
||||
}
|
||||
|
||||
public class DIContext {
|
||||
const BindingFlags k_fieldsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
const BindingFlags k_methodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
static readonly Logger s_logger = new Logger(nameof(DIContext));
|
||||
|
||||
readonly Dictionary<Type, object> m_BindingsMaps = new Dictionary<Type, object>();
|
||||
readonly List<IFieldInjector> m_FieldInjectors = new List<IFieldInjector>();
|
||||
|
||||
public DIContext() {
|
||||
Bind(this);
|
||||
|
||||
AddInjector(new InjectAttributeFieldInjector());
|
||||
}
|
||||
|
||||
public void AddInjector(IFieldInjector injector) {
|
||||
m_FieldInjectors.Add(injector);
|
||||
}
|
||||
|
||||
public void Bind(Type type, object obj) {
|
||||
if (!m_BindingsMaps.TryAdd(type, obj)) {
|
||||
s_logger.Error($"Cannot bind to '{type}', slot is already occupied");
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind<TBind>(TBind obj) {
|
||||
Bind(typeof(TBind), obj);
|
||||
}
|
||||
|
||||
public object Resolve(Type type) {
|
||||
if (m_BindingsMaps.TryGetValue(type, out object obj)) return obj;
|
||||
|
||||
s_logger.Error($"Couldn't resolve `{type}`");
|
||||
return null;
|
||||
}
|
||||
|
||||
public T Resolve<T>() {
|
||||
return (T) Resolve(typeof(T));
|
||||
}
|
||||
|
||||
// @brief creates new instance of an object and injects dependencies
|
||||
public T Create<T>() {
|
||||
T instance = Activator.CreateInstance<T>();
|
||||
|
||||
Inject(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public T Create<T>(params object[] args) {
|
||||
T instance = (T) Activator.CreateInstance(typeof(T), args);
|
||||
Inject(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void Inject(object target) {
|
||||
Type type = target.GetType();
|
||||
|
||||
foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) {
|
||||
InjectField(field, target);
|
||||
}
|
||||
|
||||
foreach (MethodInfo method in type.GetMethods(k_methodsBindingFlags)) {
|
||||
if (!Attribute.IsDefined(method, typeof(InjectAttribute))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Type[] paramsTypes = method.GetParameters()
|
||||
.AsValueEnumerable()
|
||||
.Select(t => t.ParameterType)
|
||||
.ToArray();
|
||||
|
||||
object[] instances = new object[paramsTypes.Length];
|
||||
|
||||
for (int i = 0; i < paramsTypes.Length; ++i) {
|
||||
instances[i] = Resolve(paramsTypes[i]);
|
||||
|
||||
if (instances[i] == null) {
|
||||
s_logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`");
|
||||
}
|
||||
}
|
||||
|
||||
method.Invoke(target, instances);
|
||||
}
|
||||
}
|
||||
|
||||
bool InjectField(FieldInfo field, object target) {
|
||||
for (int i = m_FieldInjectors.Count - 1; i >= 0; i--) {
|
||||
if (m_FieldInjectors[i].Inject(field, target, this)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface IFieldInjector {
|
||||
bool Inject(FieldInfo field, object target, DIContext context);
|
||||
}
|
||||
|
||||
class InjectAttributeFieldInjector : IFieldInjector {
|
||||
public bool Inject(FieldInfo field, object target, DIContext context) {
|
||||
if (!Attribute.IsDefined(field, typeof(InjectAttribute))) return false;
|
||||
|
||||
object instance = context.Resolve(field.FieldType);
|
||||
|
||||
if (instance == null) {
|
||||
s_logger.Error($"Cannot resolve `{field.FieldType}`");
|
||||
return false;
|
||||
}
|
||||
|
||||
field.SetValue(target, instance);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee4133e4a594126868703cc035663cd
|
||||
timeCreated: 1742001112
|
||||
@@ -5,10 +5,6 @@ namespace RebootKit.Engine.Foundation {
|
||||
TProd Create();
|
||||
}
|
||||
|
||||
public interface IFactoryDI<out TProd> {
|
||||
TProd Create(DIContext context);
|
||||
}
|
||||
|
||||
public abstract class FactoryAsset<TProd> : ScriptableObject, IFactory<TProd> where TProd : class {
|
||||
public abstract TProd Create();
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace RebootKit.Engine.Foundation {
|
||||
public interface IDependencyInstaller {
|
||||
void Install(DIContext context);
|
||||
}
|
||||
|
||||
public abstract class SceneDependencyInstaller : MonoBehaviour, IDependencyInstaller {
|
||||
public abstract void Install(DIContext context);
|
||||
}
|
||||
|
||||
[DefaultExecutionOrder(-1000)]
|
||||
public class SceneContext : MonoBehaviour {
|
||||
static readonly Logger s_logger = new Logger(nameof(SceneContext));
|
||||
|
||||
[SerializeField] SceneDependencyInstaller[] m_Installers;
|
||||
|
||||
DIContext m_DIContext;
|
||||
|
||||
void Awake() {
|
||||
m_DIContext = new DIContext();
|
||||
|
||||
s_logger.Info("Installing scene dependency installers");
|
||||
foreach (SceneDependencyInstaller installer in m_Installers) {
|
||||
installer.Install(m_DIContext);
|
||||
}
|
||||
|
||||
foreach (GameObject root in gameObject.scene.GetRootGameObjects()) {
|
||||
m_DIContext.InjectGameObject(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DIContextGameObjectEx {
|
||||
public static void InjectGameObject(this DIContext context, GameObject gameObject, bool injectChildren = true) {
|
||||
Assert.IsNotNull(gameObject);
|
||||
|
||||
Component[] components = injectChildren ? gameObject.GetComponentsInChildren<Component>() : gameObject.GetComponents<Component>();
|
||||
foreach (Component component in components) {
|
||||
context.Inject(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8dd28652b58c4d689ab3f2f9354d7589
|
||||
timeCreated: 1742006992
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
@@ -8,14 +11,31 @@ using UnityEngine;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootKit.Engine.Main {
|
||||
class NetworkClientState {
|
||||
enum NetworkClientSyncState {
|
||||
NotReady,
|
||||
LoadingWorld,
|
||||
PreparingForActorsSync,
|
||||
SyncingActors,
|
||||
Ready
|
||||
}
|
||||
|
||||
struct NetworkClientState : INetworkSerializable {
|
||||
public ulong ClientID;
|
||||
public bool IsWorldLoaded;
|
||||
public bool AreActorsSynced;
|
||||
public bool IsReadyForActorsSync;
|
||||
public NetworkClientSyncState SyncState;
|
||||
public int ActorsSyncPacketsLeft;
|
||||
|
||||
public bool IsReady;
|
||||
public bool IsReady {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get {
|
||||
return SyncState == NetworkClientSyncState.Ready;
|
||||
}
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
||||
serializer.SerializeValue(ref ClientID);
|
||||
serializer.SerializeValue(ref SyncState);
|
||||
serializer.SerializeValue(ref ActorsSyncPacketsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkSystem : NetworkBehaviour {
|
||||
@@ -25,13 +45,26 @@ namespace RebootKit.Engine.Main {
|
||||
|
||||
internal readonly Dictionary<ulong, NetworkClientState> Clients = new Dictionary<ulong, NetworkClientState>();
|
||||
|
||||
FixedString512Bytes m_WorldID = new FixedString512Bytes("");
|
||||
public FixedString512Bytes WorldID { get; private set; } = new FixedString512Bytes("");
|
||||
bool m_IsChangingWorld = false;
|
||||
|
||||
public ulong LocalClientID {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get {
|
||||
return NetworkManager.Singleton.LocalClientId;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void Awake() {
|
||||
RR.NetworkSystemInstance = this;
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: NetworkBehaviour callbacks
|
||||
//
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
@@ -55,35 +88,42 @@ namespace RebootKit.Engine.Main {
|
||||
|
||||
NetworkClientState newClientState = new NetworkClientState {
|
||||
ClientID = clientID,
|
||||
IsWorldLoaded = false,
|
||||
AreActorsSynced = false,
|
||||
IsReadyForActorsSync = false,
|
||||
IsReady = false
|
||||
SyncState = NetworkClientSyncState.NotReady
|
||||
};
|
||||
Clients.Add(clientID, newClientState);
|
||||
|
||||
if (!m_WorldID.IsEmpty) {
|
||||
s_Logger.Info($"Synchronizing world load for client {clientID} with world ID '{m_WorldID}'");
|
||||
ClientLoadWorldRpc(m_WorldID.ToString(), RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
if (clientID != NetworkManager.Singleton.LocalClientId) {
|
||||
foreach (NetworkClientState state in Clients.Values) {
|
||||
UpdateClientStateRpc(state, RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
}
|
||||
|
||||
if (!WorldID.IsEmpty) {
|
||||
s_Logger.Info($"Synchronizing world load for client {clientID} with world ID '{WorldID}'");
|
||||
ClientLoadWorldRpc(WorldID.ToString(), RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
}
|
||||
|
||||
void OnClientDisconnect(ulong clientID) {
|
||||
if (!IsServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Info($"OnClientDisconnect: {clientID}");
|
||||
Clients.Remove(clientID);
|
||||
}
|
||||
|
||||
internal NetworkClientState GetClientState(ulong clientID) {
|
||||
if (Clients.TryGetValue(clientID, out NetworkClientState clientState)) {
|
||||
return clientState;
|
||||
|
||||
//
|
||||
// @MARK: Server API
|
||||
//
|
||||
public void KickClient(ulong clientID, string reason = "Kicked by server") {
|
||||
if (!IsServer) {
|
||||
s_Logger.Error("Only server can kick clients.");
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Error($"Client state for {clientID} not found.");
|
||||
return null;
|
||||
if (NetworkManager.Singleton.ConnectedClients.TryGetValue(clientID, out NetworkClient client)) {
|
||||
NetworkManager.Singleton.DisconnectClient(clientID, reason);
|
||||
s_Logger.Info($"Kicked client {clientID}: {reason}");
|
||||
} else {
|
||||
s_Logger.Error($"Client {clientID} not found.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentWorld(string worldID) {
|
||||
@@ -93,7 +133,7 @@ namespace RebootKit.Engine.Main {
|
||||
}
|
||||
|
||||
if (m_IsChangingWorld) {
|
||||
s_Logger.Error($"Already changing world to '{m_WorldID}'. Please wait until the current world change is complete.");
|
||||
s_Logger.Error($"Already changing world to '{WorldID}'. Please wait until the current world change is complete.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,13 +143,12 @@ namespace RebootKit.Engine.Main {
|
||||
return;
|
||||
}
|
||||
|
||||
m_WorldID = worldID;
|
||||
|
||||
foreach (KeyValuePair<ulong, NetworkClientState> kv in Clients) {
|
||||
kv.Value.IsWorldLoaded = false;
|
||||
kv.Value.AreActorsSynced = false;
|
||||
kv.Value.IsReadyForActorsSync = false;
|
||||
kv.Value.IsReady = false;
|
||||
WorldID = worldID;
|
||||
|
||||
foreach ((ulong _, NetworkClientState clientState) in Clients.ToList()) {
|
||||
NetworkClientState state = clientState;
|
||||
state.SyncState = NetworkClientSyncState.LoadingWorld;
|
||||
UpdateClientState(state);
|
||||
}
|
||||
|
||||
ServerLoadWorldAsync(worldConfigAsset, destroyCancellationToken).Forget();
|
||||
@@ -127,8 +166,14 @@ namespace RebootKit.Engine.Main {
|
||||
|
||||
m_IsChangingWorld = false;
|
||||
|
||||
NetworkClientState localClientState = GetClientState(NetworkManager.Singleton.LocalClientId);
|
||||
localClientState.IsReady = true;
|
||||
if (!TryGetClientState(NetworkManager.Singleton.LocalClientId, out NetworkClientState localClientState)) {
|
||||
s_Logger.Error($"Local client state not found for client ID {NetworkManager.Singleton.LocalClientId}.");
|
||||
RR.Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
localClientState.SyncState = NetworkClientSyncState.Ready;
|
||||
UpdateClientState(localClientState);
|
||||
|
||||
RR.GameInstance.PlayerBecameReady(localClientState.ClientID);
|
||||
|
||||
@@ -161,7 +206,7 @@ namespace RebootKit.Engine.Main {
|
||||
|
||||
await RR.World.LoadAsync(worldConfigAsset.Config, cancellationToken);
|
||||
|
||||
m_WorldID = worldID;
|
||||
WorldID = worldID;
|
||||
ClientLoadedWorldRpc(worldID);
|
||||
}
|
||||
|
||||
@@ -169,30 +214,60 @@ namespace RebootKit.Engine.Main {
|
||||
void ClientLoadedWorldRpc(string worldID, RpcParams rpcParams = default) {
|
||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
||||
|
||||
if (!m_WorldID.Equals(worldID)) {
|
||||
s_Logger.Error($"Client {clientID} tried to load world '{worldID}', but server is in world '{m_WorldID}'.");
|
||||
if (!WorldID.Equals(worldID)) {
|
||||
s_Logger.Error($"Client {clientID} tried to load world '{worldID}', but server is in world '{WorldID}'.");
|
||||
NetworkManager.Singleton.DisconnectClient(clientID, "World mismatch!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Clients.TryGetValue(clientID, out NetworkClientState clientState)) {
|
||||
clientState.IsWorldLoaded = true;
|
||||
clientState.IsReadyForActorsSync = false;
|
||||
Actors.SynchronizeActorsForClient(clientID);
|
||||
Actors.InitializeActorsForClient(clientID);
|
||||
} else {
|
||||
NetworkManager.Singleton.DisconnectClient(clientID, "Client is not registered!");
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClientSynchronizedActors(ulong clientID) {
|
||||
NetworkClientState clientState = GetClientState(clientID);
|
||||
if (clientState is null) {
|
||||
s_Logger.Error($"Client state for {clientID} not found.");
|
||||
|
||||
//
|
||||
// @MARK: Internal
|
||||
//
|
||||
internal bool TryGetClientState(ulong clientID, out NetworkClientState clientState) {
|
||||
return Clients.TryGetValue(clientID, out clientState);
|
||||
}
|
||||
|
||||
internal void UpdateClientState(NetworkClientState clientState) {
|
||||
if (!IsServer) {
|
||||
s_Logger.Error("UpdateClientState can only be called on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
Clients[clientState.ClientID] = clientState;
|
||||
UpdateClientStateRpc(clientState, RpcTarget.NotServer);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void UpdateClientStateRpc(NetworkClientState newState, RpcParams rpcParams) {
|
||||
Clients[newState.ClientID] = newState;
|
||||
}
|
||||
|
||||
clientState.IsReady = true;
|
||||
RR.GameInstance.PlayerBecameReady(clientID);
|
||||
internal void ClientSynchronizedActors(ulong clientID) {
|
||||
if (TryGetClientState(clientID, out NetworkClientState state)) {
|
||||
state.SyncState = NetworkClientSyncState.Ready;
|
||||
UpdateClientState(state);
|
||||
|
||||
RR.GameInstance.PlayerBecameReady(clientID);
|
||||
} else {
|
||||
s_Logger.Error($"Client state for {clientID} not found.");
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetReadyClientsCount() {
|
||||
int count = 0;
|
||||
foreach (NetworkClientState clientState in Clients.Values) {
|
||||
if (clientState.IsReady) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using RebootKit.Engine.Foundation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootKit.Engine {
|
||||
public class MainSceneInstaller : SceneDependencyInstaller {
|
||||
public override void Install(DIContext context) {
|
||||
foreach (GameObject rootGameObject in gameObject.scene.GetRootGameObjects()) {
|
||||
IService[] services = rootGameObject.GetComponentsInParent<IService>();
|
||||
foreach (IService service in services) {
|
||||
context.Bind(service.GetType(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4238ea1a17e342e583cdd929103a22c6
|
||||
timeCreated: 1742007242
|
||||
@@ -259,25 +259,35 @@ namespace RebootKit.Engine.Simulation {
|
||||
public bool IsDataDirty { get; protected internal set; }
|
||||
|
||||
internal ActorsManager Manager;
|
||||
|
||||
internal DateTime LastCoreStateSyncTime = DateTime.MinValue;
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void OnValidate() {
|
||||
if (ActorID == 0) {
|
||||
ActorID = UniqueID.NewULongFromGuid();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Callbacks to override in derived classes
|
||||
//
|
||||
|
||||
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
|
||||
// @MARK: Server side
|
||||
public virtual void OnServerTick(float deltaTime) { }
|
||||
protected virtual void OnActorCommandServer(ActorCommand actorCommand) { }
|
||||
|
||||
// @NOTE: Client-side method to handle actor events
|
||||
// Override this method to implement client-side logic
|
||||
public virtual void OnClientTick(float deltaTime) { }
|
||||
protected virtual void OnActorEventClient(ActorEvent actorEvent) { }
|
||||
protected virtual void OnClientFinishedInitialSync() { }
|
||||
|
||||
//
|
||||
// @MARK: Server API
|
||||
//
|
||||
public void SetHidden(bool hidden) {
|
||||
if (!RR.IsServer()) {
|
||||
s_ActorLogger.Error($"Only the server can set actor visibility. Actor: {name} (ID: {ActorID})");
|
||||
@@ -383,7 +393,9 @@ namespace RebootKit.Engine.Simulation {
|
||||
Manager.SynchronizeActorCoreStateWithOther(this);
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Common API
|
||||
//
|
||||
public bool IsHidden() {
|
||||
return !gameObject.activeSelf;
|
||||
}
|
||||
@@ -457,7 +469,9 @@ namespace RebootKit.Engine.Simulation {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @MARK: Internal API
|
||||
//
|
||||
// @MARK: Internal
|
||||
//
|
||||
internal ActorCoreStateSnapshot GetCoreStateSnapshot() {
|
||||
ActorCoreStateSnapshot snapshot = new ActorCoreStateSnapshot();
|
||||
snapshot.Timestamp = DateTime.UtcNow;
|
||||
@@ -637,7 +651,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})");
|
||||
@@ -672,13 +690,5 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
OnActorEventClient(actorEvent);
|
||||
}
|
||||
|
||||
|
||||
// @MARK: Unity lifecycle methods
|
||||
void OnValidate() {
|
||||
if (ActorID == 0) {
|
||||
ActorID = UniqueID.NewULongFromGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,20 @@ 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.
|
||||
// - Release addressables when they are no longer needed.
|
||||
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 int InSceneActorsCount { get { return m_InSceneActors.Count; } }
|
||||
public int SpawnedActorsCount { get { return m_SpawnedActors.Count; } }
|
||||
public int TotalActorsCount { get { return m_InSceneActors.Count + m_SpawnedActors.Count; } }
|
||||
|
||||
//
|
||||
// @MARK: NetworkBehaviour callbacks
|
||||
//
|
||||
public override void OnNetworkSpawn() {
|
||||
base.OnNetworkSpawn();
|
||||
RR.ServerTick += OnServerTick;
|
||||
@@ -27,16 +35,22 @@ namespace RebootKit.Engine.Simulation {
|
||||
RR.ServerTick -= OnServerTick;
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Unity callbacks
|
||||
//
|
||||
void Update() {
|
||||
foreach (Actor actor in m_InSceneActors) {
|
||||
actor.ClientTick(Time.deltaTime);
|
||||
actor.OnClientTick(Time.deltaTime);
|
||||
}
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
actor.ClientTick(Time.deltaTime);
|
||||
actor.OnClientTick(Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Server-side logic
|
||||
//
|
||||
void OnServerTick(ulong tick) {
|
||||
if (!IsServer) {
|
||||
return;
|
||||
@@ -50,14 +64,14 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
void TickActorsList(List<Actor> actors, float deltaTime) {
|
||||
foreach (Actor actor in actors) {
|
||||
actor.ServerTick(deltaTime);
|
||||
actor.OnServerTick(deltaTime);
|
||||
|
||||
if (actor.IsDataDirty) {
|
||||
actor.IsDataDirty = false;
|
||||
|
||||
NativeArray<byte> data = SerializeActorState(actor);
|
||||
if (data.IsCreated) {
|
||||
SynchronizeActorStateWithClients(actor.ActorID, data);
|
||||
SendActorStateToClients(actor.ActorID, data);
|
||||
} else {
|
||||
s_Logger.Error($"Failed to serialize actor data for {actor.name}");
|
||||
}
|
||||
@@ -84,7 +98,7 @@ namespace RebootKit.Engine.Simulation {
|
||||
SynchronizeCoreActorStateRpc(actor.ActorID, actor.GetCoreStateSnapshot(), RpcTarget.NotMe);
|
||||
}
|
||||
|
||||
void SynchronizeActorStateWithClients(ulong actorID, NativeArray<byte> data) {
|
||||
void SendActorStateToClients(ulong actorID, NativeArray<byte> data) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can synchronize actor states with clients.");
|
||||
return;
|
||||
@@ -103,9 +117,8 @@ namespace RebootKit.Engine.Simulation {
|
||||
if (actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Synchronizing actor state for {actor.name} with ID {actorID}");
|
||||
|
||||
s_Logger.Info($"Synchronizing actor state for {actor.name} with ID {actorID}");
|
||||
DeserializeActorState(actor, data);
|
||||
}
|
||||
|
||||
@@ -127,197 +140,10 @@ namespace RebootKit.Engine.Simulation {
|
||||
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, actor.GetCoreStateSnapshot(), 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;
|
||||
}
|
||||
|
||||
ActorCoreStateSnapshot coreStateSnapshot = actor.GetCoreStateSnapshot();
|
||||
SpawnActorRpc(actor.SourceActorPath,
|
||||
actor.ActorID,
|
||||
coreStateSnapshot,
|
||||
data,
|
||||
sendParams);
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeActorStateForClientRpc(ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> data,
|
||||
RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DeserializeActorState(actor, data);
|
||||
ClientSynchronizedActorRpc();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeCoreActorStateRpc(ulong actorID, ActorCoreStateSnapshot snapshot, RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"Actor with ID {actorID} not found for core state synchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(snapshot);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
internal void SendActorEvent(ActorEvent actorEvent) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can send actor events.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((ulong clientID, NetworkClientState state) in RR.NetworkSystemInstance.Clients) {
|
||||
if (state.IsReady) {
|
||||
SendActorEventRpc(actorEvent, RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams)]
|
||||
void SendActorEventRpc(ActorEvent actorEvent, RpcParams rpcParams) {
|
||||
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.OrNull() != 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;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// @MARK: Server API
|
||||
//
|
||||
public Actor SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
||||
if (!IsServer) {
|
||||
s_Logger.Error("Only the server can spawn actors.");
|
||||
@@ -412,5 +238,205 @@ namespace RebootKit.Engine.Simulation {
|
||||
|
||||
Destroy(actor.gameObject);
|
||||
}
|
||||
|
||||
public void CleanUp() {
|
||||
if (IsServer) {
|
||||
CleanUpRpc();
|
||||
}
|
||||
|
||||
m_InSceneActors.Clear();
|
||||
|
||||
foreach (Actor actor in m_SpawnedActors) {
|
||||
if (actor.OrNull() != null) {
|
||||
Destroy(actor.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
m_SpawnedActors.Clear();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.NotMe)]
|
||||
void CleanUpRpc() {
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Common API
|
||||
//
|
||||
public void RegisterInSceneActor(Actor actor) {
|
||||
if (actor.Data == null) {
|
||||
actor.Data = actor.InternalCreateActorData();
|
||||
}
|
||||
|
||||
actor.Manager = this;
|
||||
|
||||
m_InSceneActors.Add(actor);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Initial synchronization
|
||||
//
|
||||
internal void InitializeActorsForClient(ulong clientID) {
|
||||
if (RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
clientState.SyncState = NetworkClientSyncState.PreparingForActorsSync;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
PrepareClientForActorsSyncRpc(RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp));
|
||||
} else {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot synchronize actors.");
|
||||
}
|
||||
}
|
||||
|
||||
[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;
|
||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as ready for actors sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
clientState.SyncState = NetworkClientSyncState.SyncingActors;
|
||||
clientState.ActorsSyncPacketsLeft = m_InSceneActors.Count;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
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, actor.GetCoreStateSnapshot(), 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;
|
||||
}
|
||||
|
||||
ActorCoreStateSnapshot coreStateSnapshot = actor.GetCoreStateSnapshot();
|
||||
SpawnActorRpc(actor.SourceActorPath,
|
||||
actor.ActorID,
|
||||
coreStateSnapshot,
|
||||
data,
|
||||
sendParams);
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeActorStateForClientRpc(ulong actorID,
|
||||
ActorCoreStateSnapshot coreStateSnapshot,
|
||||
NativeArray<byte> data,
|
||||
RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(coreStateSnapshot);
|
||||
DeserializeActorState(actor, data);
|
||||
ClientSynchronizedActorRpc();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
||||
void SynchronizeCoreActorStateRpc(ulong actorID, ActorCoreStateSnapshot snapshot, RpcParams rpcParams) {
|
||||
Actor actor = FindActorByID(actorID);
|
||||
if (actor is null) {
|
||||
s_Logger.Error($"Actor with ID {actorID} not found for core state synchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.RestoreCoreState(snapshot);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
||||
void ClientSynchronizedActorRpc(RpcParams rpcParams = default) {
|
||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
||||
|
||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as synchronized.");
|
||||
return;
|
||||
}
|
||||
|
||||
clientState.ActorsSyncPacketsLeft--;
|
||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
||||
|
||||
if (clientState.ActorsSyncPacketsLeft == 0) {
|
||||
RR.NetworkSystemInstance.ClientSynchronizedActors(clientID);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Actor Commands and Events
|
||||
//
|
||||
[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);
|
||||
}
|
||||
|
||||
internal void SendActorEvent(ActorEvent actorEvent) {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can send actor events.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((ulong clientID, NetworkClientState state) in RR.NetworkSystemInstance.Clients) {
|
||||
if (state.IsReady) {
|
||||
SendActorEventRpc(actorEvent, RpcTarget.Single(clientID, RpcTargetUse.Temp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Rpc(SendTo.SpecifiedInParams)]
|
||||
void SendActorEventRpc(ActorEvent actorEvent, RpcParams rpcParams) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user