From 569b1d00b3f5c5c2f0feff05edb26551c3c93272 Mon Sep 17 00:00:00 2001 From: Brajanowski Date: Sat, 15 Mar 2025 12:33:50 +0100 Subject: [PATCH] DI --- Runtime/Engine/Code/EngineConfigAsset.cs | 2 - Runtime/Engine/Code/Foundation/DIContext.cs | 117 ++++++++++++++++++ .../Engine/Code/Foundation/DIContext.cs.meta | 3 + Runtime/Engine/Code/Foundation/EntryPoint.cs | 1 - Runtime/Engine/Code/Foundation/IService.cs | 22 ++++ ...rvicesLocator.cs.meta => IService.cs.meta} | 0 Runtime/Engine/Code/Foundation/SceneDI.cs | 22 ++++ .../Engine/Code/Foundation/SceneDI.cs.meta | 3 + .../Engine/Code/Foundation/ServicesLocator.cs | 94 -------------- Runtime/Engine/Code/MainSceneInstaller.cs | 14 +++ .../Engine/Code/MainSceneInstaller.cs.meta | 3 + Runtime/Engine/Code/RR.cs | 57 ++++----- .../Services/Console/CVarFieldInjector.cs | 59 +++++++++ .../Console/CVarFieldInjector.cs.meta | 3 + .../Code/Services/Console/ConsoleService.cs | 14 ++- .../Engine/Code/Services/Game/GameService.cs | 7 +- ...bootKit.asmdef => RebootKit.Engine.asmdef} | 2 +- ...mdef.meta => RebootKit.Engine.asmdef.meta} | 0 Runtime/FPPKit/Code/PlayerController.cs | 21 ++-- Runtime/FPPKit/RebootKit.FPPKit.asmdef | 20 +++ Runtime/FPPKit/RebootKit.FPPKit.asmdef.meta | 7 ++ Tests.meta | 3 + Tests/Runtime.meta | 3 + Tests/Runtime/Engine.meta | 8 ++ Tests/Runtime/Engine/DIContextTests.cs | 78 ++++++++++++ Tests/Runtime/Engine/DIContextTests.cs.meta | 3 + .../Engine/RebootKit.Engine.Tests.asmdef | 20 +++ .../Engine/RebootKit.Engine.Tests.asmdef.meta | 7 ++ 28 files changed, 446 insertions(+), 147 deletions(-) create mode 100644 Runtime/Engine/Code/Foundation/DIContext.cs create mode 100644 Runtime/Engine/Code/Foundation/DIContext.cs.meta create mode 100644 Runtime/Engine/Code/Foundation/IService.cs rename Runtime/Engine/Code/Foundation/{ServicesLocator.cs.meta => IService.cs.meta} (100%) create mode 100644 Runtime/Engine/Code/Foundation/SceneDI.cs create mode 100644 Runtime/Engine/Code/Foundation/SceneDI.cs.meta delete mode 100644 Runtime/Engine/Code/Foundation/ServicesLocator.cs create mode 100644 Runtime/Engine/Code/MainSceneInstaller.cs create mode 100644 Runtime/Engine/Code/MainSceneInstaller.cs.meta create mode 100644 Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs create mode 100644 Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs.meta rename Runtime/Engine/{RebootKit.asmdef => RebootKit.Engine.asmdef} (96%) rename Runtime/Engine/{RebootKit.asmdef.meta => RebootKit.Engine.asmdef.meta} (100%) create mode 100644 Runtime/FPPKit/RebootKit.FPPKit.asmdef create mode 100644 Runtime/FPPKit/RebootKit.FPPKit.asmdef.meta create mode 100644 Tests.meta create mode 100644 Tests/Runtime.meta create mode 100644 Tests/Runtime/Engine.meta create mode 100644 Tests/Runtime/Engine/DIContextTests.cs create mode 100644 Tests/Runtime/Engine/DIContextTests.cs.meta create mode 100644 Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef create mode 100644 Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef.meta diff --git a/Runtime/Engine/Code/EngineConfigAsset.cs b/Runtime/Engine/Code/EngineConfigAsset.cs index a56888e..1beccff 100644 --- a/Runtime/Engine/Code/EngineConfigAsset.cs +++ b/Runtime/Engine/Code/EngineConfigAsset.cs @@ -15,8 +15,6 @@ namespace RebootKit.Engine { public ServiceAsset GameService; [Space] - public ServiceAsset[] Services; - public GameModeAsset StartingGameMode; } } \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/DIContext.cs b/Runtime/Engine/Code/Foundation/DIContext.cs new file mode 100644 index 0000000..6ed0522 --- /dev/null +++ b/Runtime/Engine/Code/Foundation/DIContext.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace RebootKit.Engine.Foundation { + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)] + public class InjectAttribute : Attribute { + } + + public class DIContext { + private const BindingFlags k_fieldsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + private const BindingFlags k_methodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + public interface IFieldInjector { + bool Inject(FieldInfo field, object target, DIContext context); + } + + private static readonly Logger Logger = new(nameof(DIContext)); + + private readonly Dictionary _bindingsMaps = new(); + private readonly List _fieldInjectors = new(); + + public DIContext() { + Bind(this); + + AddInjector(new InjectAttributeFieldInjector()); + } + + public void AddInjector(IFieldInjector injector) { + _fieldInjectors.Add(injector); + } + + public void Bind(Type type, object obj) { + if (!_bindingsMaps.TryAdd(type, obj)) { + Logger.Error($"Cannot bind to '{type}', slot is already occupied"); + } + } + + public void Bind(TBind obj) { + Bind(typeof(TBind), obj); + } + + public object Resolve(Type type) { + if (_bindingsMaps.TryGetValue(type, out object obj)) { + return obj; + } + + Logger.Error($"Couldn't resolve `{type}`"); + return null; + } + + public T Resolve() { + return (T) Resolve(typeof(T)); + } + + public void Inject(T target) { + Type type = typeof(T); + + foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) { + if (!InjectField(field, target)) { + return; + } + } + + foreach (MethodInfo method in type.GetMethods(k_methodsBindingFlags)) { + if (!Attribute.IsDefined(method, typeof(InjectAttribute))) { + continue; + } + + Type[] paramsTypes = method.GetParameters() + .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) { + Logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`"); + } + } + + method.Invoke(target, instances); + } + } + + private bool InjectField(FieldInfo field, object target) { + for (int i = _fieldInjectors.Count - 1; i >= 0; i--) { + if (_fieldInjectors[i].Inject(field, target, this)) { + return true; + } + } + + return false; + } + + private 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) { + Logger.Error($"Cannot resolve `{field.FieldType}`"); + return false; + } + + field.SetValue(target, instance); + return true; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/DIContext.cs.meta b/Runtime/Engine/Code/Foundation/DIContext.cs.meta new file mode 100644 index 0000000..303e9dc --- /dev/null +++ b/Runtime/Engine/Code/Foundation/DIContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cee4133e4a594126868703cc035663cd +timeCreated: 1742001112 \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/EntryPoint.cs b/Runtime/Engine/Code/Foundation/EntryPoint.cs index 2810675..2efc38d 100644 --- a/Runtime/Engine/Code/Foundation/EntryPoint.cs +++ b/Runtime/Engine/Code/Foundation/EntryPoint.cs @@ -16,7 +16,6 @@ namespace RebootKit.Engine.Foundation { } RR.Shared = null; - ServicesLocator.Shared.UnRegisterAll(); _cancellationTokenSource = new CancellationTokenSource(); SceneManager.LoadScene(0, LoadSceneMode.Single); diff --git a/Runtime/Engine/Code/Foundation/IService.cs b/Runtime/Engine/Code/Foundation/IService.cs new file mode 100644 index 0000000..e592a9d --- /dev/null +++ b/Runtime/Engine/Code/Foundation/IService.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; +using Cysharp.Threading.Tasks; +using UnityEngine; + +namespace RebootKit.Engine.Foundation { + public interface IService : IDisposable { + UniTask OnInit(CancellationToken cancellationToken); + } + + public abstract class ServiceMonoBehaviour : MonoBehaviour, IService { + public abstract UniTask OnInit(CancellationToken cancellationToken); + public abstract void Dispose(); + } + + public abstract class ServiceAsset : ScriptableObject where T : IService { + public abstract T Create(); + } + + public abstract class ServiceAsset : ServiceAsset { + } +} \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/ServicesLocator.cs.meta b/Runtime/Engine/Code/Foundation/IService.cs.meta similarity index 100% rename from Runtime/Engine/Code/Foundation/ServicesLocator.cs.meta rename to Runtime/Engine/Code/Foundation/IService.cs.meta diff --git a/Runtime/Engine/Code/Foundation/SceneDI.cs b/Runtime/Engine/Code/Foundation/SceneDI.cs new file mode 100644 index 0000000..a7f6f0c --- /dev/null +++ b/Runtime/Engine/Code/Foundation/SceneDI.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace RebootKit.Engine.Foundation { + public interface IDependencyInstaller { + void Install(DIContext context); + } + + public abstract class SceneDependencyInstaller : MonoBehaviour, IDependencyInstaller { + public abstract void Install(DIContext context); + } + + public class SceneDI : MonoBehaviour, IDependencyInstaller { + [SerializeField] + private SceneDependencyInstaller[] _sceneInstallers; + + public void Install(DIContext context) { + foreach (SceneDependencyInstaller installer in _sceneInstallers) { + installer.Install(context); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/SceneDI.cs.meta b/Runtime/Engine/Code/Foundation/SceneDI.cs.meta new file mode 100644 index 0000000..6821848 --- /dev/null +++ b/Runtime/Engine/Code/Foundation/SceneDI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8dd28652b58c4d689ab3f2f9354d7589 +timeCreated: 1742006992 \ No newline at end of file diff --git a/Runtime/Engine/Code/Foundation/ServicesLocator.cs b/Runtime/Engine/Code/Foundation/ServicesLocator.cs deleted file mode 100644 index 4ed8e40..0000000 --- a/Runtime/Engine/Code/Foundation/ServicesLocator.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Cysharp.Threading.Tasks; -using UnityEngine; - -namespace RebootKit.Engine.Foundation { - public interface IService : IDisposable { - UniTask OnInit(CancellationToken cancellationToken); - } - - public abstract class ServiceMonoBehaviour : MonoBehaviour, IService { - public abstract UniTask OnInit(CancellationToken cancellationToken); - public abstract void Dispose(); - } - - public abstract class ServiceAsset : ScriptableObject where T : IService { - public abstract T Create(); - } - - public abstract class ServiceAsset : ServiceAsset { - } - - public class ServicesLocator { - private static readonly Logger _logger = new(nameof(ServicesLocator)); - - private static ServicesLocator _shared; - - public static ServicesLocator Shared { - get { - if (_shared == null) { - _shared = new ServicesLocator(); - } - - return _shared; - } - } - - private readonly Dictionary _servicesMap = new(); - - public void Register(IService service, Type type) { - if (!_servicesMap.TryAdd(type, service)) { - _logger.Error($"There is already game service of type `{type}`"); - return; - } - - service.OnInit(default); - - _logger.Info($"Registered service of type {type.Name}"); - } - - public void Register(T service) where T : IService { - Register(service, service.GetType()); - } - - public async UniTask RegisterAsync(T service, CancellationToken cancellationToken = default) where T : IService { - if (!_servicesMap.TryAdd(typeof(T), service)) { - _logger.Error($"There is already game service of type `{typeof(T).Name}`"); - return; - } - - await service.OnInit(cancellationToken); - _logger.Info($"Registered service of type {typeof(T).Name}"); - } - - public void UnRegister(Type type) { - if (_servicesMap.TryGetValue(type, out IService service)) { - service.Dispose(); - _servicesMap.Remove(type); - _logger.Info($"Unregistered service of type {type.Name}"); - } - } - - public void UnRegisterAll() { - foreach (IService service in _servicesMap.Values) { - service.Dispose(); - } - _servicesMap.Clear(); - } - - public IService Get(Type type) { - if (_servicesMap.TryGetValue(type, out IService service)) { - return service; - } - - Debug.LogAssertionFormat($"Couldn't find service of type `{type}`"); - return null; - } - - public T Get() where T : IService { - return (T) Get(typeof(T)); - } - } -} \ No newline at end of file diff --git a/Runtime/Engine/Code/MainSceneInstaller.cs b/Runtime/Engine/Code/MainSceneInstaller.cs new file mode 100644 index 0000000..a827b47 --- /dev/null +++ b/Runtime/Engine/Code/MainSceneInstaller.cs @@ -0,0 +1,14 @@ +using RebootKit.Engine.Foundation; +using RebootKit.Engine.Graphics; +using UnityEngine; + +namespace RebootKit.Engine { + public class MainSceneInstaller : SceneDependencyInstaller { + [SerializeField] + private MainCameraService _mainCameraService; + + public override void Install(DIContext context) { + context.Bind(_mainCameraService); + } + } +} \ No newline at end of file diff --git a/Runtime/Engine/Code/MainSceneInstaller.cs.meta b/Runtime/Engine/Code/MainSceneInstaller.cs.meta new file mode 100644 index 0000000..5bfdf98 --- /dev/null +++ b/Runtime/Engine/Code/MainSceneInstaller.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4238ea1a17e342e583cdd929103a22c6 +timeCreated: 1742007242 \ No newline at end of file diff --git a/Runtime/Engine/Code/RR.cs b/Runtime/Engine/Code/RR.cs index 6ff5f29..0db9bb4 100644 --- a/Runtime/Engine/Code/RR.cs +++ b/Runtime/Engine/Code/RR.cs @@ -11,15 +11,13 @@ using UnityEngine.SceneManagement; using Logger = RebootKit.Engine.Foundation.Logger; namespace RebootKit.Engine { - public abstract class RAsset : ScriptableObject { - } public class RR : IDisposable { public static RR Shared { get; set; } - private static readonly Logger _logger = new Logger("RR"); + private static readonly Logger Logger = new("RR"); private EngineConfigAsset _engineConfigAsset; @@ -28,40 +26,46 @@ namespace RebootKit.Engine { private WorldService _worldService; private GameService _gameService; + private DIContext _diContext; + public void Dispose() { } public async UniTask Init(CancellationToken cancellationToken) { - _logger.Info("Waking up"); + Logger.Info("Waking up"); - _logger.Info("Loading engine config"); + Logger.Info("Loading engine config"); _engineConfigAsset = Resources.Load(RConsts.EngineConfigResourcesPath); if (_engineConfigAsset == null) { - _logger.Error($"Couldn't load engine config from resources: {RConsts.EngineConfigResourcesPath}"); + Logger.Error($"Couldn't load engine config from resources: {RConsts.EngineConfigResourcesPath}"); return; } - _logger.Info("Loading engine found"); + Logger.Info("Loading engine found"); + + _diContext = new DIContext(); + _diContext.AddInjector(new CVarFieldInjector()); await CreateCoreServices(cancellationToken); - await RegisterServiceAssetsFromConfig(cancellationToken); await RegisterServicesFromMainSceneAsync(cancellationToken); } public async UniTask Run(CancellationToken cancellationToken) { - _logger.Info($"Starting initial game mode"); + Logger.Info($"Starting initial game mode"); await _gameService.Start(_engineConfigAsset.StartingGameMode, cancellationToken); } private async UniTask CreateServiceAsync(ServiceAsset asset, CancellationToken cancellationToken = default) where TService : IService { TService service = asset.Create(); - await ServicesLocator.Shared.RegisterAsync(service, cancellationToken); + _diContext.Bind(service); + _diContext.Inject(service); + await service.OnInit(cancellationToken); return service; } private async UniTask CreateCoreServices(CancellationToken cancellationToken) { - _logger.Debug("Registering core services"); + Logger.Debug("Registering core services"); _consoleService = await CreateServiceAsync(_engineConfigAsset.ConsoleService, cancellationToken); _inputService = await CreateServiceAsync(_engineConfigAsset.InputService, cancellationToken); @@ -69,38 +73,23 @@ namespace RebootKit.Engine { _gameService = await CreateServiceAsync(_engineConfigAsset.GameService, cancellationToken); } - private async UniTask RegisterServiceAssetsFromConfig(CancellationToken cancellationToken = default) { - if (_engineConfigAsset.Services.Length == 0) { - return; - } - - _logger.Info($"Registering {_engineConfigAsset.Services.Length} services"); - foreach (ServiceAsset service in _engineConfigAsset.Services) { - await ServicesLocator.Shared.RegisterAsync(service.Create(), cancellationToken); - } - } - private async UniTask RegisterServicesFromMainSceneAsync(CancellationToken cancellationToken = default) { - _logger.Info("Looking for services in mainscene"); - int foundCount = 0; - GameObject[] gameObjects = SceneManager.GetSceneByBuildIndex(RConsts.MainSceneBuildIndex).GetRootGameObjects(); - if (gameObjects.Length == 0) { - _logger.Info("No services found in main scene"); return; } foreach (GameObject root in gameObjects) { - ServiceMonoBehaviour[] services = root.GetComponentsInChildren(); + SceneDI di = root.GetComponent(); - foreach (ServiceMonoBehaviour service in services) { - await ServicesLocator.Shared.RegisterAsync(service, cancellationToken); - foundCount += 1; + if (di == null) { + continue; } + + di.Install(_diContext); } - _logger.Info($"Found and registered {foundCount} services from active scene"); + await UniTask.Yield(); } // @@ -108,10 +97,6 @@ namespace RebootKit.Engine { // /// Services - public static T Service() where T : IService { - return ServicesLocator.Shared.Get(); - } - public static ConsoleService Console() => Shared._consoleService; public static InputService Input() => Shared._inputService; public static WorldService World() => Shared._worldService; diff --git a/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs b/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs new file mode 100644 index 0000000..cb9256c --- /dev/null +++ b/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs @@ -0,0 +1,59 @@ +using System; +using System.Reflection; +using RebootKit.Engine.Foundation; + +namespace RebootKit.Engine.Services.Console { + [AttributeUsage(AttributeTargets.Field)] + public class CVarAttribute : Attribute { + public string Name { get; } + public CVarValue Value { get; } + + public CVarAttribute(string name, float defaultValue) { + Name = name; + Value = new CVarValue(defaultValue); + } + + public CVarAttribute(string name, double defaultValue) { + Name = name; + Value = new CVarValue(defaultValue); + } + + public CVarAttribute(string name, string defaultValue) { + Name = name; + Value = new CVarValue(defaultValue); + } + + public CVarAttribute(string name, int defaultValue) { + Name = name; + Value = new CVarValue(defaultValue); + } + } + + public class CVarFieldInjector : DIContext.IFieldInjector { + private static readonly Logger Logger = new(nameof(CVarFieldInjector)); + + public bool Inject(FieldInfo field, object target, DIContext context) { + if (!Attribute.IsDefined(field, typeof(CVarAttribute))) { + return false; + } + + ConsoleService console = context.Resolve(); + if (console == null) { + Logger.Error($"Cannot inject field because cannot resolve `{nameof(ConsoleService)}`"); + return false; + } + + CVarAttribute cvarAttribute = field.GetCustomAttribute(); + CVar cvar = console.GetCVar(cvarAttribute.Name); + + if (cvar == null) { + cvar = new CVar(cvarAttribute.Name, cvarAttribute.Value); + console.Replace(cvar); + } + + field.SetValue(target, cvar); + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs.meta b/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs.meta new file mode 100644 index 0000000..7e395e5 --- /dev/null +++ b/Runtime/Engine/Code/Services/Console/CVarFieldInjector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1ce6213dda8c4f8d9eafe4afd1614a86 +timeCreated: 1742005021 \ No newline at end of file diff --git a/Runtime/Engine/Code/Services/Console/ConsoleService.cs b/Runtime/Engine/Code/Services/Console/ConsoleService.cs index 1215b25..544f04e 100644 --- a/Runtime/Engine/Code/Services/Console/ConsoleService.cs +++ b/Runtime/Engine/Code/Services/Console/ConsoleService.cs @@ -62,16 +62,26 @@ namespace RebootKit.Engine.Services.Console { _ui.Clear(); _ui.Write("Hello shelf\n"); } - + public void Dispose() { if (_ui != null) { UnityEngine.Object.Destroy(_ui); _ui = null; } - + _config.ToggleAction.Action.performed -= OnToggleAction; } + public bool CVarExists(string name) { + foreach (CVar cvar in _cvars) { + if (cvar.Name.Equals(name)) { + return true; + } + } + + return false; + } + public CVar GetCVar(string name) { foreach (CVar cvar in _cvars) { if (cvar.Name.Equals(name)) { diff --git a/Runtime/Engine/Code/Services/Game/GameService.cs b/Runtime/Engine/Code/Services/Game/GameService.cs index 382bd03..5cea58c 100644 --- a/Runtime/Engine/Code/Services/Game/GameService.cs +++ b/Runtime/Engine/Code/Services/Game/GameService.cs @@ -1,11 +1,12 @@ using System.Threading; using Cysharp.Threading.Tasks; using RebootKit.Engine.Foundation; +using RebootKit.Engine.Services.Console; namespace RebootKit.Engine.Services.Game { public class GameService : IService { - private static readonly Logger _logger = new(nameof(GameService)); - + private static readonly Logger Logger = new(nameof(GameService)); + private GameModeAsset _gameModeAsset; private GameMode _gameMode; private bool _running; @@ -28,7 +29,7 @@ namespace RebootKit.Engine.Services.Game { private async UniTask Run(CancellationToken cancellationToken) { if (_gameMode == null) { - _logger.Error("Trying to run game without game mode"); + Logger.Error("Trying to run game without game mode"); return; } diff --git a/Runtime/Engine/RebootKit.asmdef b/Runtime/Engine/RebootKit.Engine.asmdef similarity index 96% rename from Runtime/Engine/RebootKit.asmdef rename to Runtime/Engine/RebootKit.Engine.asmdef index 6069bc9..db1e02a 100644 --- a/Runtime/Engine/RebootKit.asmdef +++ b/Runtime/Engine/RebootKit.Engine.asmdef @@ -1,5 +1,5 @@ { - "name": "com.szafastudio.szafakit", + "name": "RebootKit.Engine", "rootNamespace": "RebootKit", "references": [ "GUID:6055be8ebefd69e48b49212b09b47b2f", diff --git a/Runtime/Engine/RebootKit.asmdef.meta b/Runtime/Engine/RebootKit.Engine.asmdef.meta similarity index 100% rename from Runtime/Engine/RebootKit.asmdef.meta rename to Runtime/Engine/RebootKit.Engine.asmdef.meta diff --git a/Runtime/FPPKit/Code/PlayerController.cs b/Runtime/FPPKit/Code/PlayerController.cs index 05349fc..4a4cb91 100644 --- a/Runtime/FPPKit/Code/PlayerController.cs +++ b/Runtime/FPPKit/Code/PlayerController.cs @@ -21,14 +21,19 @@ namespace RebootKit.FPPKit { public InputActionReference PrimaryActionReference; public InputActionReference SecondaryActionReference; } + + [CVar("fpp_cam_look_sens", 0.25f)] + private CVar _sensitivityCVar; + + [CVar("p_move_speed", 4.0f)] + private CVar _movementSpeedCVar; + + [Inject] + private InputService _inputService; private Config _config; - private FPPActor _player; - private CVar _sensitivityCVar; - private CVar _movementSpeedCVar; - public PlayerController(Config config) { _config = config; } @@ -37,8 +42,8 @@ namespace RebootKit.FPPKit { } public async UniTask OnStart(CancellationToken cancellationToken) { - RR.Service().LockCursor(); - RR.Service().EnableControls(); + _inputService.LockCursor(); + _inputService.EnableControls(); _sensitivityCVar = RR.CVarNumber("fpp_cam_look_sens", 0.25f); _movementSpeedCVar = RR.CVarNumber("p_move_speed", 4.0f); @@ -49,8 +54,8 @@ namespace RebootKit.FPPKit { } public void OnStop() { - RR.Service().DisableControls(); - RR.Service().UnlockCursor(); + _inputService.DisableControls(); + _inputService.UnlockCursor(); Object.Destroy(_player); RR.World().KillActor(_player); diff --git a/Runtime/FPPKit/RebootKit.FPPKit.asmdef b/Runtime/FPPKit/RebootKit.FPPKit.asmdef new file mode 100644 index 0000000..9970376 --- /dev/null +++ b/Runtime/FPPKit/RebootKit.FPPKit.asmdef @@ -0,0 +1,20 @@ +{ + "name": "RebootKit.FPPKit", + "rootNamespace": "RebootKit.FPPKit", + "references": [ + "GUID:284059c7949783646b281a1b815580e6", + "GUID:f51ebe6a0ceec4240a699833d6309b23", + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:4307f53044263cf4b835bd812fc161a4", + "GUID:9e24947de15b9834991c9d8411ea37cf" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/FPPKit/RebootKit.FPPKit.asmdef.meta b/Runtime/FPPKit/RebootKit.FPPKit.asmdef.meta new file mode 100644 index 0000000..96570c4 --- /dev/null +++ b/Runtime/FPPKit/RebootKit.FPPKit.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4d76b28af993d614f825cb34f438930f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 0000000..0c4f1f3 --- /dev/null +++ b/Tests.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ac0e7d4df8ce40909e9802720077c57e +timeCreated: 1742002062 \ No newline at end of file diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta new file mode 100644 index 0000000..d9edfb5 --- /dev/null +++ b/Tests/Runtime.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 38d2e5d9c5d2492fb523c373872899e0 +timeCreated: 1742002103 \ No newline at end of file diff --git a/Tests/Runtime/Engine.meta b/Tests/Runtime/Engine.meta new file mode 100644 index 0000000..cf3e370 --- /dev/null +++ b/Tests/Runtime/Engine.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4abcb79b93f19b448aa8369c5894eabc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Engine/DIContextTests.cs b/Tests/Runtime/Engine/DIContextTests.cs new file mode 100644 index 0000000..3fddf47 --- /dev/null +++ b/Tests/Runtime/Engine/DIContextTests.cs @@ -0,0 +1,78 @@ +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; +using RebootKit.Engine.Foundation; + +namespace Tests.Runtime.Engine { + internal interface ITestService { + int Value(); + } + + internal class TestServiceA : ITestService { + public const int k_ReturnValue = 1; + + public int Value() { + return k_ReturnValue; + } + } + + internal 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(); + context.Bind(new TestServiceA()); + + ITestService testService = context.Resolve(); + Assert.IsNotNull(testService, "Resolved service is null!"); + + Assert.IsTrue(testService.Value() == TestServiceA.k_ReturnValue, "Invalid return value of resolved service"); + } + + private class TestObject { + [Inject] + public ITestService Service; + } + + [Test] + public void Single_Bind_And_Field_Inject() { + DIContext context = new(); + context.Bind(new TestServiceB()); + + TestObject obj = new(); + context.Inject(obj); + + Assert.IsNotNull(obj.Service, "obj.Service != null"); + Assert.IsTrue(obj.Service.Value() == TestServiceB.k_ReturnValue); + } + + + private class TestObjectMethod { + public ITestService Service; + + [Inject] + public void Setup(ITestService service) { + Service = service; + } + } + + [Test] + public void Single_Bind_And_Method_Inject() { + DIContext context = new(); + context.Bind(new TestServiceA()); + + TestObjectMethod obj = new(); + context.Inject(obj); + + Assert.IsNotNull(obj.Service, "obj.Service != null"); + Assert.IsTrue(obj.Service.Value() == TestServiceA.k_ReturnValue); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Engine/DIContextTests.cs.meta b/Tests/Runtime/Engine/DIContextTests.cs.meta new file mode 100644 index 0000000..37ec3e6 --- /dev/null +++ b/Tests/Runtime/Engine/DIContextTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fb99eda81d534ddcb15cff09441d98bc +timeCreated: 1742002479 \ No newline at end of file diff --git a/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef b/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef new file mode 100644 index 0000000..01b0ee1 --- /dev/null +++ b/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef @@ -0,0 +1,20 @@ +{ + "name": "RebootKit.Engine.Tests", + "rootNamespace": "", + "references": [ + "GUID:284059c7949783646b281a1b815580e6", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:27619889b8ba8c24980f49ee34dbb44a" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef.meta b/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef.meta new file mode 100644 index 0000000..22fc352 --- /dev/null +++ b/Tests/Runtime/Engine/RebootKit.Engine.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 863a7983695eedb40a4fb34e2a16b5ba +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: