Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Assets/DebugOverlay/CVar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;

public class CVar<T>
{
public string Name { get; }
public string Description { get; }
public T DefaultValue { get; }
private T value;
private Action<T> onChanged;

public T Value
{
get => value;
set
{
if (!EqualityComparer<T>.Default.Equals(this.value, value))
{
this.value = value;
onChanged?.Invoke(value);
}
}
}

public CVar(string name, T defaultValue, string description = "", Action<T> 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;
}
}
2 changes: 2 additions & 0 deletions Assets/DebugOverlay/CVar.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions Assets/DebugOverlay/CVarRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;

public static class CVarRegistry
{
private static readonly Dictionary<string, object> cvars = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

public static void Register<T>(CVar<T> cvar)
{
cvars[cvar.Name] = cvar;
}

public static object Find(string name)
{
cvars.TryGetValue(name, out var cvar);
return cvar;
}

public static IEnumerable<object> AllCVars() => cvars.Values;
}
2 changes: 2 additions & 0 deletions Assets/DebugOverlay/CVarRegistry.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 61 additions & 1 deletion Assets/DebugOverlay/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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]);
}
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
5 changes: 4 additions & 1 deletion Assets/Game/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class Game
Console m_Console;
Stats m_Stats;

// Example config variable (CVar)
public static CVar<float> fov = new CVar<float>("fov", 60.0f, "Field of view");

public void Init()
{
Debug.Assert(_instance == null);
Expand All @@ -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()
Expand Down
56 changes: 56 additions & 0 deletions DEVELOPMENT_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -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