More built in commands, CommandsManager now has a dependency on CogwheelConsole, can now search for and set context in the console

This commit is contained in:
Chris Bell 2024-10-03 15:50:50 -05:00
parent ef73de5a86
commit ee7aacc046
13 changed files with 310 additions and 67 deletions

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using OpenCover.Framework.Model; using OpenCover.Framework.Model;
using UnityEngine; using UnityEngine;
@ -9,10 +10,10 @@ namespace Cogwheel
public static CogwheelSettings Settings { get; } public static CogwheelSettings Settings { get; }
public static CogwheelConsole Console { get; private set; } public static CogwheelConsole Console { get; private set; }
static Color _color = Color.blue;
static COGWHEEL() static COGWHEEL()
{ {
CommandsManager = new CommandsManager();
CommandsManager.GetCommands();
Settings = Resources.Load<CogwheelSettings>("CogwheelSettings"); Settings = Resources.Load<CogwheelSettings>("CogwheelSettings");
if (Settings is null) if (Settings is null)
{ {
@ -20,6 +21,8 @@ namespace Cogwheel
return; return;
} }
Console = GameObject.Instantiate(Settings.CogwheelConsolePrefab).GetComponent<CogwheelConsole>(); Console = GameObject.Instantiate(Settings.CogwheelConsolePrefab).GetComponent<CogwheelConsole>();
CommandsManager = new CommandsManager(Console);
} }
#region BUILT-IN COMMANDS #region BUILT-IN COMMANDS
@ -36,47 +39,71 @@ namespace Cogwheel
Console.LogError(message); Console.LogError(message);
} }
[Command(Description = "Logs a warning to the console.")]
public static void LogWarning(string message)
{
Console.LogWarning(message);
}
[Command(Description = "Clears the console.")] [Command(Description = "Clears the console.")]
public static void Clear() public static void Clear()
{ {
Console.Clear(); Console.Clear();
} }
[Command(Description = "Lists all available commands.")] [Command(Description = "Lists available commands for the current context. Use '-a' to list all commands in registry.")]
public static void Help() public static void List(string arg = "")
{ {
foreach (var command in CommandsManager.Commands.Values) foreach (var command in CommandsManager.Commands.Values)
{ {
if (command.Method.IsStatic) if (command.Method.IsStatic)
{ {
Console.Log($"{command.Name} - {command.Description}"); Log($"{command.Name} - {command.Description}");
} }
else else
{ {
if (CommandsManager.SceneContext is not null) if (arg == "-a")
{ {
foreach (var behaviour in CommandsManager.SceneContext.GetComponents<MonoBehaviour>()) Log(CommandsManager.IsCommandContextValid(command)
{ ? $"<color=green>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}"
if (behaviour.GetType() == command.Method.DeclaringType) : $"<color=#333333>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}");
{
Console.Log($"<color=green>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}");
}
else
{
Console.Log($"<color=red>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}");
}
}
} }
else
if (CommandsManager.IsCommandContextValid(command))
{ {
Console.Log($"<color=red>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}"); Log($"<color=green>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}");
} }
} }
//Console.Log($"<color=green>({command.Method.DeclaringType.Name})</color> : {command.Name} - {command.Description}");
} }
} }
[Command(Description = "Gets usage for a given command")]
public static void Help(string commandName)
{
Command command = CommandsManager.GetCommandByName(commandName);
if (command is null)
{
Console.LogError($"Command error: '{commandName}' is not a command.");
return;
}
Console.Log($"-- Command Usage For {commandName} --\n" +
$"Description: {command.Description}\n" +
$"Usage: {CommandsManager.GetCommandUsage(command)}");
}
[Command(CommandName = "search", Description = "Searches the scene for a GameObject with the given name and sets the Context to it.")]
public static void SetContextByName(string name)
{
CommandsManager.SetContext(name);
}
[Command(CommandName = "whois", Description = "Prints out the current Context name")]
public static void PrintContext()
{
Log($"Current context is: {CommandsManager.Context.name}");
}
#endregion #endregion

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Unity.VisualScripting;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@ -13,33 +14,50 @@ namespace Cogwheel
public class CommandsManager : ICommandsManager public class CommandsManager : ICommandsManager
{ {
public Dictionary<string, Command> Commands { get; set; } = new(StringComparer.OrdinalIgnoreCase); public Dictionary<string, Command> Commands { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GameObject SceneContext { get; set; } public GameObject Context { get; set; }
public Dictionary<Type, Func<string, object>> CustomParsers { get; private set; } = new(); public Dictionary<Type, Func<string, object>> CustomParsers { get; private set; } = new();
public string CommandPattern { get; set; } = "(?<val>(\\([^\\)]+\\)))|\"(?<val>[^\"]+)\"|'(?<val>[^']+)'|(?<val>[^\\s]+)"; public string CommandPattern { get; set; } = "(?<val>(\\([^\\)]+\\)))|\"(?<val>[^\"]+)\"|'(?<val>[^']+)'|(?<val>[^\\s]+)";
public List<Assembly> Assemblies { get; set; } = new(); public List<Assembly> Assemblies { get; set; } = new();
public void GetCommands() private CogwheelConsole _console;
public CommandsManager(CogwheelConsole console)
{ {
foreach (var type in Assembly.GetCallingAssembly().GetTypes()) _console = console;
{
var methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance);
foreach (var method in methods) RefreshCommandsList();
{
var attributes = method.GetCustomAttributes<CommandAttribute>();
foreach (var attribute in attributes)
{
var commandName = attribute.CommandName ?? method.Name;
var newCommand = new Command(commandName, method, attribute.Description);
Commands.TryAdd(newCommand.Name, newCommand);
}
}
}
} }
public (Command, List<object>)? GetCommandFromString(string input) public void SetContext(GameObject context)
{
Context = context;
}
public void SetContext(string name)
{
Context = SearchSceneForContext(name);
}
public GameObject SearchSceneForContext(string name)
{
var go = GameObject.Find(name);
if (go is null)
{
_console.LogError($"Couldn't find an object with the name '{name}' in the scene");
return null;
}
_console.Log($"Context set to: '{go.name}' <{go.GetType()}>");
return go;
}
public Command GetCommandByName(string commandName)
{
return Commands.Keys.Contains(commandName) ? Commands[commandName] : null;
}
public (Command, List<object>)? GetCommandAndArgsFromString(string input)
{ {
var splitString = Regex.Matches(input, CommandPattern).Select(m => m.Groups["val"].Value).ToArray(); var splitString = Regex.Matches(input, CommandPattern).Select(m => m.Groups["val"].Value).ToArray();
var commandName = splitString[0]; var commandName = splitString[0];
@ -60,10 +78,10 @@ namespace Cogwheel
public bool RunCommand(string input) public bool RunCommand(string input)
{ {
if (string.IsNullOrWhiteSpace(input)) return false; if (string.IsNullOrWhiteSpace(input)) return false;
var command = GetCommandFromString(input); var command = GetCommandAndArgsFromString(input);
if (command is null) if (command is null)
{ {
Debug.LogError($"Command '{input}' not found"); _console.LogError($"Command '{input}' not found");
return false; return false;
} }
@ -72,13 +90,13 @@ namespace Cogwheel
return ExecuteCommand(null, command.Value); return ExecuteCommand(null, command.Value);
} }
if (SceneContext is null) if (Context is null)
{ {
Debug.LogError("SceneContext not set"); _console.LogError("SceneContext not set");
return false; return false;
} }
foreach (var component in SceneContext.GetComponents<MonoBehaviour>()) foreach (var component in Context.GetComponents<MonoBehaviour>())
{ {
if (command.Value.Item1.Method.DeclaringType!.IsInstanceOfType(component)) if (command.Value.Item1.Method.DeclaringType!.IsInstanceOfType(component))
{ {
@ -86,7 +104,7 @@ namespace Cogwheel
} }
} }
Debug.LogError($"Instance of '{command.Value.Item1.Method.DeclaringType}' not found in SceneContext"); _console.LogError($"Instance of '{command.Value.Item1.Method.DeclaringType}' not found in SceneContext");
return false; return false;
} }
@ -116,8 +134,7 @@ namespace Cogwheel
} }
else else
{ {
Debug.LogError($"Format exception: could not parse '{command.args[parameterIndex]}' as '{parameters[parameterIndex].ParameterType}'"); _console.LogError($"Format exception: could not parse '{command.args[parameterIndex]}' as '{parameters[parameterIndex].ParameterType}'");
//print usage
return false; return false;
} }
} }
@ -133,8 +150,8 @@ namespace Cogwheel
} }
else else
{ {
Debug.LogError("Not enough args passed"); _console.LogError("Not enough args passed");
//print usage _console.Log(GetCommandUsage(command.command));
return false; return false;
} }
} }
@ -146,8 +163,7 @@ namespace Cogwheel
} }
else else
{ {
Debug.LogError($"Format exception: could not parse '{command.args[parameterIndex]}' as '{parameters[parameterIndex].ParameterType}'"); _console.LogError($"Format exception: could not parse '{command.args[parameterIndex]}' as '{parameters[parameterIndex].ParameterType}'");
//print usage
return false; return false;
} }
} }
@ -160,8 +176,7 @@ namespace Cogwheel
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError(e.Message); _console.LogError(e.Message);
//print usage
throw; throw;
} }
} }
@ -183,7 +198,7 @@ namespace Cogwheel
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError(e.Message); _console.LogError(e.Message);
parsedValue = null; parsedValue = null;
return false; return false;
} }
@ -201,7 +216,7 @@ namespace Cogwheel
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError(e.Message); _console.LogError(e.Message);
parsedValue = null; parsedValue = null;
return false; return false;
} }
@ -211,6 +226,45 @@ namespace Cogwheel
parsedValue = null; parsedValue = null;
return false; return false;
} }
public bool IsCommandContextValid(Command command)
{
if (Context is null) return false;
List<Type> components = new();
foreach (var component in Context.GetComponents<MonoBehaviour>())
{
components.Add(component.GetType());
}
if (components.Contains(command.Method.DeclaringType)) return true;
components.Clear();
return false;
}
private void RefreshCommandsList()
{
foreach (var type in Assembly.GetCallingAssembly().GetTypes())
{
var methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance);
foreach (var method in methods)
{
var attributes = method.GetCustomAttributes<CommandAttribute>();
foreach (var attribute in attributes)
{
var commandName = attribute.CommandName ?? method.Name;
var newCommand = new Command(commandName, method, attribute.Description);
if (!Commands.Values.Contains(newCommand))
{
Commands.TryAdd(newCommand.Name, newCommand);
}
}
}
}
}
} }
} }

