This commit is contained in:
2025-04-14 23:22:38 +02:00
parent 72b8a37345
commit 1e190fe94b
166 changed files with 2989 additions and 687 deletions

View File

@@ -0,0 +1,174 @@
using System;
namespace RebootKit.Engine.Services.Console {
public enum CVarValueKind {
Number, String
}
[Serializable]
public struct CVarValue {
public CVarValueKind kind;
public double numberValue;
public string stringValue;
public CVarValue(int value) {
kind = CVarValueKind.Number;
numberValue = value;
stringValue = null;
}
public CVarValue(float value) {
kind = CVarValueKind.Number;
numberValue = value;
stringValue = null;
}
public CVarValue(double value) {
kind = CVarValueKind.Number;
numberValue = value;
stringValue = null;
}
public CVarValue(string value) {
kind = CVarValueKind.String;
numberValue = 0;
stringValue = value;
}
public void CopyFrom(CVarValue value) {
kind = value.kind;
numberValue = value.numberValue;
stringValue = value.stringValue;
}
public override string ToString() {
return kind switch {
CVarValueKind.Number => numberValue.ToString(),
CVarValueKind.String => $"{stringValue}",
_ => throw new ArgumentOutOfRangeException()
};
}
}
[Flags]
public enum CVarFlags {
None = 0,
Client = 1 << 0,
Server = 1 << 1,
ReadOnly = 1 << 2,
User = 1 << 3,
}
[Serializable]
public class ConfigVar {
public CVarFlags flags;
public string name;
public string description;
public CVarValue defaultValue;
public ConfigVar(ConfigVar other) {
flags = other.flags;
name = other.name;
description = other.description;
defaultValue = other.defaultValue;
Value = other.Value;
}
public ConfigVar(string name, CVarValue value, string description = "") {
this.name = name;
this.description = description;
defaultValue = value;
Value = defaultValue;
}
public ConfigVar(string name, int value, string description = "") {
this.name = name;
this.description = description;
defaultValue = new CVarValue(value);
Value = defaultValue;
}
public ConfigVar(string name, float value, string description = "") {
this.name = name;
this.description = description;
defaultValue = new CVarValue(value);
Value = defaultValue;
}
public ConfigVar(string name, double value, string description = "") {
this.name = name;
this.description = description;
defaultValue = new CVarValue(value);
Value = defaultValue;
}
public ConfigVar(string name, string value, string description = "") {
this.name = name;
this.description = description;
defaultValue = new CVarValue(value);
Value = defaultValue;
}
public CVarValue Value { get; private set; }
public int IndexValue => (int) Value.numberValue;
public float FloatValue => (float) Value.numberValue;
public double NumberValue => Value.numberValue;
public string StringValue => Value.stringValue;
public static event Action<ConfigVar> StateChanged = delegate { };
public void Set(int value) {
if (flags.HasFlag(CVarFlags.ReadOnly)) {
return;
}
Value = new CVarValue(value);
StateChanged?.Invoke(this);
}
public void Set(float value) {
if (flags.HasFlag(CVarFlags.ReadOnly)) {
return;
}
Value = new CVarValue(value);
StateChanged?.Invoke(this);
}
public void Set(string value) {
if (flags.HasFlag(CVarFlags.ReadOnly)) {
return;
}
Value = new CVarValue(value);
StateChanged?.Invoke(this);
}
public void ParseFromString(string str) {
if (flags.HasFlag(CVarFlags.ReadOnly)) {
return;
}
if (float.TryParse(str, out float f)) {
Set(f);
} else {
Set(str);
}
}
public void Reset() {
if (flags.HasFlag(CVarFlags.ReadOnly)) {
return;
}
Value = defaultValue;
StateChanged?.Invoke(this);
}
public override string ToString() {
return Value.ToString();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f560b25a893456c90fac62ad278c02b
timeCreated: 1740779755

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using RebootKit.Engine.Services.Console;
using UnityEngine;
using ZLinq;
namespace RebootKit.Engine.Foundation {
[AttributeUsage(AttributeTargets.Field)]
public class ConfigVarAttribute : Attribute {
public ConfigVarAttribute(string name, float defaultValue, string description = "", CVarFlags flags = CVarFlags.Client) {
Name = name;
Description = description;
Value = new CVarValue(defaultValue);
Flags = flags;
}
public ConfigVarAttribute(string name, double defaultValue, string description = "", CVarFlags flags = CVarFlags.Client) {
Name = name;
Description = description;
Value = new CVarValue(defaultValue);
Flags = flags;
}
public ConfigVarAttribute(string name, string defaultValue, string description = "", CVarFlags flags = CVarFlags.Client) {
Name = name;
Description = description;
Value = new CVarValue(defaultValue);
Flags = flags;
}
public ConfigVarAttribute(string name, int defaultValue, string description = "", CVarFlags flags = CVarFlags.Client) {
Name = name;
Description = description;
Value = new CVarValue(defaultValue);
Flags = flags;
}
internal string Name { get; }
internal string Description { get; }
internal CVarValue Value { get; }
internal CVarFlags Flags { get; }
}
public static class ConfigVarsContainer {
static readonly Logger s_logger = new(nameof(ConfigVarsContainer));
static readonly List<ConfigVar> s_configVars = new();
public static void Register(ConfigVar cvar) {
if (s_configVars.AsValueEnumerable().Any(c => c.name.Equals(cvar.name, StringComparison.Ordinal))) {
throw new Exception($"CVar with name '{cvar.name}' already exists");
}
s_configVars.Add(cvar);
s_logger.Info($"Registered CVar: {cvar.name}");
}
public static void Clear() {
s_configVars.Clear();
s_logger.Info("Cleared all CVars");
}
public static ConfigVar Get(string name) {
return s_configVars.AsValueEnumerable().FirstOrDefault(c => c.name.Equals(name, StringComparison.Ordinal));
}
public static IEnumerable<ConfigVar> All() => s_configVars;
public static void Init() {
Clear();
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) {
Type[] types = assembly.GetTypes();
foreach (Type type in types) {
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
foreach (FieldInfo field in fields) {
if (Attribute.IsDefined(field, typeof(ConfigVarAttribute))) {
ConfigVarAttribute cvarAttribute = field.GetCustomAttribute<ConfigVarAttribute>();
ConfigVar cvar = Get(cvarAttribute.Name);
if (cvar == null) {
cvar = new ConfigVar(cvarAttribute.Name, cvarAttribute.Value, cvarAttribute.Description) {
flags = cvarAttribute.Flags
};
Register(cvar);
}
field.SetValue(null, cvar);
}
}
}
}
Load();
}
public static void Save() {
string path = Application.persistentDataPath + "/" + RConsts.k_CVarsFilename;
s_logger.Info("Saving cvars to file: " + path);
StringBuilder sb = new();
foreach (ConfigVar cvar in All()) {
if (!cvar.flags.HasFlag(CVarFlags.ReadOnly)) {
sb.AppendFormat("{0} {1}\n", cvar.name, cvar);
}
}
File.WriteAllText(path, sb.ToString());
}
public static void Load() {
string path = Application.persistentDataPath + "/" + RConsts.k_CVarsFilename;
if (!File.Exists(path)) {
s_logger.Info("CVar file not found, skipping load");
return;
}
s_logger.Info("Loading cvars from file: " + path);
string[] lines = File.ReadAllLines(path);
foreach (string line in lines) {
string[] parts = line.Split(' ');
if (parts.Length < 2) {
continue;
}
string name = parts[0];
string value = parts[1];
ConfigVar cvar = Get(name);
if (cvar != null) {
cvar.Value.CopyFrom(new CVarValue(value));
s_logger.Info($"Loaded CVar: {name} = {value}");
} else {
cvar = new ConfigVar(name, value) {
flags = CVarFlags.User
};
cvar.ParseFromString(value);
Register(cvar);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1ce6213dda8c4f8d9eafe4afd1614a86
timeCreated: 1742005021

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation {
public class ConstantsAsset<T> : RAsset {
[SerializeField] T m_Value;
public T Value => m_Value;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 602c79db950d497ea9800a6538aa7ab6
timeCreated: 1743848110

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation {
[CreateAssetMenu(menuName = RConsts.k_CreateAssetMenu + "Constants/Consts Color")]
public class ConstsColorAsset : ConstantsAsset<Color> {
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5055a0315a6942988576e3dc5fa72368
timeCreated: 1743848378

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation {
[CreateAssetMenu(menuName = RConsts.k_CreateAssetMenu + "Constants/Consts Float")]
public class ConstsFloatAsset : ConstantsAsset<float> {
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 82df0e4f31ac4722ae6ec18f66815179
timeCreated: 1743848335

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace RebootKit.Engine.Foundation {
[CreateAssetMenu(menuName = RConsts.k_CreateAssetMenu + "Constants/Consts Int")]
public class ConstsIntAsset : ConfigAsset<int> {
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 064ad4ee1c584489b0c65417e8b500ea
timeCreated: 1743848190

View File

@@ -0,0 +1,37 @@
using System;
using UnityEngine;
namespace RebootKit.Engine.Foundation {
[Serializable]
public struct ConstsProperty<T> {
[SerializeField] T m_InlineValue;
[SerializeField] ConstantsAsset<T> m_Asset;
[SerializeField] bool m_UseInlineValue;
public ConstsProperty(T value) {
m_InlineValue = value;
m_Asset = null;
m_UseInlineValue = true;
}
public ConstsProperty(ConstantsAsset<T> asset) {
m_InlineValue = default;
m_Asset = asset;
m_UseInlineValue = false;
}
public T Value {
get {
if (m_UseInlineValue) {
return m_InlineValue;
}
if (m_Asset != null) {
return m_Asset.Value;
}
return default;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 426c2ae5506c4629bf757f51c5b94249
timeCreated: 1743848423

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ZLinq;
namespace RebootKit.Engine.Foundation {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)]
@@ -28,7 +29,9 @@ namespace RebootKit.Engine.Foundation {
}
public void Bind(Type type, object obj) {
if (!m_BindingsMaps.TryAdd(type, obj)) s_logger.Error($"Cannot bind to '{type}', slot is already occupied");
if (!m_BindingsMaps.TryAdd(type, obj)) {
s_logger.Error($"Cannot bind to '{type}', slot is already occupied");
}
}
public void Bind<TBind>(TBind obj) {
@@ -53,22 +56,27 @@ namespace RebootKit.Engine.Foundation {
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<T>(T target) {
Type type = typeof(T);
foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) InjectField(field, target);
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;
if (!Attribute.IsDefined(method, typeof(InjectAttribute))) {
continue;
}
Type[] paramsTypes = method.GetParameters()
.AsValueEnumerable()
.Select(t => t.ParameterType)
.ToArray();
@@ -77,7 +85,9 @@ namespace RebootKit.Engine.Foundation {
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]}`");
if (instances[i] == null) {
s_logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`");
}
}
method.Invoke(target, instances);
@@ -85,9 +95,11 @@ namespace RebootKit.Engine.Foundation {
}
bool InjectField(FieldInfo field, object target) {
for (int i = m_FieldInjectors.Count - 1; i >= 0; i--)
if (m_FieldInjectors[i].Inject(field, target, this))
for (int i = m_FieldInjectors.Count - 1; i >= 0; i--) {
if (m_FieldInjectors[i].Inject(field, target, this)) {
return true;
}
}
return false;
}

View File

@@ -18,12 +18,16 @@ namespace RebootKit.Engine.Foundation {
RR.s_Shared = null;
// unload all scenes
s_cancellationTokenSource = new CancellationTokenSource();
RunAsync(s_cancellationTokenSource.Token).Forget();
#if UNITY_EDITOR
static void OnPlayerModeState(PlayModeStateChange state) {
if (state == PlayModeStateChange.ExitingPlayMode) s_cancellationTokenSource.Cancel();
if (state == PlayModeStateChange.ExitingPlayMode) {
s_cancellationTokenSource.Cancel();
}
}
EditorApplication.playModeStateChanged -= OnPlayerModeState;
@@ -32,6 +36,9 @@ namespace RebootKit.Engine.Foundation {
}
static async UniTask RunAsync(CancellationToken cancellationToken) {
s_logger.Info("Loading boot scene");
SceneManager.LoadScene(RConsts.k_BootSceneBuildIndex, LoadSceneMode.Single);
s_logger.Info("Loading engine config");
EngineConfigAsset configAsset = Resources.Load<EngineConfigAsset>(RConsts.k_EngineConfigResourcesPath);
if (configAsset == null) {
@@ -42,7 +49,7 @@ namespace RebootKit.Engine.Foundation {
if (!configAsset.initializeOnLoad) {
return;
}
using RR instance = new();
RR.s_Shared = instance;
@@ -54,6 +61,8 @@ namespace RebootKit.Engine.Foundation {
s_logger.Info("Starting RR");
instance.Run();
await UniTask.WaitUntilCanceled(Application.exitCancellationToken);
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using JetBrains.Annotations;
namespace RebootKit.Engine.Foundation {
public interface IController : IDisposable {
@@ -25,6 +26,16 @@ namespace RebootKit.Engine.Foundation {
}
}
public T Get<T>() where T : class, IController {
foreach (TController controller in _controllers) {
if (controller is T res) {
return res;
}
}
return null;
}
public bool TryFind<T>(out T outController) where T : IController {
foreach (TController controller in _controllers) {
if (controller is T res) {

View File

@@ -74,7 +74,7 @@ namespace RebootKit.Engine.Foundation {
case LogLevel.Warning:
return $"[W][<b>{name}</b>] <color=#ffffff>{message}</color>";
case LogLevel.Error:
return $"[<b><color=#ff0000>ERROR</color></b>][<b>{name}</b>] <color=#ffffff>{message}</color>";
return $"[<b><color=#e83f25>ERROR</color></b>][<b>{name}</b>] <color=#ffffff>{message}</color>";
default:
throw new ArgumentOutOfRangeException(nameof(level), level, null);
}

View File

@@ -11,19 +11,22 @@ namespace RebootKit.Engine.Foundation {
}
[DefaultExecutionOrder(-1000)]
public class SceneDI : MonoBehaviour, IDependencyInstaller {
static readonly Logger s_logger = new(nameof(SceneDI));
public class SceneContext : MonoBehaviour, IDependencyInstaller {
static readonly Logger s_logger = new(nameof(SceneContext));
[SerializeField] SceneDependencyInstaller[] m_Installers;
void Awake() {
DIContext context = RR.DIContext;
foreach (GameObject root in gameObject.scene.GetRootGameObjects()) {
s_logger.Info("Injecting root game object: " + root.name);
context.InjectGameObject(root);
}
}
public void Install(DIContext context) {
s_logger.Info("Installing scene dependency installers");
foreach (SceneDependencyInstaller installer in m_Installers) {
installer.Install(context);
}