This commit is contained in:
2025-07-17 06:36:37 +02:00
parent 4ec3dedd42
commit 1054061d91
52 changed files with 804 additions and 704 deletions

View File

@@ -1,22 +1,18 @@
using System.Collections.Generic;
using RebootKit.Engine;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.UI;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace RebootKitEditor.RebootWindow {
namespace RebootKit.Editor.RebootWindow {
public class GameServicesView : IView {
VisualElement m_RootElement;
readonly List<Editor> m_ServiceEditors = new List<Editor>();
readonly List<UnityEditor.Editor> m_ServiceEditors = new List<UnityEditor.Editor>();
[Inject] EngineConfigAsset m_EngineConfigAsset;
public void Dispose() {
foreach (Editor editor in m_ServiceEditors) {
foreach (UnityEditor.Editor editor in m_ServiceEditors) {
if (editor != null) {
Object.DestroyImmediate(editor);
}
@@ -93,7 +89,7 @@ namespace RebootKitEditor.RebootWindow {
};
root.Add(editorView);
Editor editor = Editor.CreateEditor(serviceAsset);
UnityEditor.Editor editor = UnityEditor.Editor.CreateEditor(serviceAsset);
m_ServiceEditors.Add(editor);
InspectorElement inspectorElement = new InspectorElement(editor);

View File

@@ -1,9 +1,11 @@
using RebootKit.Engine;
using RebootKit.Editor.RebootWindow;
using RebootKit.Engine;
using RebootKit.Engine.Foundation;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Logger = RebootKit.Engine.Foundation.Logger;
using TabView = RebootKit.Editor.RebootWindow.TabView;
namespace RebootKitEditor.RebootWindow {
static class RTheme {
@@ -27,7 +29,6 @@ namespace RebootKitEditor.RebootWindow {
static readonly Logger s_logger = new Logger(nameof(RebootEditorWindow));
EngineConfigAsset m_EngineConfigAsset;
DIContext m_DIContext;
VisualElement m_RootElement;
TabView m_TabView;
@@ -55,15 +56,11 @@ namespace RebootKitEditor.RebootWindow {
}
}
m_DIContext = new DIContext();
m_DIContext.Bind(this);
m_DIContext.Bind(m_EngineConfigAsset);
m_TabView = m_DIContext.Create<TabView>();
m_TabView.AddTab("Home", m_DIContext.Create<HomeView>());
m_TabView.AddTab("Config Vars", m_DIContext.Create<ConfigVarsView>());
m_TabView.AddTab("Game Services", m_DIContext.Create<GameServicesView>());
m_TabView.AddTab("Worlds", m_DIContext.Create<WorldsView>());
m_TabView = new TabView();
m_TabView.AddTab("Home", new HomeView());
m_TabView.AddTab("Config Vars", new ConfigVarsView());
m_TabView.AddTab("Game Services", new GameServicesView());
m_TabView.AddTab("Worlds", new WorldsView());
}
void CreateGUI() {

View File

@@ -1,10 +1,11 @@
using System.Collections.Generic;
using RebootKit.Engine.UI;
using RebootKitEditor.RebootWindow;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
namespace RebootKitEditor.RebootWindow {
namespace RebootKit.Editor.RebootWindow {
public class TabView : IView {
struct Tab {
public string name;

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: cee4133e4a594126868703cc035663cd
timeCreated: 1742001112

View File

@@ -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();
}

View File

@@ -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);
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8dd28652b58c4d689ab3f2f9354d7589
timeCreated: 1742006992

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4238ea1a17e342e583cdd929103a22c6
timeCreated: 1742007242

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -16,7 +16,9 @@
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
"GUID:324caed91501a9c47a04ebfd87b68794",
"GUID:1491147abca9d7d4bb7105af628b223e"
"GUID:1491147abca9d7d4bb7105af628b223e",
"GUID:b1cd7326e664a434ab35daa802773c7f",
"GUID:2c360f3794ebc41388fc11424ddbfdd0"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1ef6cf326b7733c4b88b6fcc7f9cfd36
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: e0588287147a97a4f9c935f5c65bd309
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 60c63702be318df4bb321722e4771886
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 63cae67a8dd6d314c9f90b4015047330
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: bd60d77bddcc9e841985da2eb7b885df
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 46679f55857999344b12abedb0b7851a
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: c6183785f78a93b4ea0d67248008f675
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 1d3c74f3bed9a7c4eaec31c47785ba7f
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 8cf077f0384b7ae4c9d8d56492da4232
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 23ff663a7d9068040aab4eb93825c0ce
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: c726f57dfd3fd6d4facf5f713795cfad
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 2060a838e688b8644ae8a08b867f1740
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- JetBrains Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -132,6 +132,7 @@ GameObject:
- component: {fileID: 330585544}
- component: {fileID: 330585547}
- component: {fileID: 330585548}
- component: {fileID: 330585549}
m_Layer: 0
m_Name: camera_main
m_TagString: MainCamera
@@ -194,7 +195,7 @@ Camera:
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_ForceIntoRT: 1
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
@@ -289,6 +290,19 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
CustomBlends: {fileID: 0}
--- !u!114 &330585549
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 330585543}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 351d6b8577644d599058e76fa02a11c0, type: 3}
m_Name:
m_EditorClassIdentifier:
<Camera>k__BackingField: {fileID: 330585545}
--- !u!1 &379890219
GameObject:
m_ObjectHideFlags: 0
@@ -337,7 +351,7 @@ MonoBehaviour:
m_PanelSettings: {fileID: 11400000, guid: c4c93f1ef3cc3324696c439305139cbb, type: 2}
m_ParentUI: {fileID: 0}
sourceAsset: {fileID: 9197481963319205126, guid: 15eee085e73feee4eba58c073b1455c5, type: 3}
m_SortingOrder: 0
m_SortingOrder: 10000
m_WorldSpaceSizeMode: 1
m_WorldSpaceWidth: 1920
m_WorldSpaceHeight: 1080
@@ -386,7 +400,7 @@ MonoBehaviour:
Rules:
- Type: 0
Name:
GameObject: {fileID: 647954086}
GameObject: {fileID: 0}
Ordinal: 0
Priority: 0
IconType: 0
@@ -444,7 +458,7 @@ MonoBehaviour:
m_PanelSettings: {fileID: 11400000, guid: c4c93f1ef3cc3324696c439305139cbb, type: 2}
m_ParentUI: {fileID: 0}
sourceAsset: {fileID: 9197481963319205126, guid: c2f32d01bf5f9d644aee3c2a41b14a66, type: 3}
m_SortingOrder: 1000
m_SortingOrder: 9999
m_WorldSpaceSizeMode: 1
m_WorldSpaceWidth: 1920
m_WorldSpaceHeight: 1080
@@ -476,79 +490,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Document: {fileID: 638618785}
--- !u!1 &647954086
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 647954090}
- component: {fileID: 647954089}
- component: {fileID: 647954088}
- component: {fileID: 647954087}
m_Layer: 0
m_Name: scene_context
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &647954087
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647954086}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4238ea1a17e342e583cdd929103a22c6, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &647954088
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647954086}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 351d6b8577644d599058e76fa02a11c0, type: 3}
m_Name:
m_EditorClassIdentifier:
<Camera>k__BackingField: {fileID: 330585545}
--- !u!114 &647954089
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647954086}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8dd28652b58c4d689ab3f2f9354d7589, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Installers:
- {fileID: 647954087}
--- !u!4 &647954090
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 647954086}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &735095186
GameObject:
m_ObjectHideFlags: 0
@@ -680,7 +621,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_DebugOverlayView: {fileID: 1809244332}
m_GameVersionOverlay: {fileID: 638618787}
m_NetworkStatsOverlay: {fileID: 735095186}
m_NetworkStatsOverlay: {fileID: 735095187}
--- !u!4 &1493735124
Transform:
m_ObjectHideFlags: 0
@@ -747,7 +688,7 @@ MonoBehaviour:
m_PanelSettings: {fileID: 11400000, guid: c4c93f1ef3cc3324696c439305139cbb, type: 2}
m_ParentUI: {fileID: 0}
sourceAsset: {fileID: 9197481963319205126, guid: efa5e4d1b21059c448f8a23fbe41890a, type: 3}
m_SortingOrder: 0
m_SortingOrder: 9999
m_WorldSpaceSizeMode: 1
m_WorldSpaceWidth: 1920
m_WorldSpaceHeight: 1080
@@ -768,7 +709,6 @@ MonoBehaviour:
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 647954090}
- {fileID: 330585546}
- {fileID: 1468139327}
- {fileID: 1493735124}

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 19101, guid: 0000000000000000e000000000000000, type: 0}
m_Name: panel_rebootkit
m_Name: panel_rebootkit_overlay
m_EditorClassIdentifier:
themeUss: {fileID: -4733365628477956816, guid: 5dd55e8f9f3419144b6d6fc3fcc478d0, type: 3}
m_DisableNoThemeWarning: 0
@@ -26,7 +26,7 @@ MonoBehaviour:
m_ReferenceResolution: {x: 1200, y: 800}
m_ScreenMatchMode: 0
m_Match: 0
m_SortingOrder: 0
m_SortingOrder: 500
m_TargetDisplay: 0
m_BindingLogLevel: 0
m_ClearDepthStencil: 1