View File

@ -11,15 +11,18 @@ namespace Cogwheel
public string CommandPattern { get; set; } public string CommandPattern { get; set; }
public List<Assembly> Assemblies { get; set; } public List<Assembly> Assemblies { get; set; }
public Dictionary<string, Command> Commands { get; set; } public Dictionary<string, Command> Commands { get; set; }
public GameObject SceneContext { get; set; } public GameObject Context { get; set; }
public Dictionary<Type, Func<string, object>> CustomParsers { get; } public Dictionary<Type, Func<string, object>> CustomParsers { get; }
public void GetCommands(); public (Command, List<object>)? GetCommandAndArgsFromString(string input);
public (Command, List<object>)? GetCommandFromString(string input);
public bool RunCommand(string input); public bool RunCommand(string input);
public string GetCommandUsage(Command command); public string GetCommandUsage(Command command);
public bool ExecuteCommand(object obj, (Command command, List<object> args) command); public bool ExecuteCommand(object obj, (Command command, List<object> args) command);
public bool TryParseParameter(Type parameterType, string parameterString, out object parsedValue); public bool TryParseParameter(Type parameterType, string parameterString, out object parsedValue);
public bool IsCommandContextValid(Command command);
public Command GetCommandByName(string commandName);
public void SetContext(GameObject context);
public void SetContext(string name);
public GameObject SearchSceneForContext(string name);
} }
} }

