common extensions and some utilities

This commit is contained in:
2025-03-19 00:18:40 +01:00
parent f80fd22a00
commit e62bd2aa6d
35 changed files with 323 additions and 58 deletions

View File

@@ -1,12 +1,12 @@
<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 style="flex-grow: 1; background-color: rgba(176, 137, 137, 0.69); max-height: 100%; max-width: none; height: 100%;">
<engine:VisualElement name="console-window" enabled="true" class="window-bg" style="flex-grow: 1; text-shadow: 0 0 0 rgba(255, 255, 255, 0); position: relative; width: 100%; height: 100%; max-height: 100%;">
<engine:VisualElement name="console-window" enabled="true" class="window-bg" style="flex-grow: 1; text-shadow: 0 0 0 rgba(255, 255, 255, 0); position: relative; width: 100%; height: 50%; max-height: 50%;">
<engine:VisualElement name="VisualElement" enabled="true" class="window-content rr-base" style="flex-grow: 1; align-content: stretch; align-self: auto; justify-content: flex-start; align-items: stretch;">
<engine:ScrollView name="console-scrollview" class="rr-scroll-view" style="max-height: none; height: 100%;">
<engine:Label text="Label" />
<engine:Label text="Label" name="console-window-message" />
</engine:ScrollView>
<engine:VisualElement name="console-bottomrow" style="flex-grow: 1; flex-basis: auto; flex-direction: row; align-items: center; justify-content: flex-end; align-self: stretch; align-content: stretch; flex-wrap: nowrap;">
<engine:TextField name="console-textfield" class="rr-text-field" style="width: 92%;" />
<engine:TextField name="console-text-field" class="rr-text-field" style="width: 92%;" />
<engine:Button text="Submit" name="console-btn-submit" class="rr-button" />
</engine:VisualElement>
</engine:VisualElement>

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5074153f2624f03aeec7008af142fdc
timeCreated: 1742242187

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace RebootKit.Engine.Extensions {
public static class ColorEx {
public static Color With(this Color color, float? red = null, float? green = null, float? blue = null, float? alpha = null) {
return new Color(red ?? color.r,
green ?? color.g,
blue ?? color.b,
alpha ?? color.a);
}
public static string ToHexRGBA(this Color color) => $"#{ColorUtility.ToHtmlStringRGBA(color)}";
public static string ToHexRGB(this Color color) => $"#{ColorUtility.ToHtmlStringRGB(color)}";
public static Color Clamp01(this Color color) {
return new Color {
r = Mathf.Clamp01(color.r),
g = Mathf.Clamp01(color.g),
b = Mathf.Clamp01(color.b),
a = Mathf.Clamp01(color.a)
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 28161883ac0e4fbdb5e74d21207e0556
timeCreated: 1742242194

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace RebootKit.Engine.Extensions {
public static class GameObjectEx {
public static T GetOrAdd<T>(this GameObject gameObject) where T : Component {
T component = gameObject.GetComponent<T>();
if (component == null) {
component = gameObject.AddComponent<T>();
}
return component;
}
public static void DestroyChildren(this GameObject gameObject) {
gameObject.transform.DestroyChildren();
}
public static T OrNull<T>(this T instance) where T : Object {
return instance ? instance : null;
}
public static void SetLayersRecursively(this GameObject gameObject, int layer) {
gameObject.layer = layer;
gameObject.transform.ForEachChild(child => {
child.gameObject.SetLayersRecursively(layer);
});
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ae97bc354214364bab8f941e0e00971
timeCreated: 1742289183

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace RebootKit.Engine.Extensions {
public static class LayerMaskEx {
public static bool Contains(this LayerMask mask, int layerNumber) {
return ((1 << layerNumber) | mask) == mask;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7dfd76edaf614f77aba6039f2457f5f6
timeCreated: 1742289792

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace RebootKit.Engine.Extensions {
public static class ListEx {
public static T Random<T>(this IList<T> list) {
return list.Count == 0 ? default : list[UnityEngine.Random.Range(0, list.Count)];
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 69489a0a09924f89b47ac5198c1e75b1
timeCreated: 1742289937

View File

@@ -0,0 +1,19 @@
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace RebootKit.Engine.Extensions {
public static class TransformEx {
public static void DestroyChildren(this Transform transform) {
foreach (Transform child in transform) {
Object.Destroy(child);
}
}
public static void ForEachChild(this Transform transform, Action<Transform> action) {
foreach (Transform child in transform) {
action(child);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c54e8de68d1f4e5099b1abbf04d5d243
timeCreated: 1742289362

View File

@@ -0,0 +1,11 @@
using UnityEngine;
namespace RebootKit.Engine.Extensions {
public static class Vector3Ex {
public static Vector3 With(this Vector3 vec, float? x = null, float? y = null, float? z = null) {
return new Vector3(x ?? vec.x,
y ?? vec.y,
z ?? vec.z);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0e23e375c6042c8b7acaa5295ed63c2
timeCreated: 1742290228

View File

@@ -1,10 +1,7 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation
namespace RebootKit.Engine.Foundation
{
public abstract class ControllerAsset<TController> : ScriptableObject where TController : IController
public abstract class ControllerAsset<TController> : FactoryAsset<TController> where TController : class, IController
{
public abstract TController Create();
}
public abstract class ControllerAsset : ControllerAsset<IController>

View File

@@ -17,10 +17,10 @@ namespace RebootKit.Engine.Foundation {
}
private static readonly Logger Logger = new(nameof(DIContext));
private readonly Dictionary<Type, object> _bindingsMaps = new();
private readonly List<IFieldInjector> _fieldInjectors = new();
public DIContext() {
Bind(this);
@@ -54,6 +54,14 @@ namespace RebootKit.Engine.Foundation {
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 void Inject<T>(T target) {
Type type = typeof(T);
@@ -99,7 +107,7 @@ namespace RebootKit.Engine.Foundation {
if (!Attribute.IsDefined(field, typeof(InjectAttribute))) {
return false;
}
object instance = context.Resolve(field.FieldType);
if (instance == null) {

View File

@@ -38,7 +38,7 @@ namespace RebootKit.Engine.Foundation {
if (_isRunning) {
controller.OnStop();
}
_controllers.Remove(controller);
}
@@ -46,7 +46,7 @@ namespace RebootKit.Engine.Foundation {
if (_isRunning) {
return;
}
foreach (IController controller in _controllers) {
await controller.OnStart(cancellationToken);
}
@@ -58,7 +58,7 @@ namespace RebootKit.Engine.Foundation {
if (!_isRunning) {
return;
}
foreach (IController controller in _controllers) {
controller.OnStop();
}
@@ -74,19 +74,22 @@ namespace RebootKit.Engine.Foundation {
}
public static class ControllersManagerUtils {
public static void Add(this ControllersManager manager, ControllerAsset controllerAsset) {
manager.Add(controllerAsset.Create());
public static void Add(this ControllersManager manager, ControllerAsset asset, DIContext context) {
IController controller = asset.Create(context);
manager.Add(controller);
}
public static void Add(this ControllersManager manager, List<ControllerAsset> controllerAsset) {
public static void Add(this ControllersManager manager, List<ControllerAsset> controllerAsset, DIContext context) {
foreach (ControllerAsset asset in controllerAsset) {
manager.Add(asset.Create());
IController controller = asset.Create(context);
manager.Add(controller);
}
}
public static void Add(this ControllersManager manager, ControllerAsset[] controllerAsset) {
public static void Add(this ControllersManager manager, ControllerAsset[] controllerAsset, DIContext context) {
foreach (ControllerAsset asset in controllerAsset) {
manager.Add(asset.Create());
IController controller = asset.Create(context);
manager.Add(controller);
}
}
}

View File

@@ -0,0 +1,11 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation {
public interface IFactoryDI<out TProd> {
TProd Create(DIContext context);
}
public abstract class FactoryAsset<TProd> : ScriptableObject, IFactoryDI<TProd> where TProd : class {
public abstract TProd Create(DIContext context);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e7e7ee5fb51843b38826a4d6dac11204
timeCreated: 1742234966

View File

@@ -9,8 +9,7 @@ namespace RebootKit.Engine.Foundation {
public abstract void Dispose();
}
public abstract class ServiceAsset<T> : ScriptableObject where T : IService {
public abstract T Create();
public abstract class ServiceAsset<T> : FactoryAsset<T> where T : class, IService {
}
public abstract class ServiceAsset : ServiceAsset<IService> {

View File

@@ -12,6 +12,9 @@ namespace RebootKit.Engine.Foundation {
public class SceneDI : MonoBehaviour, IDependencyInstaller {
[SerializeField]
private SceneDependencyInstaller[] _sceneInstallers;
[SerializeField]
private SerializableGuid _guid;
public void Install(DIContext context) {
foreach (SceneDependencyInstaller installer in _sceneInstallers) {

View File

@@ -0,0 +1,53 @@
using System;
using UnityEngine;
namespace RebootKit.Engine.Foundation {
[Serializable]
public struct SerializableGuid : IEquatable<SerializableGuid> {
public static SerializableGuid Zero = new(0, 0, 0, 0);
[SerializeField, HideInInspector]
public uint A;
[SerializeField, HideInInspector]
public uint B;
[SerializeField, HideInInspector]
public uint C;
[SerializeField, HideInInspector]
public uint D;
public SerializableGuid(uint a, uint b, uint c, uint d) {
A = a;
B = b;
C = c;
D = d;
}
public SerializableGuid(Guid guid) {
byte[] bytes = guid.ToByteArray();
A = BitConverter.ToUInt32(bytes, 0);
B = BitConverter.ToUInt32(bytes, 4);
C = BitConverter.ToUInt32(bytes, 8);
D = BitConverter.ToUInt32(bytes, 12);
}
public bool Equals(SerializableGuid other) {
return A == other.A && B == other.B && C == other.C && D == other.D;
}
public override bool Equals(object obj) {
return obj is SerializableGuid other && Equals(other);
}
public override int GetHashCode() {
return HashCode.Combine(A, B, C, D);
}
public static SerializableGuid New() => new(Guid.NewGuid());
public static bool operator ==(SerializableGuid lhs, SerializableGuid rhs) => lhs.Equals(rhs);
public static bool operator !=(SerializableGuid lhs, SerializableGuid rhs) => !lhs.Equals(rhs);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c05fa8702ffc2cc489335afd290a9052

View File

@@ -10,6 +10,15 @@ using UnityEngine;
using UnityEngine.SceneManagement;
using Logger = RebootKit.Engine.Foundation.Logger;
/*
* Game starts:
* - Entry point creates instance of RR
* - Core Services are installed
* - Loads main scene and installs deps
* -
*/
namespace RebootKit.Engine {
public abstract class RAsset : ScriptableObject {
}
@@ -57,10 +66,9 @@ namespace RebootKit.Engine {
await _gameService.Start(_engineConfigAsset.StartingGameMode, cancellationToken);
}
private TService CreateService<TService>(ServiceAsset<TService> asset) where TService : IService {
TService service = asset.Create();
private TService CreateService<TService>(ServiceAsset<TService> asset) where TService : class, IService {
TService service = asset.Create(_diContext);
_diContext.Bind(service);
_diContext.Inject(service);
return service;
}

View File

@@ -44,14 +44,11 @@ namespace RebootKit.Engine.Services.Console {
public ConsoleService(Config config) {
_config = config;
}
public async UniTask OnWakeUp(CancellationToken cancellationToken) {
_logger.Info("Waking up");
_ui = UnityEngine.Object.Instantiate(_config.ConsoleUIPrefab);
UnityEngine.Object.DontDestroyOnLoad(_ui.gameObject);
await UniTask.Yield(cancellationToken);
_config.ToggleAction.Action.Enable();
_config.ToggleAction.Action.performed += OnToggleAction;
@@ -156,9 +153,11 @@ namespace RebootKit.Engine.Services.Console {
_ui.SetVisibility(!_ui.IsVisible);
if (_ui.IsVisible) {
// RR.Input().DisableControls();
RR.Input().DisableControls();
RR.Input().UnlockCursor();
} else {
RR.Input().EnableControls();
RR.Input().LockCursor();
}
}

View File

@@ -13,8 +13,10 @@ namespace RebootKit.Engine.Services.Console {
[SerializeField]
private bool _loadCVarsFromResources = true;
public override ConsoleService Create() {
public override ConsoleService Create(DIContext context) {
ConsoleService service = new(_config);
context.Inject(service);
foreach (CVar cvar in _initialCVars) {
service.Replace(cvar);
cvar.Reset();
@@ -28,7 +30,7 @@ namespace RebootKit.Engine.Services.Console {
}
}
return new ConsoleService(_config);
return service;
}
}
}

View File

@@ -1,26 +1,33 @@
using System.Text;
using UnityEngine;
using UnityEngine.UIElements;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Services.Console {
public class ConsoleUI : MonoBehaviour {
private static readonly Logger Logger = new(nameof(ConsoleUI));
private StringBuilder _content = new();
[SerializeField]
private UIDocument _document;
private Label _labelMessage;
private TextField _textField;
private Button _submitButton;
public bool IsVisible { get; private set; }
private void Awake() {
private void OnEnable() {
IsVisible = _document.enabled;
VisualElement root = _document.rootVisualElement;
return;
// _textField = root.Q<TextField>("console-text-field");
// _submitButton = root.Q<Button>("console-btn-submit");
// _submitButton.clicked += OnSubmitButtonClicked;
_labelMessage = root.Q<Label>("console-window-message");
_labelMessage.text = "SIEMA";
_textField = root.Q<TextField>("console-text-field");
_submitButton = root.Q<Button>("console-btn-submit");
_submitButton.clicked += OnSubmitButtonClicked;
}
private void OnSubmit(string input) {
@@ -30,22 +37,26 @@ namespace RebootKit.Engine.Services.Console {
public void SetVisibility(bool visible) {
_document.enabled = visible;
IsVisible = visible;
}
public void Write(string message) {
_content.Append(message);
//_textField.value = _content.ToString();
_labelMessage.text = _content.ToString();
_labelMessage.MarkDirtyRepaint();
}
public void Clear() {
_content.Clear();
_labelMessage.text = "";
if (_textField != null) {
_textField.value = "";
_textField.label = "";
}
}
private void OnSubmitButtonClicked() {
Logger.Info("Submit");
OnSubmit(_textField.value);
}
}

View File

@@ -9,12 +9,13 @@ namespace RebootKit.Engine.Services.Game {
public ControllerAsset[] Controllers;
}
public abstract class GameModeAsset : ScriptableObject {
public abstract class GameModeAsset : FactoryAsset<GameMode> {
[field: SerializeField]
public GameModeConfig GameModeConfig { get; private set; }
public GameMode Create() {
GameMode gameMode = new GameMode(GameModeConfig);
public override GameMode Create(DIContext context) {
GameMode gameMode = new(GameModeConfig);
context.Inject(gameMode);
ConfigureGameMode(gameMode);
return gameMode;
}
@@ -25,6 +26,9 @@ namespace RebootKit.Engine.Services.Game {
public class GameMode : IDisposable {
private readonly GameModeConfig _config;
private readonly ControllersManager _controllersManager;
[Inject]
private DIContext _diContext;
private CancellationTokenSource _destroyCancellationTokenSource;
@@ -44,7 +48,7 @@ namespace RebootKit.Engine.Services.Game {
}
public async Awaitable<bool> Start(CancellationToken cancellationToken) {
_controllersManager.Add(_config.Controllers);
_controllersManager.Add(_config.Controllers, _diContext);
await _controllersManager.Start(cancellationToken);
_isRunning = true;

View File

@@ -1,21 +1,19 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Services.Console;
using UnityEngine.Assertions;
namespace RebootKit.Engine.Services.Game {
public class GameService : IService {
private static readonly Logger Logger = new(nameof(GameService));
[Inject]
private DIContext _diContext;
private GameModeAsset _gameModeAsset;
private GameMode _gameMode;
private bool _running;
public async UniTask OnWakeUp(CancellationToken cancellationToken) {
await UniTask.Yield(cancellationToken);
}
public void Dispose() {
_running = false;
_gameMode.Dispose();
@@ -24,7 +22,7 @@ namespace RebootKit.Engine.Services.Game {
public async UniTask Start(GameModeAsset asset, CancellationToken cancellationToken) {
Assert.IsNotNull(asset);
_gameMode = asset.Create();
_gameMode = asset.Create(_diContext);
await _gameMode.Start(cancellationToken);
Run(cancellationToken).Forget();

View File

@@ -4,8 +4,9 @@ using UnityEngine;
namespace RebootKit.Engine.Services.Game {
[CreateAssetMenu(menuName = RConsts.ServiceAssetMenu + "Game")]
public class GameServiceAsset : ServiceAsset<GameService> {
public override GameService Create() {
return new GameService();
public override GameService Create(DIContext context) {
GameService service = context.Create<GameService>();
return service;
}
}
}

View File

@@ -7,8 +7,10 @@ namespace RebootKit.Engine.Services.Input {
[SerializeField]
private InputService.Config _config;
public override InputService Create() {
return new InputService(_config);
public override InputService Create(DIContext context) {
InputService instance = new(_config);
context.Inject(instance);
return instance;
}
}
}

View File

@@ -4,8 +4,8 @@ using UnityEngine;
namespace RebootKit.Engine.Services.Simulation {
[CreateAssetMenu(menuName = RConsts.ServiceAssetMenu + "World")]
public class WorldServiceAsset : ServiceAsset<WorldService> {
public override WorldService Create() {
return new WorldService();
public override WorldService Create(DIContext context) {
return context.Create<WorldService>();
}
}
}

View File

@@ -8,8 +8,10 @@ namespace RebootKit.FPPKit {
[SerializeField]
private PlayerController.Config _config;
public override IController Create() {
return new PlayerController(_config);
public override IController Create(DIContext context) {
PlayerController instance = new(_config);
context.Inject(instance);
return instance;
}
}
}