diff --git a/Assets/DebugOverlay/CVar.cs b/Assets/DebugOverlay/CVar.cs new file mode 100644 index 0000000..2717ce9 --- /dev/null +++ b/Assets/DebugOverlay/CVar.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; + +public class CVar +{ + public string Name { get; } + public string Description { get; } + public T DefaultValue { get; } + private T value; + private Action onChanged; + + public T Value + { + get => value; + set + { + if (!EqualityComparer.Default.Equals(this.value, value)) + { + this.value = value; + onChanged?.Invoke(value); + } + } + } + + public CVar(string name, T defaultValue, string description = "", Action onChanged = null) + { + Name = name; + DefaultValue = defaultValue; + Description = description; + this.onChanged = onChanged; + this.value = defaultValue; + CVarRegistry.Register(this); + } + + public void Reset() + { + Value = DefaultValue; + } + + public void SetFromString(string str) + { + object parsedVal; + var t = typeof(T); + if (t == typeof(int)) + parsedVal = int.Parse(str); + else if (t == typeof(float)) + parsedVal = float.Parse(str, System.Globalization.CultureInfo.InvariantCulture); + else if (t == typeof(bool)) + parsedVal = bool.Parse(str); + else if (t == typeof(string)) + parsedVal = str; + else + throw new Exception($"Unsupported CVar type: {t}"); + Value = (T)parsedVal; + } +} \ No newline at end of file diff --git a/Assets/DebugOverlay/CVar.cs.meta b/Assets/DebugOverlay/CVar.cs.meta new file mode 100644 index 0000000..1dd8f5e --- /dev/null +++ b/Assets/DebugOverlay/CVar.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b93b9209f3fbe74a8a9aea98ebb6970 \ No newline at end of file diff --git a/Assets/DebugOverlay/CVarRegistry.cs b/Assets/DebugOverlay/CVarRegistry.cs new file mode 100644 index 0000000..28f68e1 --- /dev/null +++ b/Assets/DebugOverlay/CVarRegistry.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +public static class CVarRegistry +{ + private static readonly Dictionary cvars = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static void Register(CVar cvar) + { + cvars[cvar.Name] = cvar; + } + + public static object Find(string name) + { + cvars.TryGetValue(name, out var cvar); + return cvar; + } + + public static IEnumerable AllCVars() => cvars.Values; +} \ No newline at end of file diff --git a/Assets/DebugOverlay/CVarRegistry.cs.meta b/Assets/DebugOverlay/CVarRegistry.cs.meta new file mode 100644 index 0000000..c35502a --- /dev/null +++ b/Assets/DebugOverlay/CVarRegistry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b3d7e8bb1fc1b9147b3f54efb1b10902 \ No newline at end of file diff --git a/Assets/DebugOverlay/Console.cs b/Assets/DebugOverlay/Console.cs index 944080d..934a1d8 100644 --- a/Assets/DebugOverlay/Console.cs +++ b/Assets/DebugOverlay/Console.cs @@ -43,6 +43,7 @@ public Console() m_InputFieldBuffer = new char[k_InputBufferSize]; AddCommand("help", CmdHelp, "Show available commands"); AddCommand("dump", CmdDumpScene, "Dump scene hierarchy in active scene"); + AddCommand("cvars", CmdListCVars, "List all config variables (CVars)"); Keyboard.current.onTextInput += OnTextInput; } @@ -255,7 +256,44 @@ void ExecuteCommand(string command) } else { - Write("Unknown command: {0}\n", splitCommand[0]); + // CVar get/set support + // Support: name (get), name value (set) + string cvarName = commandName; + string valueStr = null; + if (splitCommand.Length >= 2) + { + valueStr = string.Join(" ", splitCommand, 1, splitCommand.Length - 1); + } + + var cvarObj = CVarRegistry.Find(cvarName); + if (cvarObj != null) + { + var cvarType = cvarObj.GetType(); + var valueProp = cvarType.GetProperty("Value"); + if (valueStr == null) + { + // Get value + var val = valueProp.GetValue(cvarObj); + Write($"{cvarName} = {val}\n"); + } + else + { + try + { + var setFromStringMethod = cvarType.GetMethod("SetFromString"); + setFromStringMethod.Invoke(cvarObj, new object[] { valueStr }); + Write($"{cvarName} set to {valueProp.GetValue(cvarObj)}\n"); + } + catch (Exception ex) + { + Write($"Failed to set {cvarName}: {ex.Message}\n"); + } + } + } + else + { + Write("Unknown command or cvar: {0}\n", splitCommand[0]); + } } } @@ -433,6 +471,16 @@ void TabComplete() continue; matches.Add(name); } + // Add CVar names to tab completion + foreach (var obj in CVarRegistry.AllCVars()) + { + var type = obj.GetType(); + var name = (string)type.GetProperty("Name").GetValue(obj); + if (!name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + continue; + if(!matches.Contains(name)) + matches.Add(name); + } if (matches.Count == 0) return; @@ -469,5 +517,17 @@ static int CommonPrefix(string a, string b) return minl; } + void CmdListCVars(string[] args) + { + foreach (var obj in CVarRegistry.AllCVars()) + { + var type = obj.GetType(); + var name = (string)type.GetProperty("Name").GetValue(obj); + var value = type.GetProperty("Value").GetValue(obj); + var desc = (string)type.GetProperty("Description").GetValue(obj); + Write($" {name} = {value} // {desc}\n"); + } + } + DebugOverlay m_DebugOverlay; } diff --git a/Assets/Game/Game.cs b/Assets/Game/Game.cs index 1ba3bdc..cb9561f 100644 --- a/Assets/Game/Game.cs +++ b/Assets/Game/Game.cs @@ -29,6 +29,9 @@ public class Game Console m_Console; Stats m_Stats; + // Example config variable (CVar) + public static CVar fov = new CVar("fov", 60.0f, "Field of view"); + public void Init() { Debug.Assert(_instance == null); @@ -45,7 +48,7 @@ public void Init() m_Stats = new Stats(); m_Stats.Init(); - Game.console.Write("^FFFGame initialized^F44.^4F4.^44F.\n"); + Game.console.Write($"^FFFGame initialized. FOV is {fov.Value}^F44.^4F4.^44F.\n"); } public void Shutdown() diff --git a/DEVELOPMENT_GUIDELINES.md b/DEVELOPMENT_GUIDELINES.md new file mode 100644 index 0000000..b66c3c8 --- /dev/null +++ b/DEVELOPMENT_GUIDELINES.md @@ -0,0 +1,56 @@ +# Development Guidelines + +This document contains guidelines extracted from analyzing preferred implementations to ensure future development aligns with the project's design philosophy. + +## Core Principles + +### 1. Prioritize Simplicity Over Abstraction +- **Prefer concrete classes over generic abstractions** +- **Avoid complex manager patterns when simple registration works** +- **Keep related functionality in single files when possible** +- **Write less code when possible** + +### 2. Performance is Paramount +- **Direct field access over method calls for hot paths** +- **Minimize allocation and indirection** +- **Avoid implicit operators or complex conversion layers** +- **Prefer explicit over implicit behavior** + +### 3. Follow Unity's Design Patterns +- **Use direct field access like Unity components do** +- **Keep registration simple and automatic** +- **Prefer concrete implementations over abstract interfaces** +- **Use clear, descriptive names** + +### 4. Console Integration Should Feel Natural +- **Use intuitive command syntax (`name value` vs `name = value`)** +- **Query by name for getting values** +- **Keep parsing logic simple and direct** +- **Minimize complex assignment detection** + +### 5. Code Style Guidelines +- **Write less code when possible** +- **Prefer concrete implementations over abstract interfaces** +- **Use clear, descriptive names** +- **Avoid over-engineering solutions** + +### 6. File Organization +- **Keep related functionality together** +- **Prefer single files over multiple small files** +- **Minimize cross-file dependencies** + +## Key Takeaways + +1. **Simplicity wins**: The preferred implementation is much shorter and easier to understand +2. **Performance matters**: Direct field access (`cvar.value`) is faster than method calls +3. **Unity-like patterns**: Follow Unity's style of direct, explicit access +4. **Natural integration**: Console commands should feel intuitive and simple +5. **Less is more**: Avoid unnecessary abstraction layers and complex type systems + +## When to Apply These Guidelines + +- **New feature development**: Always consider the simpler approach first +- **Performance-critical code**: Prioritize direct access over abstraction +- **Console integration**: Keep commands natural and intuitive +- **Code reviews**: Question complex abstractions and prefer simpler solutions +- **Refactoring**: Look for opportunities to simplify existing code \ No newline at end of file