View File

@@ -1,4 +1,3 @@
@import url("/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss");
.rr-base {
@@ -66,10 +65,10 @@
/* Text Field */
.rr-text-field {
min-height: 32px;
padding: 0;
margin: 4px;
--unity-cursor-color: #dedede;
}
@@ -78,7 +77,7 @@
border-color: #373737;
border-radius: 3px;
border-width: 1px;
padding: 4px;
}
@@ -136,9 +135,11 @@
width: 72px;
}
.rr__debug-label {
color: #00ff00;
-unity-font-style: bold;
font-size: 18px;
text-shadow: 1px 1px 16px rgba(0, 0, 0, 0.75);
.rr__debug-overlay-label {
color: #ffffff;
-unity-text-outline-color: black;
-unity-text-outline-width: 1px;
font-size: 16px;
-unity-font-definition: url("project://database/Assets/RebootKit/Runtime/Engine/core_assets/fonts/JetBrainsMono-Bold.ttf");
text-shadow: 0 2px 16px rgba(0, 0, 0, 0.75);
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd"
>
</engine:UXML>

View File

@@ -1,3 +1,3 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<engine:VisualElement name="root__container" style="flex-grow: 1;" />
</engine:UXML>
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<engine:VisualElement name="root__container" picking-mode="Ignore" style="flex-grow: 1;" />
</engine:UXML>

View File

@@ -0,0 +1,5 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<engine:VisualElement picking-mode="Ignore" style="flex-grow: 1;">
<engine:Label text="ver. 0.1.0 (RR_DEBUG)" name="rr-dev__version_label" style="position: absolute; right: 0; bottom: 0; -unity-text-align: upper-right; font-size: 14px; -unity-font-style: bold; -unity-font-definition: url(&quot;project://database/Assets/TextMesh%20Pro/Fonts/LiberationSans.ttf?fileID=12800000&amp;guid=e3265ab4bf004d28a9537516768c1c75&amp;type=3#LiberationSans&quot;); color: rgba(255, 255, 255, 0.7); -unity-text-outline-width: 1px; -unity-text-outline-color: rgba(0, 0, 0, 0.99); text-shadow: 0 1px 10px rgba(0, 0, 0, 0.59);" />
</engine:VisualElement>
</engine:UXML>

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f775bf05b095519478da40645323ba90
guid: c2f32d01bf5f9d644aee3c2a41b14a66
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}

View File

@@ -1,75 +0,0 @@
using NUnit.Framework;
using RebootKit.Engine.Foundation;
namespace Tests.Runtime.Engine {
interface ITestService {
int Value();
}
class TestServiceA : ITestService {
public const int k_ReturnValue = 1;
public int Value() {
return k_ReturnValue;
}
}
class TestServiceB : ITestService {
public const int k_ReturnValue = 2;
public int Value() {
return k_ReturnValue;
}
}
public class DIContextTests {
[Test]
public void Single_Bind_And_Resolve() {
DIContext context = new DIContext();
context.Bind<ITestService>(new TestServiceA());
ITestService testService = context.Resolve<ITestService>();
Assert.IsNotNull(testService, "Resolved service is null!");
Assert.IsTrue(testService.Value() == TestServiceA.k_ReturnValue, "Invalid return value of resolved service");
}
[Test]
public void Single_Bind_And_Field_Inject() {
DIContext context = new DIContext();
context.Bind<ITestService>(new TestServiceB());
TestObject obj = new TestObject();
context.Inject(obj);
Assert.IsNotNull(obj.Service, "obj.Service != null");
Assert.IsTrue(obj.Service.Value() == TestServiceB.k_ReturnValue);
}
[Test]
public void Single_Bind_And_Method_Inject() {
DIContext context = new DIContext();
context.Bind<ITestService>(new TestServiceA());
TestObjectMethod obj = new TestObjectMethod();
context.Inject(obj);
Assert.IsNotNull(obj.Service, "obj.Service != null");
Assert.IsTrue(obj.Service.Value() == TestServiceA.k_ReturnValue);
}
class TestObject {
[Inject] public ITestService Service;
}
class TestObjectMethod {
public ITestService Service;
[Inject]
public void Setup(ITestService service) {
Service = service;
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: fb99eda81d534ddcb15cff09441d98bc
timeCreated: 1742002479