View File

@ -0,0 +1,21 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Cogwheel;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
public class Test : MonoBehaviour
{
private void Start()
{
var settings = COGWHEEL.Settings;
}
[Command(CommandName = "private", Description = "This is a private command")]
private void PrivateCommand(int value, string text, float f)
{
Debug.Log($"PrivateCommand called: int is {value} and string is {text} and float is {f}");
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f2dd85165475cf7a81bf4b02e9502f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace Cogwheel
{
public class Test2 : MonoBehaviour
{
[Command(CommandName = "test2", Description = "Command on Test2")]
private void Test2Command(string input)
{
Debug.Log($"Test 2 ran: {input}");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a2c4c2a5f3f0432eaf914b6d27031ca6
timeCreated: 1727877159

View File

@ -42,7 +42,12 @@ public class CogwheelConsole : MonoBehaviour
public void LogError(string message) public void LogError(string message)
{ {
Log($"<color=red>{message}</color>"); Log($"<color=red>ERROR: {message}</color>");
}
public void LogWarning(string message)
{
Log($"<color=yellow>WARNING: {message}</color>");
} }
public void Clear() public void Clear()

View File

@ -0,0 +1,3 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:VisualElement name="CogwheelConsolePanel" style="flex-grow: 1; width: 100%;" />
</ui:UXML>

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6d915ffc1116cd340b99d408824b929e
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -38,6 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1 m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0} m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 705507994} m_Sun: {fileID: 705507994}
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0 m_UseRadianceAmbientProbe: 0
--- !u!157 &3 --- !u!157 &3
LightmapSettings: LightmapSettings:
@ -227,7 +228,6 @@ GameObject:
- component: {fileID: 963194228} - component: {fileID: 963194228}
- component: {fileID: 963194227} - component: {fileID: 963194227}
- component: {fileID: 963194226} - component: {fileID: 963194226}
- component: {fileID: 963194229}
m_Layer: 0 m_Layer: 0
m_Name: Main Camera m_Name: Main Camera
m_TagString: MainCamera m_TagString: MainCamera
@ -309,21 +309,100 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &963194229 --- !u!1 &1312985368
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1312985370}
- component: {fileID: 1312985369}
m_Layer: 0
m_Name: Test2
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1312985369
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 963194225} m_GameObject: {fileID: 1312985368}
m_Enabled: 1 m_Enabled: 1
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4a61e49036c312c83a47481446b0c01a, type: 3} m_Script: {fileID: 11500000, guid: a2c4c2a5f3f0432eaf914b6d27031ca6, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
--- !u!4 &1312985370
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1312985368}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1830122060
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1830122061}
- component: {fileID: 1830122062}
m_Layer: 0
m_Name: Test
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1830122061
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1830122060}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1830122062
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1830122060}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f2dd85165475cf7a81bf4b02e9502f4, type: 3}
m_Name:
m_EditorClassIdentifier:
context: {fileID: 0}
--- !u!1660057539 &9223372036854775807 --- !u!1660057539 &9223372036854775807
SceneRoots: SceneRoots:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_Roots: m_Roots:
- {fileID: 963194228} - {fileID: 963194228}
- {fileID: 705507995} - {fileID: 705507995}
- {fileID: 1830122061}
- {fileID: 1312985370}

View File

@ -0,0 +1,2 @@
@import url("unity-theme://default");
VisualElement {}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d98f6689678726a488b8ca0747fb3857
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12388, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0