From 2d4a37787c38acf9c35a98170a75d2a416864085 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 16:36:20 -0800 Subject: [PATCH 01/19] Move Run from consolesystem.h to cvarmanager --- Source/Host/consolesystem.h | 41 +-------------------------------- Source/Host/cvarmanager.cpp | 45 +++++++++++++++++++++++++++++++++++++ Source/Host/cvarmanager.h | 2 ++ 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/Source/Host/consolesystem.h b/Source/Host/consolesystem.h index d7f08f07..e74b37ec 100644 --- a/Source/Host/consolesystem.h +++ b/Source/Host/consolesystem.h @@ -8,47 +8,8 @@ namespace ConsoleSystem { - // Run a console command. - // The following formats are currently supported: - // - [convar name]: Output the current value for a console variable - // - [convar name] [value]: Update a console variable with a new value GENERATE_BINDINGS inline void Run( const char* command ) { - std::string inputString = std::string( command ); - - std::stringstream ss( inputString ); - - std::string cvarName, cvarValue; - ss >> cvarName >> cvarValue; - - std::stringstream valueStream( cvarValue ); - - if ( cvarName == "list" ) - { -// This fails on libclang so we'll ignore it for now... -#ifndef __clang__ - // List all available cvars - CVarSystem::Instance().ForEach( [&]( CVarEntry& entry ) { - spdlog::info( "- '{}': '{}'", entry.m_name, CVarSystem::Instance().ToString( entry.m_name ) ); - spdlog::info( "\t{}", entry.m_description ); - } ); -#endif - } - else if ( !CVarSystem::Instance().Exists( cvarName ) ) - { - spdlog::info( "{} is not a valid command or variable", cvarName ); - } - else - { - if ( valueStream.str().size() > 0 ) - { - CVarSystem::Instance().FromString( cvarName, cvarValue ); - } - else - { - cvarValue = CVarSystem::Instance().ToString( cvarName ); - spdlog::info( "{} is '{}'", cvarName, cvarValue ); - } - } + CVarSystem::Instance().Run( command ); } } // namespace ConsoleSystem \ No newline at end of file diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 3a25f22d..664eaf0f 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -61,6 +61,51 @@ void CVarSystem::Shutdown() cvarFile << std::setw( 4 ) << cvarArchive << std::endl; } + + // Run a console command. +// The following formats are currently supported: +// - [convar name]: Output the current value for a console variable +// - [convar name] [value]: Update a console variable with a new value +void CVarSystem::Run( const char* command ) +{ + std::string inputString = std::string( command ); + + std::stringstream ss( inputString ); + + std::string cvarName, cvarValue; + ss >> cvarName >> cvarValue; + + std::stringstream valueStream( cvarValue ); + + if ( cvarName == "list" ) + { +// This fails on libclang so we'll ignore it for now... +#ifndef __clang__ + // List all available cvars + ForEach( [&]( CVarEntry& entry ) { + spdlog::info( "- '{}': '{}'", entry.m_name, ToString( entry.m_name ) ); + spdlog::info( "\t{}", entry.m_description ); + } ); +#endif + } + else if ( !Exists( cvarName ) ) + { + spdlog::info( "{} is not a valid command or variable", cvarName ); + } + else + { + if ( valueStream.str().size() > 0 ) + { + FromString( cvarName, cvarValue ); + } + else + { + cvarValue = ToString( cvarName ); + spdlog::info( "{} is '{}'", cvarName, cvarValue ); + } + } +} + void CVarSystem::RegisterString( std::string name, std::string value, CVarFlags flags, std::string description ) { Register( name, value, flags, description ); diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 2bc2edf2..154b6e4b 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -80,6 +80,8 @@ class CVarSystem void Startup(); void Shutdown(); + void Run( const char* command ); + bool Exists( std::string name ); void RegisterString( std::string name, std::string value, CVarFlags flags, std::string description ); From e28afa7ce30a9cfa8451097061f5989d9c1dbae4 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 16:40:06 -0800 Subject: [PATCH 02/19] Reorganize cvarmanager --- Source/Host/cvarmanager.h | 100 ++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 154b6e4b..d68e565a 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -9,6 +9,10 @@ #include #include +// ---------------------------------------- +// Core CVar functionality +// ---------------------------------------- + enum CVarFlags { None = 0, @@ -33,15 +37,6 @@ struct CVarEntry std::any m_value; }; -class CVarParameter -{ -protected: - std::string m_name; - -public: - friend class CVarSystem; -}; - class CVarManager : ISubSystem { public: @@ -103,6 +98,56 @@ class CVarSystem std::string ToString( std::string name ); }; +template +inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, std::string description ) +{ + CVarEntry entry = {}; + entry.m_name = name; + entry.m_description = description; + entry.m_flags = flags; + entry.m_value = value; + + size_t hash = GetHash( name ); + m_cvarEntries[hash] = entry; +} + +template +inline T CVarSystem::Get( std::string name ) +{ + assert( Exists( name ) ); // Doesn't exist! Register it first + + size_t hash = GetHash( name ); + CVarEntry& entry = m_cvarEntries[hash]; + + return std::any_cast( entry.m_value ); +} + +template +inline void CVarSystem::Set( std::string name, T value ) +{ + assert( Exists( name ) ); // Doesn't exist! Register it first + + size_t hash = GetHash( name ); + CVarEntry& entry = m_cvarEntries[hash]; + + entry.m_value = value; + + spdlog::info( "{} was set to '{}'.", entry.m_name, value ); +} + +// ---------------------------------------- +// Native CVar interface +// ---------------------------------------- + +class CVarParameter +{ +protected: + std::string m_name; + +public: + friend class CVarSystem; +}; + class StringCVar : CVarParameter { public: @@ -150,40 +195,3 @@ class BoolCVar : CVarParameter operator bool() { return GetValue(); }; }; - -template -inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, std::string description ) -{ - CVarEntry entry = {}; - entry.m_name = name; - entry.m_description = description; - entry.m_flags = flags; - entry.m_value = value; - - size_t hash = GetHash( name ); - m_cvarEntries[hash] = entry; -} - -template -inline T CVarSystem::Get( std::string name ) -{ - assert( Exists( name ) ); // Doesn't exist! Register it first - - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; - - return std::any_cast( entry.m_value ); -} - -template -inline void CVarSystem::Set( std::string name, T value ) -{ - assert( Exists( name ) ); // Doesn't exist! Register it first - - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; - - entry.m_value = value; - - spdlog::info( "{} was set to '{}'.", entry.m_name, value ); -} \ No newline at end of file From 3354c9aa8866afa5eb5c9f56c76d85f8d5d88897 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 17:36:34 -0800 Subject: [PATCH 03/19] Add optional callbacks to cvars --- Source/Host/cvarmanager.cpp | 23 ++++++++++---- Source/Host/cvarmanager.h | 62 ++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 664eaf0f..d3679459 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -106,19 +106,19 @@ void CVarSystem::Run( const char* command ) } } -void CVarSystem::RegisterString( std::string name, std::string value, CVarFlags flags, std::string description ) +void CVarSystem::RegisterString( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description ); + Register( name, value, flags, description, callback ); } -void CVarSystem::RegisterFloat( std::string name, float value, CVarFlags flags, std::string description ) +void CVarSystem::RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description ); + Register( name, value, flags, description, callback ); } -void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, std::string description ) +void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description ); + Register( name, value, flags, description, callback ); } std::string CVarSystem::GetString( std::string name ) @@ -247,3 +247,14 @@ void CVarManager::Shutdown() { CVarSystem::Instance().Shutdown(); } + +// ---------------------------------------- +// Test CVars +// ---------------------------------------- + +FloatCVar cvar_test_float( "cvartest.float", 0.0f, CVarFlags::None, "Yeah", + []( float oldValue, float newValue ) + { + spdlog::trace( "cvartest.float changed! old {}, new {}", oldValue, newValue ); + } +); \ No newline at end of file diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index d68e565a..72364448 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -13,6 +13,9 @@ // Core CVar functionality // ---------------------------------------- +template +using CVarCallback = std::function; + enum CVarFlags { None = 0, @@ -24,7 +27,13 @@ enum CVarFlags Cheat = 1 << 1, // TODO - Temp = 1 << 2 + Temp = 1 << 2, + + // TODO: Networked variables server -> client + Replicated = 1 << 3, + + // TODO: CVars created by the game, hotload these? + Game = 1 << 4 }; struct CVarEntry @@ -35,6 +44,7 @@ struct CVarEntry CVarFlags m_flags; std::any m_value; + std::any m_callback; }; class CVarManager : ISubSystem @@ -51,7 +61,7 @@ class CVarSystem size_t GetHash( std::string string ); template - void Register( std::string name, T value, CVarFlags flags, std::string description ); + void Register( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ); template T Get( std::string name ); @@ -79,9 +89,9 @@ class CVarSystem bool Exists( std::string name ); - void RegisterString( std::string name, std::string value, CVarFlags flags, std::string description ); - void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description ); - void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description ); + void RegisterString( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ); + void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ); + void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ); std::string GetString( std::string name ); float GetFloat( std::string name ); @@ -99,13 +109,14 @@ class CVarSystem }; template -inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, std::string description ) +inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) { CVarEntry entry = {}; entry.m_name = name; entry.m_description = description; entry.m_flags = flags; entry.m_value = value; + entry.m_callback = callback; size_t hash = GetHash( name ); m_cvarEntries[hash] = entry; @@ -130,8 +141,17 @@ inline void CVarSystem::Set( std::string name, T value ) size_t hash = GetHash( name ); CVarEntry& entry = m_cvarEntries[hash]; + T lastValue = std::any_cast( entry.m_value ); + entry.m_value = value; + auto callback = std::any_cast>( entry.m_callback ); + + if ( callback ) + { + callback( lastValue, value ); + } + spdlog::info( "{} was set to '{}'.", entry.m_name, value ); } @@ -151,11 +171,17 @@ class CVarParameter class StringCVar : CVarParameter { public: - StringCVar( std::string name, std::string value, CVarFlags flags, std::string description ) + StringCVar( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ) { m_name = name; - CVarSystem::Instance().RegisterString( name, value, flags, description ); + CVarSystem::Instance().RegisterString( name, value, flags, description, callback ); + } + + StringCVar( std::string name, std::string value, CVarFlags flags, std::string description ) + : StringCVar( name, value, flags, description, nullptr ) + { + } std::string GetValue() { return CVarSystem::Instance().GetString( m_name ); } @@ -167,11 +193,17 @@ class StringCVar : CVarParameter class FloatCVar : CVarParameter { public: - FloatCVar( std::string name, float value, CVarFlags flags, std::string description ) + FloatCVar( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ) { m_name = name; - CVarSystem::Instance().RegisterFloat( name, value, flags, description ); + CVarSystem::Instance().RegisterFloat( name, value, flags, description, callback ); + } + + FloatCVar( std::string name, float value, CVarFlags flags, std::string description ) + : FloatCVar( name, value, flags, description, nullptr ) + { + } float GetValue() { return CVarSystem::Instance().GetFloat( m_name ); } @@ -183,11 +215,17 @@ class FloatCVar : CVarParameter class BoolCVar : CVarParameter { public: - BoolCVar( std::string name, bool value, CVarFlags flags, std::string description ) + BoolCVar( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ) { m_name = name; - CVarSystem::Instance().RegisterBool( name, value, flags, description ); + CVarSystem::Instance().RegisterBool( name, value, flags, description, callback ); + } + + BoolCVar( std::string name, bool value, CVarFlags flags, std::string description ) + : BoolCVar( name, value, flags, description, nullptr ) + { + } bool GetValue() { return CVarSystem::Instance().GetBool( m_name ); } From 4b22b020bc4129c8eacb541c480aff657d974781 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 19:53:07 -0800 Subject: [PATCH 04/19] Implement concommands --- Source/Host/cvarmanager.cpp | 97 ++++++++++++++++++++++++++++--------- Source/Host/cvarmanager.h | 80 ++++++++++++++++++++++-------- 2 files changed, 133 insertions(+), 44 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index d3679459..b05b6006 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -94,46 +94,94 @@ void CVarSystem::Run( const char* command ) } else { - if ( valueStream.str().size() > 0 ) + CVarEntry& entry = GetEntry( cvarName ); + + if ( entry.m_flags & CVarFlags::Command ) { - FromString( cvarName, cvarValue ); + InvokeCommand( cvarName, {} ); } else { - cvarValue = ToString( cvarName ); - spdlog::info( "{} is '{}'", cvarName, cvarValue ); + if ( valueStream.str().size() > 0 ) + { + FromString( cvarName, cvarValue ); + } + else + { + cvarValue = ToString( cvarName ); + spdlog::info( "{} is '{}'", cvarName, cvarValue ); + } } + } } +CVarEntry& CVarSystem::GetEntry( std::string name ) +{ + assert( Exists( name ) ); // Doesn't exist! Register it first + + size_t hash = GetHash( name ); + CVarEntry& entry = m_cvarEntries[hash]; + + return entry; +} + +void CVarSystem::RegisterCommand( std::string name, CVarFlags flags, std::string description, CCmdCallback callback ) +{ + assert( callback != nullptr ); + + CVarEntry entry = {}; + entry.m_name = name; + entry.m_description = description; + entry.m_flags = CVarFlags::Command | flags; + entry.m_callback = callback; + + size_t hash = GetHash( name ); + m_cvarEntries[hash] = entry; +} + void CVarSystem::RegisterString( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description, callback ); + RegisterVariable( name, value, flags, description, callback ); } void CVarSystem::RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description, callback ); + RegisterVariable( name, value, flags, description, callback ); } void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ) { - Register( name, value, flags, description, callback ); + RegisterVariable( name, value, flags, description, callback ); +} + +void CVarSystem::InvokeCommand( std::string name, std::vector arguments ) +{ + CVarEntry& entry = GetEntry( name ); + + assert( entry.m_flags & CVarFlags::Command ); // Should be a command + + auto callback = std::any_cast( entry.m_callback ); + + if ( callback ) + { + callback( arguments ); + } } std::string CVarSystem::GetString( std::string name ) { - return Get( name ); + return GetVariable( name ); } float CVarSystem::GetFloat( std::string name ) { - return Get( name ); + return GetVariable( name ); } bool CVarSystem::GetBool( std::string name ) { - return Get( name ); + return GetVariable( name ); } void CVarSystem::ForEach( std::function func ) @@ -169,11 +217,9 @@ bool CVarSystem::Exists( std::string name ) void CVarSystem::FromString( std::string name, std::string valueStr ) { - assert( Exists( name ) ); // Doesn't exist! Register it first + CVarEntry& entry = GetEntry( name ); std::stringstream valueStream( valueStr ); - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; auto& type = entry.m_value.type(); @@ -182,7 +228,7 @@ void CVarSystem::FromString( std::string name, std::string valueStr ) float value; valueStream >> value; - Set( entry.m_name, value ); + SetVariable( entry.m_name, value ); } else if ( type == typeid( bool ) ) { @@ -195,20 +241,17 @@ void CVarSystem::FromString( std::string name, std::string valueStr ) else assert( false ); // Invalid bool value - Set( entry.m_name, value ); + SetVariable( entry.m_name, value ); } else if ( type == typeid( std::string ) ) { - Set( entry.m_name, valueStr ); + SetVariable( entry.m_name, valueStr ); } } std::string CVarSystem::ToString( std::string name ) { - assert( Exists( name ) ); // Doesn't exist! Register it first - - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; + CVarEntry& entry = GetEntry( name ); const std::type_info& type = entry.m_value.type(); std::string valueStr; @@ -225,17 +268,17 @@ std::string CVarSystem::ToString( std::string name ) void CVarSystem::SetString( std::string name, std::string value ) { - Set( name, value ); + SetVariable( name, value ); } void CVarSystem::SetFloat( std::string name, float value ) { - Set( name, value ); + SetVariable( name, value ); } void CVarSystem::SetBool( std::string name, bool value ) { - Set( name, value ); + SetVariable( name, value ); } void CVarManager::Startup() @@ -252,9 +295,15 @@ void CVarManager::Shutdown() // Test CVars // ---------------------------------------- -FloatCVar cvar_test_float( "cvartest.float", 0.0f, CVarFlags::None, "Yeah", +FloatCVar cvartest_float( "cvartest.float", 0.0f, CVarFlags::None, "Yeah", []( float oldValue, float newValue ) { spdlog::trace( "cvartest.float changed! old {}, new {}", oldValue, newValue ); } +); + +CCmd cvartest_command( "cvartest.command", CVarFlags::None, "A test command", + []( std::vector arguments ) { + spdlog::trace( "cvartest.command has been invoked! Hooray" ); + } ); \ No newline at end of file diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 72364448..4246c4b5 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -16,24 +17,29 @@ template using CVarCallback = std::function; -enum CVarFlags +using CCmdCallback = std::function )>; + +enum CVarFlags : uint32_t { None = 0, + // If this isn't present, it's inherently assumed to be a variable + Command = 1 << 0, + // Save this convar to cvars.json - Archive = 1 << 0, + Archive = 1 << 1, // TODO - Cheat = 1 << 1, + Cheat = 1 << 2, // TODO - Temp = 1 << 2, + Temp = 1 << 3, // TODO: Networked variables server -> client - Replicated = 1 << 3, + Replicated = 1 << 4, // TODO: CVars created by the game, hotload these? - Game = 1 << 4 + Game = 1 << 5 }; struct CVarEntry @@ -41,7 +47,7 @@ struct CVarEntry std::string m_name; std::string m_description; - CVarFlags m_flags; + uint32_t m_flags; std::any m_value; std::any m_callback; @@ -61,16 +67,17 @@ class CVarSystem size_t GetHash( std::string string ); template - void Register( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ); + void RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ); template - T Get( std::string name ); + T GetVariable( std::string name ); template - void Set( std::string name, T value ); + void SetVariable( std::string name, T value ); + + CVarEntry& GetEntry( std::string name ); public: - friend class StringCVar; // // CVarSystem is a singleton because it needs creating *as soon as* it's referenced @@ -87,12 +94,21 @@ class CVarSystem void Run( const char* command ); + /// + /// Check if a specific convar exists + /// + /// + /// bool Exists( std::string name ); + void RegisterCommand( std::string name, CVarFlags flags, std::string description, CCmdCallback callback ); + void RegisterString( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ); + void InvokeCommand( std::string name, std::vector arguments ); + std::string GetString( std::string name ); float GetFloat( std::string name ); bool GetBool( std::string name ); @@ -104,12 +120,15 @@ class CVarSystem void ForEach( std::function func ); void ForEach( std::string filter, std::function func ); + inline static float AsFloat( std::string& argument ) { return std::strtof( argument.c_str(), nullptr ); } + inline static bool AsBool( std::string& argument ) { return argument == "true"; } + void FromString( std::string name, std::string valueStr ); std::string ToString( std::string name ); }; template -inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) +inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) { CVarEntry entry = {}; entry.m_name = name; @@ -123,23 +142,21 @@ inline void CVarSystem::Register( std::string name, T value, CVarFlags flags, st } template -inline T CVarSystem::Get( std::string name ) +inline T CVarSystem::GetVariable( std::string name ) { - assert( Exists( name ) ); // Doesn't exist! Register it first + CVarEntry& entry = GetEntry( name ); - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; + assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable return std::any_cast( entry.m_value ); } template -inline void CVarSystem::Set( std::string name, T value ) +inline void CVarSystem::SetVariable( std::string name, T value ) { - assert( Exists( name ) ); // Doesn't exist! Register it first + CVarEntry& entry = GetEntry( name ); - size_t hash = GetHash( name ); - CVarEntry& entry = m_cvarEntries[hash]; + assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable T lastValue = std::any_cast( entry.m_value ); @@ -233,3 +250,26 @@ class BoolCVar : CVarParameter operator bool() { return GetValue(); }; }; + +class CCmd : CVarParameter +{ +public: + CCmd( std::string name, CVarFlags flags, std::string description, CCmdCallback callback ) + { + m_name = name; + + CVarSystem::Instance().RegisterCommand( name, flags, description, callback ); + } + + // + // You can invoke like this, but honestly, just define a separate function. + // This is not going to be as clean as C#. + // + + void Invoke( std::vector arguments ) + { + CVarSystem::Instance().InvokeCommand( m_name, arguments ); + } + + void operator()( std::vector arguments ) { Invoke( arguments ); } +}; \ No newline at end of file From 438870ff9fb6284253f1eaaa277f0efc2ea6b028 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 20:26:35 -0800 Subject: [PATCH 05/19] Use references directly to get rid of some unnecessary looping --- Source/Host/cvarmanager.cpp | 132 ++++++++++++++++++++++++------------ Source/Host/cvarmanager.h | 36 ++++++---- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index b05b6006..7b2d4046 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -98,24 +98,28 @@ void CVarSystem::Run( const char* command ) if ( entry.m_flags & CVarFlags::Command ) { - InvokeCommand( cvarName, {} ); + InvokeCommand( entry, {} ); } else { if ( valueStream.str().size() > 0 ) { - FromString( cvarName, cvarValue ); + FromString( entry, cvarValue ); } else { - cvarValue = ToString( cvarName ); + cvarValue = ToString( entry ); spdlog::info( "{} is '{}'", cvarName, cvarValue ); } } - } } +bool CVarSystem::Exists( std::string name ) +{ + return m_cvarEntries.find( GetHash( name ) ) != m_cvarEntries.end(); +} + CVarEntry& CVarSystem::GetEntry( std::string name ) { assert( Exists( name ) ); // Doesn't exist! Register it first @@ -155,10 +159,9 @@ void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, st RegisterVariable( name, value, flags, description, callback ); } -void CVarSystem::InvokeCommand( std::string name, std::vector arguments ) -{ - CVarEntry& entry = GetEntry( name ); +void CVarSystem::InvokeCommand( CVarEntry& entry, std::vector arguments ) +{ assert( entry.m_flags & CVarFlags::Command ); // Should be a command auto callback = std::any_cast( entry.m_callback ); @@ -169,56 +172,80 @@ void CVarSystem::InvokeCommand( std::string name, std::vector argum } } +void CVarSystem::InvokeCommand( std::string name, std::vector arguments ) +{ + InvokeCommand( GetEntry( name ), arguments ); +} + + +std::string CVarSystem::GetString( CVarEntry& entry ) +{ + return GetVariable( entry ); +} + std::string CVarSystem::GetString( std::string name ) { - return GetVariable( name ); + return GetString( GetEntry( name ) ); +} + + +float CVarSystem::GetFloat( CVarEntry& entry ) +{ + return GetVariable( entry ); } float CVarSystem::GetFloat( std::string name ) { - return GetVariable( name ); + return GetFloat( GetEntry( name ) ); +} + + +bool CVarSystem::GetBool( CVarEntry& entry ) +{ + return GetVariable( entry ); } bool CVarSystem::GetBool( std::string name ) { - return GetVariable( name ); + return GetBool( GetEntry( name ) ); } -void CVarSystem::ForEach( std::function func ) + +void CVarSystem::SetString( CVarEntry& entry, std::string value ) { - for ( auto& entry : m_cvarEntries ) - { - func( entry.second ); - } + SetVariable( entry, value ); } -void CVarSystem::ForEach( std::string filter, std::function func ) +void CVarSystem::SetString( std::string name, std::string value ) { - std::vector matchingEntries = {}; + SetString( GetEntry( name ), value ); +} - for ( auto& item : m_cvarEntries ) - { - if ( item.second.m_name.find( filter ) == std::string::npos ) - continue; - matchingEntries.push_back( item.second ); - } +void CVarSystem::SetFloat( CVarEntry& entry, float value ) +{ + SetVariable( entry, value ); +} - for ( auto& entry : matchingEntries ) - { - func( entry ); - } +void CVarSystem::SetFloat( std::string name, float value ) +{ + SetFloat( GetEntry( name ), value ); } -bool CVarSystem::Exists( std::string name ) + +void CVarSystem::SetBool( CVarEntry& entry, bool value ) { - return m_cvarEntries.find( GetHash( name ) ) != m_cvarEntries.end(); + SetVariable( entry, value ); } -void CVarSystem::FromString( std::string name, std::string valueStr ) +void CVarSystem::SetBool( std::string name, bool value ) { - CVarEntry& entry = GetEntry( name ); + SetBool( GetEntry( name ), value ); +} + +void CVarSystem::FromString( CVarEntry& entry, std::string valueStr ) +{ std::stringstream valueStream( valueStr ); auto& type = entry.m_value.type(); @@ -228,7 +255,7 @@ void CVarSystem::FromString( std::string name, std::string valueStr ) float value; valueStream >> value; - SetVariable( entry.m_name, value ); + SetVariable( entry, value ); } else if ( type == typeid( bool ) ) { @@ -241,18 +268,22 @@ void CVarSystem::FromString( std::string name, std::string valueStr ) else assert( false ); // Invalid bool value - SetVariable( entry.m_name, value ); + SetVariable( entry, value ); } else if ( type == typeid( std::string ) ) { - SetVariable( entry.m_name, valueStr ); + SetVariable( entry, valueStr ); } } -std::string CVarSystem::ToString( std::string name ) +void CVarSystem::FromString( std::string name, std::string valueStr ) { - CVarEntry& entry = GetEntry( name ); + FromString( GetEntry( name ), valueStr ); +} + +std::string CVarSystem::ToString( CVarEntry& entry ) +{ const std::type_info& type = entry.m_value.type(); std::string valueStr; @@ -266,19 +297,36 @@ std::string CVarSystem::ToString( std::string name ) return valueStr; } -void CVarSystem::SetString( std::string name, std::string value ) +std::string CVarSystem::ToString( std::string name ) { - SetVariable( name, value ); + return ToString( GetEntry( name ) ); } -void CVarSystem::SetFloat( std::string name, float value ) + +void CVarSystem::ForEach( std::function func ) { - SetVariable( name, value ); + for ( auto& entry : m_cvarEntries ) + { + func( entry.second ); + } } -void CVarSystem::SetBool( std::string name, bool value ) +void CVarSystem::ForEach( std::string filter, std::function func ) { - SetVariable( name, value ); + std::vector matchingEntries = {}; + + for ( auto& item : m_cvarEntries ) + { + if ( item.second.m_name.find( filter ) == std::string::npos ) + continue; + + matchingEntries.push_back( item.second ); + } + + for ( auto& entry : matchingEntries ) + { + func( entry ); + } } void CVarManager::Startup() diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 4246c4b5..321417c1 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -70,12 +70,10 @@ class CVarSystem void RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ); template - T GetVariable( std::string name ); + T GetVariable( CVarEntry& entry ); template - void SetVariable( std::string name, T value ); - - CVarEntry& GetEntry( std::string name ); + void SetVariable( CVarEntry& entry, T value ); public: @@ -101,30 +99,46 @@ class CVarSystem /// bool Exists( std::string name ); + CVarEntry& GetEntry( std::string name ); + void RegisterCommand( std::string name, CVarFlags flags, std::string description, CCmdCallback callback ); void RegisterString( std::string name, std::string value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ); + void InvokeCommand( CVarEntry& entry, std::vector arguments ); void InvokeCommand( std::string name, std::vector arguments ); + std::string GetString( CVarEntry& entry ); std::string GetString( std::string name ); + + float GetFloat( CVarEntry& entry ); float GetFloat( std::string name ); + + bool GetBool( CVarEntry& entry ); bool GetBool( std::string name ); + void SetString( CVarEntry& entry, std::string value ); void SetString( std::string name, std::string value ); + + void SetFloat( CVarEntry& entry, float value ); void SetFloat( std::string name, float value ); + + void SetBool( CVarEntry& entry, bool value ); void SetBool( std::string name, bool value ); + void FromString( CVarEntry& entry, std::string valueStr ); + void FromString( std::string name, std::string valueStr ); + + std::string ToString( CVarEntry& entry ); + std::string ToString( std::string name ); + void ForEach( std::function func ); void ForEach( std::string filter, std::function func ); inline static float AsFloat( std::string& argument ) { return std::strtof( argument.c_str(), nullptr ); } inline static bool AsBool( std::string& argument ) { return argument == "true"; } - - void FromString( std::string name, std::string valueStr ); - std::string ToString( std::string name ); }; template @@ -142,20 +156,16 @@ inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags f } template -inline T CVarSystem::GetVariable( std::string name ) +inline T CVarSystem::GetVariable( CVarEntry& entry ) { - CVarEntry& entry = GetEntry( name ); - assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable return std::any_cast( entry.m_value ); } template -inline void CVarSystem::SetVariable( std::string name, T value ) +inline void CVarSystem::SetVariable( CVarEntry& entry, T value ) { - CVarEntry& entry = GetEntry( name ); - assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable T lastValue = std::any_cast( entry.m_value ); From be8f9cc4729bdd4d293f6fb66e142719fbd3b74f Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 20:28:06 -0800 Subject: [PATCH 06/19] Move list to a CCmd for proof of concept --- Source/Host/cvarmanager.cpp | 63 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 7b2d4046..57e24300 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -77,40 +77,28 @@ void CVarSystem::Run( const char* command ) std::stringstream valueStream( cvarValue ); - if ( cvarName == "list" ) + if ( !Exists( cvarName ) ) { -// This fails on libclang so we'll ignore it for now... -#ifndef __clang__ - // List all available cvars - ForEach( [&]( CVarEntry& entry ) { - spdlog::info( "- '{}': '{}'", entry.m_name, ToString( entry.m_name ) ); - spdlog::info( "\t{}", entry.m_description ); - } ); -#endif + spdlog::info( "{} is not a valid command or variable", cvarName ); + return; } - else if ( !Exists( cvarName ) ) + + CVarEntry& entry = GetEntry( cvarName ); + + if ( entry.m_flags & CVarFlags::Command ) { - spdlog::info( "{} is not a valid command or variable", cvarName ); + InvokeCommand( entry, {} ); } else { - CVarEntry& entry = GetEntry( cvarName ); - - if ( entry.m_flags & CVarFlags::Command ) + if ( valueStream.str().size() > 0 ) { - InvokeCommand( entry, {} ); + FromString( entry, cvarValue ); } else { - if ( valueStream.str().size() > 0 ) - { - FromString( entry, cvarValue ); - } - else - { - cvarValue = ToString( entry ); - spdlog::info( "{} is '{}'", cvarName, cvarValue ); - } + cvarValue = ToString( entry ); + spdlog::info( "{} is '{}'", cvarName, cvarValue ); } } } @@ -339,19 +327,40 @@ void CVarManager::Shutdown() CVarSystem::Instance().Shutdown(); } +// ---------------------------------------- +// Built-in CVars +// ---------------------------------------- + +static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables", + []( std::vector arguments ) + { + auto instance = CVarSystem::Instance(); + +// This fails on libclang so we'll ignore it for now... +#ifndef __clang__ + // List all available cvars + instance.ForEach( [&]( CVarEntry& entry ) { + spdlog::info( "- '{}': '{}'", entry.m_name, instance.ToString( entry ) ); + spdlog::info( "\t{}", entry.m_description ); + } ); +#endif + } +); + // ---------------------------------------- // Test CVars // ---------------------------------------- -FloatCVar cvartest_float( "cvartest.float", 0.0f, CVarFlags::None, "Yeah", +static FloatCVar cvartest_float( "cvartest.float", 0.0f, CVarFlags::None, "Yeah", []( float oldValue, float newValue ) { spdlog::trace( "cvartest.float changed! old {}, new {}", oldValue, newValue ); } ); -CCmd cvartest_command( "cvartest.command", CVarFlags::None, "A test command", - []( std::vector arguments ) { +static CCmd cvartest_command( "cvartest.command", CVarFlags::None, "A test command", + []( std::vector arguments ) + { spdlog::trace( "cvartest.command has been invoked! Hooray" ); } ); \ No newline at end of file From bebca44cb3f627b6970175c2d3c62305074186fb Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 21:04:28 -0800 Subject: [PATCH 07/19] Parse text for quotes, comments, break statements up with ';'. Pass arguments to commands. --- Source/Host/cvarmanager.cpp | 296 +++++++++++++++++++++++++++++++++--- Source/Host/cvarmanager.h | 24 +++ 2 files changed, 297 insertions(+), 23 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 57e24300..8a6855bb 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -42,7 +42,7 @@ void CVarSystem::Startup() CVarEntry& entry = m_cvarEntries[hash]; if ( entry.m_flags & CVarFlags::Archive ) - FromString( name, value ); + FromString( entry, value ); } } @@ -61,44 +61,286 @@ void CVarSystem::Shutdown() cvarFile << std::setw( 4 ) << cvarArchive << std::endl; } +#pragma region Parsing - // Run a console command. -// The following formats are currently supported: -// - [convar name]: Output the current value for a console variable -// - [convar name] [value]: Update a console variable with a new value -void CVarSystem::Run( const char* command ) +// I hate parsing text in C/C++. This is super messy. +// It works though. + +std::vector CVarSystem::GetStatementArguments( std::string_view statement, size_t cursor, size_t& cursorIndex ) { - std::string inputString = std::string( command ); + std::vector arguments; - std::stringstream ss( inputString ); + auto begin = statement.begin(); + auto end = statement.end(); - std::string cvarName, cvarValue; - ss >> cvarName >> cvarValue; + bool quoted = false; + bool commented = false; + std::string_view::const_iterator argumentStart = begin; + char lastChar = ' '; - std::stringstream valueStream( cvarValue ); + cursorIndex = -1; + bool cursorReached = false; - if ( !Exists( cvarName ) ) + auto setCursor = [&]( auto argStart, auto argEnd ) { + size_t argStartIndex = argStart - begin; + size_t argEndIndex = argEnd - begin; + bool set = !cursorReached && ( argStartIndex <= cursor && cursor < argEndIndex ); + if ( set ) + { + cursorIndex = arguments.size() - 1; + cursorReached = true; + } + }; + + for ( auto curChar = begin; curChar != end; curChar++ ) { - spdlog::info( "{} is not a valid command or variable", cvarName ); - return; + size_t curCharIndex = curChar - begin; + auto nextChar = curChar + 1; + + if ( quoted ) + { + if ( *curChar == '"' ) + { + quoted = !quoted; + arguments.emplace_back( argumentStart + 1, curChar ); + setCursor( argumentStart, curChar ); + } + else if ( nextChar == end ) + { + arguments.emplace_back( argumentStart + 1, end ); + setCursor( argumentStart, end ); + } + } + else + { + switch ( *curChar ) + { + case '"': + if ( lastChar != ' ' ) + { + arguments.emplace_back( argumentStart, curChar ); + setCursor( argumentStart, curChar ); + } + + quoted = !quoted; + argumentStart = curChar; + break; + + case ' ': + switch ( lastChar ) + { + case ' ': + case '"': + break; + default: + arguments.emplace_back( argumentStart, curChar ); + setCursor( argumentStart, curChar ); + break; + } + break; + + case '/': + if ( nextChar != end ) + { + if ( *nextChar == '/' ) + { + switch ( lastChar ) + { + case ' ': + case '"': + break; + default: + arguments.emplace_back( argumentStart, curChar ); + + break; + } + + setCursor( argumentStart, curChar ); + + // We've entered a comment, nothing left to do, so we bail + return arguments; + } + } + break; + + default: + switch ( lastChar ) + { + case ' ': + case '"': + argumentStart = curChar; + break; + } + + if ( nextChar == end ) + { + arguments.emplace_back( argumentStart, end ); + setCursor( argumentStart, end ); + } + + break; + } + } + + lastChar = *curChar; } - CVarEntry& entry = GetEntry( cvarName ); + return arguments; +} + +static std::tuple GetNextStatementLength( std::string_view line ) +{ + bool skip = false; + bool quoted = false; + bool commented = false; + int totalLength = line.length(); + int length = 0; + + auto curChar = line.begin(); + auto nextChar = curChar + 1; - if ( entry.m_flags & CVarFlags::Command ) + for ( ; curChar != line.end(); curChar++, length++ ) { - InvokeCommand( entry, {} ); + nextChar = curChar + 1; + + if ( !commented ) + { + if ( *curChar == '"' ) + { + quoted = !quoted; + continue; + } + + if ( !quoted ) + { + if ( nextChar != line.end() ) + { + if ( *curChar == '/' && *nextChar == '/' ) + { + commented = true; + continue; + } + } + + if ( *curChar == ';' ) + { + skip = true; + break; + } + } + + if ( *curChar == '\n' ) + { + skip = true; + break; + } + } } - else + + return { ( curChar != line.end() ? length : totalLength ), skip }; +} + +std::vector CVarSystem::GetStatements( const std::string& line, size_t cursor, size_t& cursorIndex ) +{ + auto begin = line.begin(); + auto end = line.end(); + std::vector statements; + + std::string_view remaining( line ); + bool cursorReached = false; + + cursorIndex = -1; + + auto setCursor = [&]( size_t statementLength ) { + if ( statementLength < cursor ) + { + cursor -= statementLength; + } + else if ( !cursorReached ) + { + cursorIndex = statements.size() - 1; + cursorReached = true; + } + }; + + while ( !remaining.empty() ) { - if ( valueStream.str().size() > 0 ) + auto [length, skip] = GetNextStatementLength( remaining ); + int skip_length = skip ? length + 1 : length; + + std::string_view statement = remaining.substr( 0, length ); + + const char* whitespace = " \t\r"; + + size_t prefix = std::min( statement.find_first_not_of( whitespace ), statement.size() ); + statement.remove_prefix( prefix ); + + size_t suffix = std::min( statement.size() - statement.find_last_not_of( whitespace ) - 1, statement.size() ); + statement.remove_suffix( suffix ); + + if ( !statement.empty() ) + statements.push_back( statement ); + setCursor( skip_length ); + + remaining = remaining.substr( skip_length, remaining.length() ); + } + + return statements; +} + +std::vector CVarSystem::GetStatementArguments( std::string_view statement ) +{ + size_t cursorIndex; + return GetStatementArguments( statement, 0, cursorIndex ); +} + +std::vector CVarSystem::GetStatements( const std::string& line ) +{ + size_t cursor = 0, cursorIndex; + return GetStatements( line, cursor, cursorIndex ); +} + +#pragma endregion + +void CVarSystem::Run( const char* command ) +{ + std::string inputString = std::string( command ); + + std::vector statements = GetStatements( inputString ); + + for ( std::string_view& statement : statements ) + { + std::vector arguments = GetStatementArguments( statement ); + + if ( arguments.size() < 1 ) + continue; + + std::string& arg0 = arguments.at( 0 ); + + if ( !Exists( arg0 ) ) + { + spdlog::info( "{} is not a valid command or variable", arg0 ); + continue; + } + + CVarEntry& entry = GetEntry( arg0 ); + + if ( entry.m_flags & CVarFlags::Command ) { - FromString( entry, cvarValue ); + auto passedArguments = std::vector( arguments.begin() + 1, arguments.end() ); + InvokeCommand( entry, passedArguments ); } else { - cvarValue = ToString( entry ); - spdlog::info( "{} is '{}'", cvarName, cvarValue ); + if ( arguments.size() == 1 ) + { + spdlog::info( "{} is '{}'", arg0, ToString( entry ) ); + } + else + { + // Guaranteed to be at least 2 + FromString( entry, arguments.at( 1 ) ); + } } } } @@ -340,7 +582,10 @@ static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables #ifndef __clang__ // List all available cvars instance.ForEach( [&]( CVarEntry& entry ) { - spdlog::info( "- '{}': '{}'", entry.m_name, instance.ToString( entry ) ); + if ( entry.m_flags & CVarFlags::Command ) + spdlog::info( "- '{}' (command)", entry.m_name ); + else + spdlog::info( "- '{}': '{}' (variable)", entry.m_name, instance.ToString( entry ) ); spdlog::info( "\t{}", entry.m_description ); } ); #endif @@ -362,5 +607,10 @@ static CCmd cvartest_command( "cvartest.command", CVarFlags::None, "A test comma []( std::vector arguments ) { spdlog::trace( "cvartest.command has been invoked! Hooray" ); + + for ( int i = 0; i < arguments.size(); i++ ) + { + spdlog::trace( "\t{} - '{}'", i, arguments.at( i ) ); + } } ); \ No newline at end of file diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 321417c1..c3aa5d6d 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -90,6 +90,30 @@ class CVarSystem void Startup(); void Shutdown(); + /// + /// Get command arguments from a statement, for use with GetStatements + /// Ignores comments starting with "//" + /// + /// The statement to get arguments from + /// Where the user's cursor is currently within the statement line + /// Return value of + static std::vector GetStatementArguments( std::string_view statement, size_t cursor, size_t& cursorIndex ); + + /// + /// Get the statements of a line, with each statement separated by ";" + /// + /// + /// + static std::vector GetStatements( const std::string& line, size_t cursor, size_t& cursorIndex ); + + // Variants for when you don't care about any cursor + static std::vector GetStatementArguments( std::string_view statement ); + static std::vector GetStatements( const std::string& line ); + + /// + /// Run a command in the console + /// + /// void Run( const char* command ); /// From d0d0b37369bc628579a872962a789379b8ca8bb8 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Sun, 29 Jan 2023 22:33:50 -0800 Subject: [PATCH 08/19] Shuffle the CVarEntry& variants to the struct, made no sense the other way round --- Source/Host/cvarmanager.cpp | 78 ++++++++++++++--------------- Source/Host/cvarmanager.h | 98 +++++++++++++++++++------------------ 2 files changed, 90 insertions(+), 86 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 8a6855bb..24e61627 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -42,7 +42,7 @@ void CVarSystem::Startup() CVarEntry& entry = m_cvarEntries[hash]; if ( entry.m_flags & CVarFlags::Archive ) - FromString( entry, value ); + entry.FromString( value ); } } @@ -328,18 +328,18 @@ void CVarSystem::Run( const char* command ) if ( entry.m_flags & CVarFlags::Command ) { auto passedArguments = std::vector( arguments.begin() + 1, arguments.end() ); - InvokeCommand( entry, passedArguments ); + entry.InvokeCommand( passedArguments ); } else { if ( arguments.size() == 1 ) { - spdlog::info( "{} is '{}'", arg0, ToString( entry ) ); + spdlog::info( "{} is '{}'", arg0, entry.ToString() ); } else { // Guaranteed to be at least 2 - FromString( entry, arguments.at( 1 ) ); + entry.FromString( arguments.at( 1 ) ); } } } @@ -390,11 +390,11 @@ void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, st } -void CVarSystem::InvokeCommand( CVarEntry& entry, std::vector arguments ) +void CVarEntry::InvokeCommand( std::vector arguments ) { - assert( entry.m_flags & CVarFlags::Command ); // Should be a command + assert( m_flags & CVarFlags::Command ); // Should be a command - auto callback = std::any_cast( entry.m_callback ); + auto callback = std::any_cast( m_callback ); if ( callback ) { @@ -404,88 +404,88 @@ void CVarSystem::InvokeCommand( CVarEntry& entry, std::vector argum void CVarSystem::InvokeCommand( std::string name, std::vector arguments ) { - InvokeCommand( GetEntry( name ), arguments ); + GetEntry( name ).InvokeCommand( arguments ); } -std::string CVarSystem::GetString( CVarEntry& entry ) +std::string CVarEntry::GetString() { - return GetVariable( entry ); + return GetVariable(); } std::string CVarSystem::GetString( std::string name ) { - return GetString( GetEntry( name ) ); + return GetEntry( name ).GetString(); } -float CVarSystem::GetFloat( CVarEntry& entry ) +float CVarEntry::GetFloat() { - return GetVariable( entry ); + return GetVariable(); } float CVarSystem::GetFloat( std::string name ) { - return GetFloat( GetEntry( name ) ); + return GetEntry( name ).GetFloat(); } -bool CVarSystem::GetBool( CVarEntry& entry ) +bool CVarEntry::GetBool() { - return GetVariable( entry ); + return GetVariable(); } bool CVarSystem::GetBool( std::string name ) { - return GetBool( GetEntry( name ) ); + return GetEntry( name ).GetBool(); } -void CVarSystem::SetString( CVarEntry& entry, std::string value ) +void CVarEntry::SetString( std::string value ) { - SetVariable( entry, value ); + SetVariable( value ); } void CVarSystem::SetString( std::string name, std::string value ) { - SetString( GetEntry( name ), value ); + GetEntry( name ).SetString( value ); } -void CVarSystem::SetFloat( CVarEntry& entry, float value ) +void CVarEntry::SetFloat( float value ) { - SetVariable( entry, value ); + SetVariable( value ); } void CVarSystem::SetFloat( std::string name, float value ) { - SetFloat( GetEntry( name ), value ); + GetEntry( name ).SetFloat( value ); } -void CVarSystem::SetBool( CVarEntry& entry, bool value ) +void CVarEntry::SetBool( bool value ) { - SetVariable( entry, value ); + SetVariable( value ); } void CVarSystem::SetBool( std::string name, bool value ) { - SetBool( GetEntry( name ), value ); + GetEntry( name ).SetBool( value ); } -void CVarSystem::FromString( CVarEntry& entry, std::string valueStr ) +void CVarEntry::FromString( std::string valueStr ) { std::stringstream valueStream( valueStr ); - auto& type = entry.m_value.type(); + auto& type = m_value.type(); if ( type == typeid( float ) ) { float value; valueStream >> value; - SetVariable( entry, value ); + SetVariable( value ); } else if ( type == typeid( bool ) ) { @@ -498,38 +498,38 @@ void CVarSystem::FromString( CVarEntry& entry, std::string valueStr ) else assert( false ); // Invalid bool value - SetVariable( entry, value ); + SetVariable( value ); } else if ( type == typeid( std::string ) ) { - SetVariable( entry, valueStr ); + SetVariable( valueStr ); } } void CVarSystem::FromString( std::string name, std::string valueStr ) { - FromString( GetEntry( name ), valueStr ); + GetEntry( name ).FromString( valueStr ); } -std::string CVarSystem::ToString( CVarEntry& entry ) +std::string CVarEntry::ToString() { - const std::type_info& type = entry.m_value.type(); + const std::type_info& type = m_value.type(); std::string valueStr; if ( type == typeid( std::string ) ) - valueStr = std::any_cast( entry.m_value ); + valueStr = std::any_cast( m_value ); else if ( type == typeid( float ) ) - valueStr = std::to_string( std::any_cast( entry.m_value ) ); + valueStr = std::to_string( std::any_cast( m_value ) ); else if ( type == typeid( bool ) ) - valueStr = std::any_cast( entry.m_value ) ? "true" : "false"; + valueStr = std::any_cast( m_value ) ? "true" : "false"; return valueStr; } std::string CVarSystem::ToString( std::string name ) { - return ToString( GetEntry( name ) ); + return GetEntry( name ).ToString(); } @@ -585,7 +585,7 @@ static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables if ( entry.m_flags & CVarFlags::Command ) spdlog::info( "- '{}' (command)", entry.m_name ); else - spdlog::info( "- '{}': '{}' (variable)", entry.m_name, instance.ToString( entry ) ); + spdlog::info( "- '{}': '{}' (variable)", entry.m_name, entry.ToString() ); spdlog::info( "\t{}", entry.m_description ); } ); #endif diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index c3aa5d6d..ee67e914 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -19,6 +19,7 @@ using CVarCallback = std::function; using CCmdCallback = std::function )>; + enum CVarFlags : uint32_t { None = 0, @@ -42,8 +43,17 @@ enum CVarFlags : uint32_t Game = 1 << 5 }; + struct CVarEntry { +private: + template + T GetVariable(); + + template + void SetVariable( T value ); + +public: std::string m_name; std::string m_description; @@ -51,8 +61,49 @@ struct CVarEntry std::any m_value; std::any m_callback; + + void InvokeCommand( std::vector arguments ); + + std::string GetString(); + float GetFloat(); + bool GetBool(); + + void SetString( std::string value ); + void SetFloat( float value ); + void SetBool( bool value ); + + void FromString( std::string valueStr ); + std::string ToString(); }; +template +inline T CVarEntry::GetVariable() +{ + assert( !( m_flags & CVarFlags::Command ) ); // Should be a variable + + return std::any_cast( m_value ); +} + +template +inline void CVarEntry::SetVariable( T value ) +{ + assert( !( m_flags & CVarFlags::Command ) ); // Should be a variable + + T lastValue = std::any_cast( m_value ); + + m_value = value; + + auto callback = std::any_cast>( m_callback ); + + if ( callback ) + { + callback( lastValue, value ); + } + + spdlog::info( "{} was set to '{}'.", m_name, value ); +} + + class CVarManager : ISubSystem { public: @@ -69,12 +120,6 @@ class CVarSystem template void RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ); - template - T GetVariable( CVarEntry& entry ); - - template - void SetVariable( CVarEntry& entry, T value ); - public: // @@ -131,31 +176,17 @@ class CVarSystem void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ); - void InvokeCommand( CVarEntry& entry, std::vector arguments ); void InvokeCommand( std::string name, std::vector arguments ); - std::string GetString( CVarEntry& entry ); std::string GetString( std::string name ); - - float GetFloat( CVarEntry& entry ); float GetFloat( std::string name ); - - bool GetBool( CVarEntry& entry ); bool GetBool( std::string name ); - void SetString( CVarEntry& entry, std::string value ); void SetString( std::string name, std::string value ); - - void SetFloat( CVarEntry& entry, float value ); void SetFloat( std::string name, float value ); - - void SetBool( CVarEntry& entry, bool value ); void SetBool( std::string name, bool value ); - void FromString( CVarEntry& entry, std::string valueStr ); void FromString( std::string name, std::string valueStr ); - - std::string ToString( CVarEntry& entry ); std::string ToString( std::string name ); void ForEach( std::function func ); @@ -179,33 +210,6 @@ inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags f m_cvarEntries[hash] = entry; } -template -inline T CVarSystem::GetVariable( CVarEntry& entry ) -{ - assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable - - return std::any_cast( entry.m_value ); -} - -template -inline void CVarSystem::SetVariable( CVarEntry& entry, T value ) -{ - assert( !( entry.m_flags & CVarFlags::Command ) ); // Should be a variable - - T lastValue = std::any_cast( entry.m_value ); - - entry.m_value = value; - - auto callback = std::any_cast>( entry.m_callback ); - - if ( callback ) - { - callback( lastValue, value ); - } - - spdlog::info( "{} was set to '{}'.", entry.m_name, value ); -} - // ---------------------------------------- // Native CVar interface // ---------------------------------------- From 1454a1c9ec50dbe1f5ed514849fd0067428a49de Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 00:13:58 -0800 Subject: [PATCH 09/19] Make CVarSystem API a little more user-friendly, add a few bindings --- Source/Common/Utils/Glue/ConsoleSystem.cs | 27 +++++ Source/Host/consolesystem.h | 46 ++++++++ Source/Host/cvarmanager.cpp | 121 ++++++++++++++++------ Source/Host/cvarmanager.h | 24 +++-- 4 files changed, 176 insertions(+), 42 deletions(-) diff --git a/Source/Common/Utils/Glue/ConsoleSystem.cs b/Source/Common/Utils/Glue/ConsoleSystem.cs index 89957863..501117a8 100644 --- a/Source/Common/Utils/Glue/ConsoleSystem.cs +++ b/Source/Common/Utils/Glue/ConsoleSystem.cs @@ -6,4 +6,31 @@ public static void Run( string command ) { Glue.ConsoleSystem.Run( command ); } + + public static float GetFloat( string name ) + { + return Glue.ConsoleSystem.GetFloat( name ); + } + + public static bool GetBool( string name ) + { + return Glue.ConsoleSystem.GetBool( name ); + } + + public static void Set( string name, string value ) + { + // I figure we won't even want to think about what the underlying type is, + // so let's use FromString by default + Glue.ConsoleSystem.FromString( name, value ); + } + + public static void Set( string name, float value ) + { + Glue.ConsoleSystem.SetFloat( name, value ); + } + + public static void Set( string name, bool value ) + { + Glue.ConsoleSystem.SetBool( name, value ); + } } diff --git a/Source/Host/consolesystem.h b/Source/Host/consolesystem.h index e74b37ec..218411a9 100644 --- a/Source/Host/consolesystem.h +++ b/Source/Host/consolesystem.h @@ -12,4 +12,50 @@ namespace ConsoleSystem { CVarSystem::Instance().Run( command ); } + + // TODO: Not until all memory leak concerns are addressed +/* + GENERATE_BINDINGS inline const char* GetString( const char* name ) + { + return CVarSystem::Instance().GetString( name ).c_str(); + } +*/ + + GENERATE_BINDINGS inline float GetFloat( const char* name ) + { + return CVarSystem::Instance().GetFloat( name ); + } + + GENERATE_BINDINGS inline bool GetBool( const char* name ) + { + return CVarSystem::Instance().GetBool( name ); + } + + GENERATE_BINDINGS inline void SetString( const char* name, const char* value ) + { + CVarSystem::Instance().SetString( name, value ); + } + + GENERATE_BINDINGS inline void SetFloat( const char* name, float value ) + { + CVarSystem::Instance().SetFloat( name, value ); + } + + GENERATE_BINDINGS inline void SetBool( const char* name, bool value ) + { + CVarSystem::Instance().SetBool( name, value ); + } + + // TODO: Not until all memory leak concerns are addressed +/* + GENERATE_BINDINGS inline const char* ToString( const char* name ) + { + + } +*/ + + GENERATE_BINDINGS inline void FromString( const char* name, const char* valueStr ) + { + CVarSystem::Instance().FromString( name, valueStr ); + } } // namespace ConsoleSystem \ No newline at end of file diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 24e61627..990335f7 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -325,7 +325,7 @@ void CVarSystem::Run( const char* command ) CVarEntry& entry = GetEntry( arg0 ); - if ( entry.m_flags & CVarFlags::Command ) + if ( entry.IsCommand() ) { auto passedArguments = std::vector( arguments.begin() + 1, arguments.end() ); entry.InvokeCommand( passedArguments ); @@ -392,7 +392,7 @@ void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, st void CVarEntry::InvokeCommand( std::vector arguments ) { - assert( m_flags & CVarFlags::Command ); // Should be a command + assert( IsCommand() ); auto callback = std::any_cast( m_callback ); @@ -404,75 +404,144 @@ void CVarEntry::InvokeCommand( std::vector arguments ) void CVarSystem::InvokeCommand( std::string name, std::vector arguments ) { - GetEntry( name ).InvokeCommand( arguments ); + if ( !Exists( name ) ) + { + spdlog::error( "Tried to invoke command '{}', but it doesn't exist!", name ); + return; + } + + CVarEntry& entry = GetEntry( name ); + + if ( !entry.IsCommand() ) + { + spdlog::error( "Tried to invoke command '{}', but it's a variable!", name ); + return; + } + + entry.InvokeCommand( arguments ); } std::string CVarEntry::GetString() { - return GetVariable(); + return GetValue(); } std::string CVarSystem::GetString( std::string name ) { + if ( !Exists( name ) ) + { + return ""; + } + return GetEntry( name ).GetString(); } float CVarEntry::GetFloat() { - return GetVariable(); + return GetValue(); } float CVarSystem::GetFloat( std::string name ) { + if ( !Exists( name ) ) + { + return 0.0f; + } + return GetEntry( name ).GetFloat(); } bool CVarEntry::GetBool() { - return GetVariable(); + return GetValue(); } bool CVarSystem::GetBool( std::string name ) { + if ( !Exists( name ) ) + { + return false; + } + return GetEntry( name ).GetBool(); } void CVarEntry::SetString( std::string value ) { - SetVariable( value ); + SetValue( value ); } void CVarSystem::SetString( std::string name, std::string value ) { + if ( !Exists( name ) ) + { + return; + } + GetEntry( name ).SetString( value ); } void CVarEntry::SetFloat( float value ) { - SetVariable( value ); + SetValue( value ); } void CVarSystem::SetFloat( std::string name, float value ) { + if ( !Exists( name ) ) + { + return; + } + GetEntry( name ).SetFloat( value ); } void CVarEntry::SetBool( bool value ) { - SetVariable( value ); + SetValue( value ); } void CVarSystem::SetBool( std::string name, bool value ) { + if ( !Exists( name ) ) + { + return; + } + GetEntry( name ).SetBool( value ); } +std::string CVarEntry::ToString() +{ + const std::type_info& type = m_value.type(); + std::string valueStr; + + if ( type == typeid( std::string ) ) + valueStr = std::any_cast( m_value ); + else if ( type == typeid( float ) ) + valueStr = std::to_string( std::any_cast( m_value ) ); + else if ( type == typeid( bool ) ) + valueStr = std::any_cast( m_value ) ? "true" : "false"; + + return valueStr; +} + +std::string CVarSystem::ToString( std::string name ) +{ + if ( !Exists( name ) ) + { + return ""; + } + + return GetEntry( name ).ToString(); +} + void CVarEntry::FromString( std::string valueStr ) { @@ -485,7 +554,7 @@ void CVarEntry::FromString( std::string valueStr ) float value; valueStream >> value; - SetVariable( value ); + SetValue( value ); } else if ( type == typeid( bool ) ) { @@ -498,41 +567,25 @@ void CVarEntry::FromString( std::string valueStr ) else assert( false ); // Invalid bool value - SetVariable( value ); + SetValue( value ); } else if ( type == typeid( std::string ) ) { - SetVariable( valueStr ); + SetValue( valueStr ); } } void CVarSystem::FromString( std::string name, std::string valueStr ) { + if ( !Exists( name ) ) + { + return; + } + GetEntry( name ).FromString( valueStr ); } -std::string CVarEntry::ToString() -{ - const std::type_info& type = m_value.type(); - std::string valueStr; - - if ( type == typeid( std::string ) ) - valueStr = std::any_cast( m_value ); - else if ( type == typeid( float ) ) - valueStr = std::to_string( std::any_cast( m_value ) ); - else if ( type == typeid( bool ) ) - valueStr = std::any_cast( m_value ) ? "true" : "false"; - - return valueStr; -} - -std::string CVarSystem::ToString( std::string name ) -{ - return GetEntry( name ).ToString(); -} - - void CVarSystem::ForEach( std::function func ) { for ( auto& entry : m_cvarEntries ) @@ -582,7 +635,7 @@ static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables #ifndef __clang__ // List all available cvars instance.ForEach( [&]( CVarEntry& entry ) { - if ( entry.m_flags & CVarFlags::Command ) + if ( entry.IsCommand() ) spdlog::info( "- '{}' (command)", entry.m_name ); else spdlog::info( "- '{}': '{}' (variable)", entry.m_name, entry.ToString() ); diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index ee67e914..9db1f474 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -48,10 +48,10 @@ struct CVarEntry { private: template - T GetVariable(); + T GetValue(); template - void SetVariable( T value ); + void SetValue( T value ); public: std::string m_name; @@ -62,6 +62,8 @@ struct CVarEntry std::any m_value; std::any m_callback; + inline bool IsCommand() const { return m_flags & CVarFlags::Command; } + void InvokeCommand( std::vector arguments ); std::string GetString(); @@ -72,22 +74,28 @@ struct CVarEntry void SetFloat( float value ); void SetBool( bool value ); - void FromString( std::string valueStr ); std::string ToString(); + void FromString( std::string valueStr ); }; template -inline T CVarEntry::GetVariable() +inline T CVarEntry::GetValue() { - assert( !( m_flags & CVarFlags::Command ) ); // Should be a variable + if ( IsCommand() || m_value.type() != typeid( T ) ) + { + return {}; + } return std::any_cast( m_value ); } template -inline void CVarEntry::SetVariable( T value ) +inline void CVarEntry::SetValue( T value ) { - assert( !( m_flags & CVarFlags::Command ) ); // Should be a variable + if ( IsCommand() || m_value.type() != typeid( T ) ) + { + return; + } T lastValue = std::any_cast( m_value ); @@ -186,8 +194,8 @@ class CVarSystem void SetFloat( std::string name, float value ); void SetBool( std::string name, bool value ); - void FromString( std::string name, std::string valueStr ); std::string ToString( std::string name ); + void FromString( std::string name, std::string valueStr ); void ForEach( std::function func ); void ForEach( std::string filter, std::function func ); From fc6303ad901e58f898ce5525bf1dcaf51f3c2d0c Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 17:06:29 -0800 Subject: [PATCH 10/19] Change Run, GetStatements, GetStatementArguments' docs/args a tad --- Source/Host/cvarmanager.cpp | 16 ++++++++-------- Source/Host/cvarmanager.h | 27 ++++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 990335f7..9446b459 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -240,13 +240,13 @@ static std::tuple GetNextStatementLength( std::string_view line ) return { ( curChar != line.end() ? length : totalLength ), skip }; } -std::vector CVarSystem::GetStatements( const std::string& line, size_t cursor, size_t& cursorIndex ) +std::vector CVarSystem::GetStatements( const std::string& input, size_t cursor, size_t& cursorIndex ) { - auto begin = line.begin(); - auto end = line.end(); + auto begin = input.begin(); + auto end = input.end(); std::vector statements; - std::string_view remaining( line ); + std::string_view remaining( input ); bool cursorReached = false; cursorIndex = -1; @@ -294,17 +294,17 @@ std::vector CVarSystem::GetStatementArguments( std::string_view sta return GetStatementArguments( statement, 0, cursorIndex ); } -std::vector CVarSystem::GetStatements( const std::string& line ) +std::vector CVarSystem::GetStatements( const std::string& input ) { size_t cursor = 0, cursorIndex; - return GetStatements( line, cursor, cursorIndex ); + return GetStatements( input, cursor, cursorIndex ); } #pragma endregion -void CVarSystem::Run( const char* command ) +void CVarSystem::Run( const char* input ) { - std::string inputString = std::string( command ); + std::string inputString = std::string( input ); std::vector statements = GetStatements( inputString ); diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index 9db1f474..dba84070 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -144,30 +144,31 @@ class CVarSystem void Shutdown(); /// - /// Get command arguments from a statement, for use with GetStatements - /// Ignores comments starting with "//" + /// Get command arguments from a statement, for use with GetStatements. + /// Ignores comments, starting with "//". /// /// The statement to get arguments from - /// Where the user's cursor is currently within the statement line - /// Return value of + /// Where the user's cursor currently is within the statement string + /// Returns which argument the cursor is within static std::vector GetStatementArguments( std::string_view statement, size_t cursor, size_t& cursorIndex ); /// - /// Get the statements of a line, with each statement separated by ";" + /// Get the statements from a string, with each statement separated either by ";" or a newline. /// - /// - /// - static std::vector GetStatements( const std::string& line, size_t cursor, size_t& cursorIndex ); + /// + /// Where the user's cursor currently is within the input string + /// Returns which statement the cursor is within + static std::vector GetStatements( const std::string& input, size_t cursor, size_t& cursorIndex ); - // Variants for when you don't care about any cursor + // Variants for uses without a text cursor static std::vector GetStatementArguments( std::string_view statement ); - static std::vector GetStatements( const std::string& line ); + static std::vector GetStatements( const std::string& input ); /// - /// Run a command in the console + /// Run statements in the console /// - /// - void Run( const char* command ); + /// + void Run( const char* input ); /// /// Check if a specific convar exists From 9753b5d644ceb12405e33e4e8b8b5529a096f12f Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 17:07:32 -0800 Subject: [PATCH 11/19] Managed ConCmds --- Source/Common/Console/CVarFlags.cs | 32 ++++ Source/Common/Console/ConCmd.cs | 36 ++++ Source/Common/Console/ConVar.cs | 16 ++ Source/Common/Console/ConsoleDispatchInfo.cs | 41 ++++ .../Common/Console/ConsoleSystem.Internal.cs | 110 +++++++++++ .../{Utils/Glue => Console}/ConsoleSystem.cs | 17 +- Source/Host/consolesystem.h | 35 ++++ Source/Host/cvarmanager.cpp | 180 +++++++++++++++++- Source/Host/cvarmanager.h | 93 ++++----- Source/Host/hostmanager.cpp | 22 +++ Source/Host/hostmanager.h | 10 + Source/Hotload/Assembly/ProjectAssembly.cs | 5 + Source/Hotload/Main.cs | 55 ++++++ 13 files changed, 581 insertions(+), 71 deletions(-) create mode 100644 Source/Common/Console/CVarFlags.cs create mode 100644 Source/Common/Console/ConCmd.cs create mode 100644 Source/Common/Console/ConVar.cs create mode 100644 Source/Common/Console/ConsoleDispatchInfo.cs create mode 100644 Source/Common/Console/ConsoleSystem.Internal.cs rename Source/Common/{Utils/Glue => Console}/ConsoleSystem.cs (70%) diff --git a/Source/Common/Console/CVarFlags.cs b/Source/Common/Console/CVarFlags.cs new file mode 100644 index 00000000..806528d8 --- /dev/null +++ b/Source/Common/Console/CVarFlags.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mocha.Common; + +public enum CVarFlags +{ + // Mirrors native + + None = 0, + + // If this isn't present, it's inherently assumed to be a variable + Command = 1 << 0, + + // If this is present, it lives in managed space + Managed = 1 << 1, + + // This cvar was created by the game, it should be wiped on hotload + Game = 1 << 2, + + // Save this convar to cvars.json + Archive = 1 << 3, + + Cheat = 1 << 4, + + Temp = 1 << 5, + + Replicated = 1 << 6, +} diff --git a/Source/Common/Console/ConCmd.cs b/Source/Common/Console/ConCmd.cs new file mode 100644 index 00000000..7b2614d1 --- /dev/null +++ b/Source/Common/Console/ConCmd.cs @@ -0,0 +1,36 @@ +namespace Mocha.Common; + +public static class ConCmd +{ + public abstract class BaseAttribute : Attribute + { + public string Name { get; init; } + public CVarFlags Flags { get; init; } + public string Description { get; init; } + + public BaseAttribute( string name, CVarFlags flags, string description ) + { + Name = name; + Flags = flags; + Description = description; + } + } + + // public class ServerAttribute + // public class ClientAttribute + + public sealed class TestAttribute : BaseAttribute + { + public TestAttribute( string name ) + : base( name, CVarFlags.None, "" ) + { + + } + + public TestAttribute( string name, string description ) + : base( name, CVarFlags.None, description ) + { + + } + } +} diff --git a/Source/Common/Console/ConVar.cs b/Source/Common/Console/ConVar.cs new file mode 100644 index 00000000..932c3e35 --- /dev/null +++ b/Source/Common/Console/ConVar.cs @@ -0,0 +1,16 @@ +namespace Mocha.Common; + +public static class ConVar +{ + public abstract class BaseAttribute : Attribute + { + public required string Name { get; init; } + public required CVarFlags Flags { get; init; } + public required string Description { get; init; } + } + + public class TestAttribute : Attribute + { + + } +} diff --git a/Source/Common/Console/ConsoleDispatchInfo.cs b/Source/Common/Console/ConsoleDispatchInfo.cs new file mode 100644 index 00000000..e0f4f66a --- /dev/null +++ b/Source/Common/Console/ConsoleDispatchInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Mocha.Common.Console; + + +[StructLayout( LayoutKind.Sequential )] +public struct ConCmdDispatchInfo +{ + public IntPtr name; + public IntPtr data; + public int size; +} + +[StructLayout( LayoutKind.Sequential )] +public struct StringCVarDispatchInfo +{ + public IntPtr name; + public IntPtr oldValue; + public IntPtr newValue; +} + +[StructLayout( LayoutKind.Sequential )] +public struct FloatCVarDispatchInfo +{ + public IntPtr name; + public float oldValue; + public float newValue; +} + +[StructLayout( LayoutKind.Sequential )] +public struct BoolCVarDispatchInfo +{ + public IntPtr name; + public bool oldValue; + public bool newValue; +} diff --git a/Source/Common/Console/ConsoleSystem.Internal.cs b/Source/Common/Console/ConsoleSystem.Internal.cs new file mode 100644 index 00000000..5ca969da --- /dev/null +++ b/Source/Common/Console/ConsoleSystem.Internal.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Mocha.Common; + +public static partial class ConsoleSystem +{ + public static class Internal + { + internal static List s_items = new(); + internal static Dictionary>> s_commandCallbacks = new(); + + internal static class ConVarCallbackStore + { + public static Dictionary> Callbacks = new(); + } + + internal static void RegisterCommand( string name, CVarFlags flags, string description, Action> callback ) + { + Glue.ConsoleSystem.RegisterCommand( name, flags, description ); + s_commandCallbacks[name] = callback; + s_items.Add( name ); + } + + internal static void RegisterStringConVar( string name, string value, CVarFlags flags, string description, Action callback ) + { + Glue.ConsoleSystem.RegisterString( name, value, flags, description ); + ConVarCallbackStore.Callbacks[name] = callback; + s_items.Add( name ); + } + + internal static void RegisterFloatConVar( string name, float value, CVarFlags flags, string description, Action callback ) + { + Glue.ConsoleSystem.RegisterFloat( name, value, flags, description ); + ConVarCallbackStore.Callbacks[name] = callback; + s_items.Add( name ); + } + + internal static void RegisterBoolConVar( string name, bool value, CVarFlags flags, string description, Action callback ) + { + Glue.ConsoleSystem.RegisterBool( name, value, flags, description ); + ConVarCallbackStore.Callbacks[name] = callback; + s_items.Add( name ); + } + + /// + /// Register all of the CVars in an assembly + /// + /// The assembly to grab from + /// Extra flags to force each CVar to have. Used mainly for hotloaded assemblies + public static void RegisterAssembly( Assembly assembly, CVarFlags extraFlags = CVarFlags.None ) + { + if ( assembly is null ) + return; + + foreach ( Type type in assembly.GetTypes() ) + { + foreach ( MethodInfo method in type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ) ) + { + ConCmd.BaseAttribute? customAttribute = method.GetCustomAttribute(); + if ( customAttribute is not null ) + { + RegisterCommand( customAttribute.Name, customAttribute.Flags | extraFlags, customAttribute.Description, + ( List arguments ) => method.Invoke( null, new object[] { arguments } ) ); + } + } + } + } + + public static void ClearGameCVars() + { + foreach ( var name in s_items.ToList() ) + { + bool exists = Glue.ConsoleSystem.Exists( name ); + CVarFlags flags = Glue.ConsoleSystem.GetFlags( name ); + + if ( exists && (flags & CVarFlags.Game) != 0 ) + { + s_items.Remove( name ); + s_commandCallbacks.Remove( name ); + ConVarCallbackStore.Callbacks.Remove( name ); + ConVarCallbackStore.Callbacks.Remove( name ); + ConVarCallbackStore.Callbacks.Remove( name ); + + Glue.ConsoleSystem.Remove( name ); + } + } + } + + public static void DispatchCommand( string name, List arguments ) + { + if ( s_commandCallbacks.TryGetValue( name, out var callback ) ) + { + callback( arguments ); + } + } + + public static void DispatchConVarCallback( string name, T oldValue, T newValue ) + { + if ( ConVarCallbackStore.Callbacks.TryGetValue( name, out var callback ) ) + { + callback( oldValue, newValue ); + } + } + } +} diff --git a/Source/Common/Utils/Glue/ConsoleSystem.cs b/Source/Common/Console/ConsoleSystem.cs similarity index 70% rename from Source/Common/Utils/Glue/ConsoleSystem.cs rename to Source/Common/Console/ConsoleSystem.cs index 501117a8..83f20791 100644 --- a/Source/Common/Utils/Glue/ConsoleSystem.cs +++ b/Source/Common/Console/ConsoleSystem.cs @@ -1,6 +1,10 @@ -namespace Mocha.Common; +using System; +using System.Reflection; +using Mocha.Common.Console; -public static class ConsoleSystem +namespace Mocha.Common; + +public static partial class ConsoleSystem { public static void Run( string command ) { @@ -19,9 +23,7 @@ public static bool GetBool( string name ) public static void Set( string name, string value ) { - // I figure we won't even want to think about what the underlying type is, - // so let's use FromString by default - Glue.ConsoleSystem.FromString( name, value ); + Glue.ConsoleSystem.SetString( name, value ); } public static void Set( string name, float value ) @@ -33,4 +35,9 @@ public static void Set( string name, bool value ) { Glue.ConsoleSystem.SetBool( name, value ); } + + public static void SetFromString( string name, string value ) + { + Glue.ConsoleSystem.FromString( name, value ); + } } diff --git a/Source/Host/consolesystem.h b/Source/Host/consolesystem.h index 218411a9..e6e649cb 100644 --- a/Source/Host/consolesystem.h +++ b/Source/Host/consolesystem.h @@ -13,6 +13,41 @@ namespace ConsoleSystem CVarSystem::Instance().Run( command ); } + GENERATE_BINDINGS inline bool Exists( const char* name ) + { + return CVarSystem::Instance().Exists( name ); + } + + GENERATE_BINDINGS inline void RegisterCommand( const char* name, CVarFlags flags, const char* description ) + { + CVarSystem::Instance().RegisterCommand( name, ( CVarFlags )( CVarFlags::Managed | flags ), description, nullptr ); + } + + GENERATE_BINDINGS inline void RegisterString( const char* name, const char* value, CVarFlags flags, const char* description ) + { + CVarSystem::Instance().RegisterString( name, value, ( CVarFlags )( CVarFlags::Managed | flags ), description, nullptr ); + } + + GENERATE_BINDINGS inline void RegisterFloat( const char* name, float value, CVarFlags flags, const char* description ) + { + CVarSystem::Instance().RegisterFloat( name, value, ( CVarFlags )( CVarFlags::Managed | flags ), description, nullptr ); + } + + GENERATE_BINDINGS inline void RegisterBool( const char* name, bool value, CVarFlags flags, const char* description ) + { + CVarSystem::Instance().RegisterBool( name, value, ( CVarFlags )( CVarFlags::Managed | flags ), description, nullptr ); + } + + GENERATE_BINDINGS inline void Remove( const char* name ) + { + CVarSystem::Instance().Remove( name ); + } + + GENERATE_BINDINGS inline CVarFlags GetFlags( const char* name ) + { + return CVarSystem::Instance().GetFlags( name ); + } + // TODO: Not until all memory leak concerns are addressed /* GENERATE_BINDINGS inline const char* GetString( const char* name ) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 9446b459..514c7d8f 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -1,6 +1,7 @@ #include "cvarmanager.h" #include +#include size_t CVarSystem::GetHash( std::string string ) { @@ -362,12 +363,32 @@ CVarEntry& CVarSystem::GetEntry( std::string name ) void CVarSystem::RegisterCommand( std::string name, CVarFlags flags, std::string description, CCmdCallback callback ) { - assert( callback != nullptr ); + // This *has* to have the command flag + flags = ( CVarFlags )( flags | CVarFlags::Command ); CVarEntry entry = {}; entry.m_name = name; - entry.m_description = description; - entry.m_flags = CVarFlags::Command | flags; + entry.m_description = ( description != "" ) ? description : "(no description)"; + entry.m_flags = flags; + entry.m_callback = callback; + + assert( entry.IsManaged() || callback != nullptr ); + + size_t hash = GetHash( name ); + m_cvarEntries[hash] = entry; +} + +template +inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) +{ + // This *must not* have the command flag + flags = flags & ~( CVarFlags::Command ); + + CVarEntry entry = {}; + entry.m_name = name; + entry.m_description = ( description != "" ) ? description : "(no description)"; + entry.m_flags = flags; + entry.m_value = value; entry.m_callback = callback; size_t hash = GetHash( name ); @@ -390,15 +411,40 @@ void CVarSystem::RegisterBool( std::string name, bool value, CVarFlags flags, st } +void CVarSystem::Remove( std::string name ) +{ + size_t hash = GetHash( name ); + m_cvarEntries.erase( hash ); +} + + void CVarEntry::InvokeCommand( std::vector arguments ) { assert( IsCommand() ); - auto callback = std::any_cast( m_callback ); - - if ( callback ) + if ( IsManaged() ) { - callback( arguments ); + std::vector managedArguments; + + for ( auto& argument : arguments ) + managedArguments.push_back( argument.c_str() ); + + CVarManagedCmdDispatchInfo info + { + m_name.c_str(), + managedArguments.data(), + managedArguments.size() + }; + + g_hostManager->DispatchCommand( info ); + } + else { + auto callback = std::any_cast( m_callback ); + + if ( callback ) + { + callback( arguments ); + } } } @@ -422,6 +468,78 @@ void CVarSystem::InvokeCommand( std::string name, std::vector argum } +CVarFlags CVarSystem::GetFlags( std::string name ) +{ + if ( !Exists( name ) ) + { + return CVarFlags::None; + } + + return ( CVarFlags )GetEntry( name ).m_flags; +} + + +// Putting this stuff in the header caused bad juju + +template +inline T CVarEntry::GetValue() +{ + if ( IsCommand() || m_value.type() != typeid( T ) ) + { + return {}; + } + + return std::any_cast( m_value ); +} + +template +inline void CVarEntry::SetValue( T value ) +{ + if ( IsCommand() || m_value.type() != typeid( T ) ) + { + return; + } + + T oldValue = std::any_cast( m_value ); + m_value = value; + + if ( IsManaged() ) + { + // This is kinda dirty + + if constexpr ( std::is_same::value ) + { + CVarManagedVarDispatchInfo stringInfo{ m_name.c_str(), oldValue.c_str(), value.c_str() }; + + g_hostManager->DispatchStringCVarCallback( stringInfo ); + } + else if constexpr ( std::is_same::value ) + { + CVarManagedVarDispatchInfo primitiveInfo{ m_name.c_str(), oldValue, value }; + + g_hostManager->DispatchFloatCVarCallback( primitiveInfo ); + } + else if constexpr ( std::is_same::value ) + { + CVarManagedVarDispatchInfo primitiveInfo{ m_name.c_str(), oldValue, value }; + + g_hostManager->DispatchBoolCVarCallback( primitiveInfo ); + } + } + else + { + auto callback = std::any_cast>( m_callback ); + + if ( callback ) + { + callback( oldValue, value ); + } + } + + spdlog::info( "{} was set to '{}'.", m_name, value ); +} + + std::string CVarEntry::GetString() { return GetValue(); @@ -626,6 +744,46 @@ void CVarManager::Shutdown() // Built-in CVars // ---------------------------------------- +static std::string GetFlagsString(CVarFlags flags) +{ + std::vector flagNames; + + if ( flags & CVarFlags::Command ) + flagNames.push_back( "command" ); + else + flagNames.push_back( "variable" ); + + if ( flags & CVarFlags::Managed ) + flagNames.push_back( "managed" ); + + if ( flags & CVarFlags::Game ) + flagNames.push_back( "game" ); + + if ( flags & CVarFlags::Archive ) + flagNames.push_back( "archive" ); + + if ( flags & CVarFlags::Cheat ) + flagNames.push_back( "cheat" ); + + if ( flags & CVarFlags::Temp ) + flagNames.push_back( "temp" ); + + if ( flags & CVarFlags::Replicated ) + flagNames.push_back( "replicated" ); + + std::stringstream ss; + + size_t len = flagNames.size(); + for ( int i = 0; i < len; i++ ) + { + ss << flagNames[i]; + if ( i != len - 1 ) + ss << ", "; + } + + return ss.str(); +} + static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables", []( std::vector arguments ) { @@ -634,11 +792,13 @@ static CCmd ccmd_list( "list", CVarFlags::None, "List all commands and variables // This fails on libclang so we'll ignore it for now... #ifndef __clang__ // List all available cvars - instance.ForEach( [&]( CVarEntry& entry ) { + instance.ForEach( [&]( CVarEntry& entry ) { + std::string flagNames = GetFlagsString( (CVarFlags) entry.m_flags ); + if ( entry.IsCommand() ) - spdlog::info( "- '{}' (command)", entry.m_name ); + spdlog::info( "- '{}' - {}", entry.m_name, flagNames ); else - spdlog::info( "- '{}': '{}' (variable)", entry.m_name, entry.ToString() ); + spdlog::info( "- '{}': '{}' - {}", entry.m_name, entry.ToString(), flagNames ); spdlog::info( "\t{}", entry.m_description ); } ); #endif diff --git a/Source/Host/cvarmanager.h b/Source/Host/cvarmanager.h index dba84070..af172ada 100644 --- a/Source/Host/cvarmanager.h +++ b/Source/Host/cvarmanager.h @@ -20,27 +20,46 @@ using CVarCallback = std::function; using CCmdCallback = std::function )>; -enum CVarFlags : uint32_t +struct CVarManagedCmdDispatchInfo +{ + const char* name; + void* data; + int size; +}; + +template +struct CVarManagedVarDispatchInfo +{ + const char* name; + T oldValue; + T newValue; +}; + + +enum CVarFlags : int32_t { None = 0, // If this isn't present, it's inherently assumed to be a variable Command = 1 << 0, + // If this is present, it lives in managed space + Managed = 1 << 1, + + // This cvar was created by the game, it should be wiped on hotload + Game = 1 << 2, + // Save this convar to cvars.json - Archive = 1 << 1, + Archive = 1 << 3, // TODO - Cheat = 1 << 2, + Cheat = 1 << 4, // TODO - Temp = 1 << 3, + Temp = 1 << 5, // TODO: Networked variables server -> client - Replicated = 1 << 4, - - // TODO: CVars created by the game, hotload these? - Game = 1 << 5 + Replicated = 1 << 6, }; @@ -57,15 +76,20 @@ struct CVarEntry std::string m_name; std::string m_description; - uint32_t m_flags; + int32_t m_flags; std::any m_value; std::any m_callback; inline bool IsCommand() const { return m_flags & CVarFlags::Command; } + inline bool IsManaged() const { return m_flags & CVarFlags::Managed; } + + // Commands void InvokeCommand( std::vector arguments ); + // Variables + std::string GetString(); float GetFloat(); bool GetBool(); @@ -78,39 +102,6 @@ struct CVarEntry void FromString( std::string valueStr ); }; -template -inline T CVarEntry::GetValue() -{ - if ( IsCommand() || m_value.type() != typeid( T ) ) - { - return {}; - } - - return std::any_cast( m_value ); -} - -template -inline void CVarEntry::SetValue( T value ) -{ - if ( IsCommand() || m_value.type() != typeid( T ) ) - { - return; - } - - T lastValue = std::any_cast( m_value ); - - m_value = value; - - auto callback = std::any_cast>( m_callback ); - - if ( callback ) - { - callback( lastValue, value ); - } - - spdlog::info( "{} was set to '{}'.", m_name, value ); -} - class CVarManager : ISubSystem { @@ -185,8 +176,12 @@ class CVarSystem void RegisterFloat( std::string name, float value, CVarFlags flags, std::string description, CVarCallback callback ); void RegisterBool( std::string name, bool value, CVarFlags flags, std::string description, CVarCallback callback ); + void Remove( std::string name ); + void InvokeCommand( std::string name, std::vector arguments ); + CVarFlags GetFlags( std::string name ); + std::string GetString( std::string name ); float GetFloat( std::string name ); bool GetBool( std::string name ); @@ -205,20 +200,6 @@ class CVarSystem inline static bool AsBool( std::string& argument ) { return argument == "true"; } }; -template -inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) -{ - CVarEntry entry = {}; - entry.m_name = name; - entry.m_description = description; - entry.m_flags = flags; - entry.m_value = value; - entry.m_callback = callback; - - size_t hash = GetHash( name ); - m_cvarEntries[hash] = entry; -} - // ---------------------------------------- // Native CVar interface // ---------------------------------------- diff --git a/Source/Host/hostmanager.cpp b/Source/Host/hostmanager.cpp index c8f1bd22..4f4b6d84 100644 --- a/Source/Host/hostmanager.cpp +++ b/Source/Host/hostmanager.cpp @@ -1,5 +1,7 @@ #include "hostmanager.h" +#include + void* HostGlobals::load_library( const char_t* path ) { HMODULE h = ::LoadLibraryW( path ); @@ -118,6 +120,26 @@ void HostManager::FireEvent( std::string eventName ) Invoke( "FireEvent", ( void* )eventName.c_str() ); } +void HostManager::DispatchCommand( CVarManagedCmdDispatchInfo info ) +{ + Invoke( "DispatchCommand", &info ); +} + +void HostManager::DispatchStringCVarCallback( CVarManagedVarDispatchInfo info ) +{ + Invoke( "DispatchStringCVarCallback", &info ); +} + +void HostManager::DispatchFloatCVarCallback( CVarManagedVarDispatchInfo info ) +{ + Invoke( "DispatchFloatCVarCallback", &info ); +} + +void HostManager::DispatchBoolCVarCallback( CVarManagedVarDispatchInfo info ) +{ + Invoke( "DispatchBoolCVarCallback", &info ); +} + inline void HostManager::Invoke( std::string _method, void* params, const char_t* delegateTypeName ) { // Convert to std::wstring diff --git a/Source/Host/hostmanager.h b/Source/Host/hostmanager.h index c55287cf..8f0fdfb7 100644 --- a/Source/Host/hostmanager.h +++ b/Source/Host/hostmanager.h @@ -16,6 +16,11 @@ using string_t = std::basic_string; typedef int( CORECLR_DELEGATE_CALLTYPE* run_fn )( UnmanagedArgs* args ); +struct CVarManagedCmdDispatchInfo; + +template +struct CVarManagedVarDispatchInfo; + namespace HostGlobals { // Globals to hold hostfxr exports @@ -52,4 +57,9 @@ class HostManager : ISubSystem void Render(); void DrawEditor(); void FireEvent( std::string eventName ); + + void DispatchCommand( CVarManagedCmdDispatchInfo info ); + void DispatchStringCVarCallback( CVarManagedVarDispatchInfo info ); + void DispatchFloatCVarCallback( CVarManagedVarDispatchInfo info ); + void DispatchBoolCVarCallback( CVarManagedVarDispatchInfo info ); }; diff --git a/Source/Hotload/Assembly/ProjectAssembly.cs b/Source/Hotload/Assembly/ProjectAssembly.cs index f9e98ace..377798ef 100644 --- a/Source/Hotload/Assembly/ProjectAssembly.cs +++ b/Source/Hotload/Assembly/ProjectAssembly.cs @@ -75,6 +75,8 @@ private void CompileIntoMemory() // Unregister events for old interface Event.Unregister( oldGameInterface ); + + ConsoleSystem.Internal.ClearGameCVars(); } // Now that everything's been upgraded, swap the new interface @@ -82,6 +84,9 @@ private void CompileIntoMemory() Swap( newAssembly, newInterface ); Notify.AddNotification( $"Build successful!", $"Compiled '{_projectAssemblyInfo.AssemblyName}'!", FontAwesome.FaceGrinStars ); + + ConsoleSystem.Internal.RegisterAssembly( newAssembly, extraFlags: CVarFlags.Game ); + Event.Run( Event.Game.HotloadAttribute.Name ); } diff --git a/Source/Hotload/Main.cs b/Source/Hotload/Main.cs index 8e8073b4..dbfa3972 100644 --- a/Source/Hotload/Main.cs +++ b/Source/Hotload/Main.cs @@ -1,5 +1,6 @@ global using static Mocha.Common.Global; using Mocha.Common; +using Mocha.Common.Console; using MochaTool.AssetCompiler; using System.Runtime.InteropServices; @@ -26,6 +27,12 @@ public static void Run( IntPtr args ) // Initialize the logger Log = new NativeLogger(); + // TODO: Is there a better way to register these cvars? + // Register cvars for assemblies that will never hotload + ConsoleSystem.Internal.RegisterAssembly( typeof( Mocha.Hotload.Main ).Assembly ); // Hotload + ConsoleSystem.Internal.RegisterAssembly( typeof( Mocha.Common.IGame ).Assembly ); // Common + ConsoleSystem.Internal.RegisterAssembly( typeof( Mocha.BaseGame ).Assembly ); // Engine + // Initialize upgrader, we do this as early as possible to prevent // slowdowns while the engine is running. Upgrader.Init(); @@ -122,4 +129,52 @@ public static void FireEvent( IntPtr ptrEventName ) Event.Run( eventName ); } + + [UnmanagedCallersOnly] + public static void DispatchCommand( IntPtr infoPtr ) + { + var info = Marshal.PtrToStructure( infoPtr ); + var name = Marshal.PtrToStringUTF8( info.name ); + + if ( name is null ) + return; + + var arguments = new List(); + + if ( info.size != 0 ) + { + // TODO: Remove this alloc + var stringPtrs = new IntPtr[info.size]; + Marshal.Copy( info.data, stringPtrs, 0, info.size ); + + arguments.Capacity = info.size; + + for ( int i = 0; i < info.size; i++ ) + { + arguments.Add( Marshal.PtrToStringUTF8( stringPtrs[i] ) ?? "" ); + } + } + + Log.Trace( $"Dispatching managed command '{name}'" ); + + ConsoleSystem.Internal.DispatchCommand( name, arguments ); + } + + [UnmanagedCallersOnly] + public static void DispatchStringCVarCallback( IntPtr infoPtr ) + { + var info = Marshal.PtrToStructure( infoPtr ); + } + + [UnmanagedCallersOnly] + public static void DispatchFloatCVarCallback( IntPtr infoPtr ) + { + var info = Marshal.PtrToStructure( infoPtr ); + } + + [UnmanagedCallersOnly] + public static void DispatchBoolCVarCallback( IntPtr infoPtr ) + { + var info = Marshal.PtrToStructure( infoPtr ); + } } From 2a9498939da0d715d9bd23fc88ba1b2f2b7d3a24 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 17:26:16 -0800 Subject: [PATCH 12/19] Compile fix --- Source/Host/cvarmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 514c7d8f..bddfbfa8 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -382,7 +382,7 @@ template inline void CVarSystem::RegisterVariable( std::string name, T value, CVarFlags flags, std::string description, CVarCallback callback ) { // This *must not* have the command flag - flags = flags & ~( CVarFlags::Command ); + flags = ( CVarFlags )( flags & ~( CVarFlags::Command ) ); CVarEntry entry = {}; entry.m_name = name; From 96a966e8728ceb5f1a52dfe3e16b38b10199d2d8 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 17:30:32 -0800 Subject: [PATCH 13/19] GetStatementArguments/GetStatements uniformity --- Source/Host/cvarmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index bddfbfa8..0bbe3959 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -297,8 +297,8 @@ std::vector CVarSystem::GetStatementArguments( std::string_view sta std::vector CVarSystem::GetStatements( const std::string& input ) { - size_t cursor = 0, cursorIndex; - return GetStatements( input, cursor, cursorIndex ); + size_t cursorIndex; + return GetStatements( input, 0, cursorIndex ); } #pragma endregion From 3d30764ab22760fe059853135af12e280b4236f0 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 22:52:50 -0800 Subject: [PATCH 14/19] Add TryConvert() and several ToX() extensions to StringExtensions --- Source/Common/Utils/Extensions.cs | 105 ++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/Source/Common/Utils/Extensions.cs b/Source/Common/Utils/Extensions.cs index 582b7d5f..fa2faef7 100644 --- a/Source/Common/Utils/Extensions.cs +++ b/Source/Common/Utils/Extensions.cs @@ -76,4 +76,109 @@ public static string DisplayName( this string str ) return result; } + public static bool TryConvert( this string str, Type t, out object? Value ) + { + Value = null; + + if ( t == typeof( string ) ) + { + Value = str; + return true; + } + + if ( t == typeof( float ) ) + { + Value = str.ToFloat(); + return true; + } + + if ( t == typeof( double ) ) + { + Value = str.ToDouble(); + return true; + } + + if ( t == typeof( int ) ) + { + Value = str.ToInt(); + return true; + } + + if ( t == typeof( uint ) ) + { + Value = str.ToUInt(); + return true; + } + + if ( t == typeof( long ) ) + { + Value = str.ToLong(); + return true; + } + + if ( t == typeof( ulong ) ) + { + Value = str.ToULong(); + return true; + } + + if ( t == typeof( bool ) ) + { + Value = str.ToBool(); + return true; + } + + return false; + } + + public static float ToFloat( this string str, float Default = default ) + { + return float.TryParse( str, out var result ) ? result : Default; + } + + public static double ToDouble( this string str, double Default = default ) + { + return double.TryParse( str, out var result ) ? result : Default; + } + + public static int ToInt( this string str, int Default = default ) + { + return int.TryParse( str, out var result ) ? result : Default; + } + + public static uint ToUInt( this string str, uint Default = default ) + { + return uint.TryParse( str, out var result ) ? result : Default; + } + + public static long ToLong( this string str, long Default = default ) + { + return long.TryParse( str, out var result ) ? result : Default; + } + + public static ulong ToULong( this string str, ulong Default = default ) + { + return ulong.TryParse( str, out var result ) ? result : Default; + } + + public static bool ToBool( this string str ) + { + switch ( str ) + { + case null: + case "": + case "0": + return false; + + default: + if ( str.Equals( "yes", StringComparison.OrdinalIgnoreCase ) || + str.Equals( "true", StringComparison.OrdinalIgnoreCase ) || + str.Equals( "on", StringComparison.OrdinalIgnoreCase ) ) + return true; + + // If it fails to parse as a number, it returns false + // If it succeeds and it's not 0, it returns true + return float.TryParse( str, out var result ) && result != 0.0f; + } + } } From 1b7a01ece332b3766550a85e55c1a20a654baa63 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Mon, 30 Jan 2023 22:54:46 -0800 Subject: [PATCH 15/19] Revamp dispatch, convert strings to callback parameter types on the fly --- .../Common/Console/ConsoleSystem.Internal.cs | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/Source/Common/Console/ConsoleSystem.Internal.cs b/Source/Common/Console/ConsoleSystem.Internal.cs index 5ca969da..e894f21d 100644 --- a/Source/Common/Console/ConsoleSystem.Internal.cs +++ b/Source/Common/Console/ConsoleSystem.Internal.cs @@ -11,18 +11,25 @@ public static partial class ConsoleSystem { public static class Internal { - internal static List s_items = new(); - internal static Dictionary>> s_commandCallbacks = new(); + internal struct ConCmdCallbackInfo + { + public required MethodInfo method; + public required ParameterInfo[] parameters; + } internal static class ConVarCallbackStore { public static Dictionary> Callbacks = new(); } - internal static void RegisterCommand( string name, CVarFlags flags, string description, Action> callback ) + internal static List s_items = new(); + + internal static Dictionary s_commandCallbacks = new(); + + internal static void RegisterCommand( string name, CVarFlags flags, string description, ConCmdCallbackInfo callbackInfo ) { Glue.ConsoleSystem.RegisterCommand( name, flags, description ); - s_commandCallbacks[name] = callback; + s_commandCallbacks[name] = callbackInfo; s_items.Add( name ); } @@ -64,8 +71,15 @@ public static void RegisterAssembly( Assembly assembly, CVarFlags extraFlags = C ConCmd.BaseAttribute? customAttribute = method.GetCustomAttribute(); if ( customAttribute is not null ) { - RegisterCommand( customAttribute.Name, customAttribute.Flags | extraFlags, customAttribute.Description, - ( List arguments ) => method.Invoke( null, new object[] { arguments } ) ); + var parameters = method.GetParameters(); + + var callbackInfo = new ConCmdCallbackInfo + { + method = method, + parameters = parameters + }; + + RegisterCommand( customAttribute.Name, customAttribute.Flags | extraFlags, customAttribute.Description, callbackInfo ); } } } @@ -91,20 +105,72 @@ public static void ClearGameCVars() } } - public static void DispatchCommand( string name, List arguments ) + public static void DispatchCommand( string name, List dispatchArguments ) { - if ( s_commandCallbacks.TryGetValue( name, out var callback ) ) + if ( !s_commandCallbacks.TryGetValue( name, out ConCmdCallbackInfo callbackInfo ) ) + return; + + ParameterInfo[] callbackParameters = callbackInfo.parameters; + + if ( callbackParameters.Length == 1 && + callbackParameters[0].ParameterType == dispatchArguments.GetType() ) + { + // All it takes is a List so we can probably assume they want direct access to the arguments + callbackInfo.method.Invoke( null, new object[] { dispatchArguments } ); + return; + } + + object?[]? arguments = null; + + if ( callbackParameters.Length > 0 ) { - callback( arguments ); + // + // Softly convert arguments from strings to whatever the callback expects + // + + arguments = new object[callbackParameters.Length]; + + for ( int i = 0; i < callbackParameters.Length; i++ ) + { + object? value; + + ParameterInfo parameter = callbackParameters[i]; + Type parameterType = parameter.ParameterType; + + if ( i < dispatchArguments.Count ) + { + // Try to convert + if ( !dispatchArguments[i].TryConvert( parameterType, out value ) ) + { + Log.Error( $"Error dispatching ConCmd '{name}': Couldn't convert '{dispatchArguments[i]}' to type {parameterType}" ); + return; + } + } + else if ( parameter.HasDefaultValue ) + { + // There weren't enough arguments passed in, + // but we have a default value, so use that instead + value = parameter.DefaultValue; + } + else + { + Log.Error( $"Error dispatching ConCmd '{name}': Not enough arguments - expected {callbackParameters.Length}, got {dispatchArguments.Count}" ); + return; + } + + arguments[i] = value; + } } + + callbackInfo.method.Invoke( null, arguments ); } public static void DispatchConVarCallback( string name, T oldValue, T newValue ) { - if ( ConVarCallbackStore.Callbacks.TryGetValue( name, out var callback ) ) - { - callback( oldValue, newValue ); - } + if ( !ConVarCallbackStore.Callbacks.TryGetValue( name, out var callback ) ) + return; + + callback( oldValue, newValue ); } } } From 7d714938431bf8e8388f9d764fe3c42b79a2784c Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Tue, 31 Jan 2023 00:00:28 -0800 Subject: [PATCH 16/19] Remove debug logfrom ConCmd dispatch --- Source/Hotload/Main.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Hotload/Main.cs b/Source/Hotload/Main.cs index 80512700..da8f068f 100644 --- a/Source/Hotload/Main.cs +++ b/Source/Hotload/Main.cs @@ -149,8 +149,6 @@ public static void DispatchCommand( IntPtr infoPtr ) } } - Log.Trace( $"Dispatching managed command '{name}'" ); - ConsoleSystem.Internal.DispatchCommand( name, arguments ); } From 75d7540826f6e8fbcaa85c003f62e1a7c74aed3c Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Tue, 31 Jan 2023 00:05:08 -0800 Subject: [PATCH 17/19] Hook up ConVar dispatches (can't test right now) --- Source/Hotload/Main.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Source/Hotload/Main.cs b/Source/Hotload/Main.cs index da8f068f..bdc33b13 100644 --- a/Source/Hotload/Main.cs +++ b/Source/Hotload/Main.cs @@ -128,7 +128,7 @@ public static void FireEvent( IntPtr ptrEventName ) public static void DispatchCommand( IntPtr infoPtr ) { var info = Marshal.PtrToStructure( infoPtr ); - var name = Marshal.PtrToStringUTF8( info.name ); + string? name = Marshal.PtrToStringUTF8( info.name ); if ( name is null ) return; @@ -156,18 +156,39 @@ public static void DispatchCommand( IntPtr infoPtr ) public static void DispatchStringCVarCallback( IntPtr infoPtr ) { var info = Marshal.PtrToStructure( infoPtr ); + string? name = Marshal.PtrToStringUTF8( info.name ); + + if ( name is null ) + return; + + string oldValue = Marshal.PtrToStringUTF8( info.oldValue ) ?? ""; + string newValue = Marshal.PtrToStringUTF8( info.newValue ) ?? ""; + + ConsoleSystem.Internal.DispatchConVarCallback( name, oldValue, newValue ); } [UnmanagedCallersOnly] public static void DispatchFloatCVarCallback( IntPtr infoPtr ) { var info = Marshal.PtrToStructure( infoPtr ); + string? name = Marshal.PtrToStringUTF8( info.name ); + + if ( name is null ) + return; + + ConsoleSystem.Internal.DispatchConVarCallback( name, info.oldValue, info.newValue ); } [UnmanagedCallersOnly] public static void DispatchBoolCVarCallback( IntPtr infoPtr ) { var info = Marshal.PtrToStructure( infoPtr ); + string? name = Marshal.PtrToStringUTF8( info.name ); + + if ( name is null ) + return; + + ConsoleSystem.Internal.DispatchConVarCallback( name, info.oldValue, info.newValue ); } /// From 5e5ed0e58ecfe4310367e4ddaaef39216d034750 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Tue, 31 Jan 2023 00:59:56 -0800 Subject: [PATCH 18/19] Make CVarSystem case insensitive by lowering strings when getting hashes --- Source/Host/cvarmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Host/cvarmanager.cpp b/Source/Host/cvarmanager.cpp index 0bbe3959..50b5b100 100644 --- a/Source/Host/cvarmanager.cpp +++ b/Source/Host/cvarmanager.cpp @@ -5,6 +5,8 @@ size_t CVarSystem::GetHash( std::string string ) { + std::transform( string.begin(), string.end(), string.begin(), + []( unsigned char c ) { return std::tolower( c ); } ); return std::hash{}( string ); } From 3b363663669328c3c5db6ca33b8a723f6b1f3439 Mon Sep 17 00:00:00 2001 From: MuffinTastic Date: Tue, 31 Jan 2023 01:00:41 -0800 Subject: [PATCH 19/19] Rely more heavily on ConCmds variants rather than setting flags directly --- Source/Common/Console/ConCmd.cs | 35 ++++++++++++++++--- .../Common/Console/ConsoleSystem.Internal.cs | 2 +- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Source/Common/Console/ConCmd.cs b/Source/Common/Console/ConCmd.cs index 7b2614d1..56be51f5 100644 --- a/Source/Common/Console/ConCmd.cs +++ b/Source/Common/Console/ConCmd.cs @@ -4,11 +4,11 @@ public static class ConCmd { public abstract class BaseAttribute : Attribute { - public string Name { get; init; } - public CVarFlags Flags { get; init; } - public string Description { get; init; } + internal string? Name { get; init; } + internal CVarFlags Flags { get; init; } + internal string? Description { get; init; } - public BaseAttribute( string name, CVarFlags flags, string description ) + public BaseAttribute( string? name, CVarFlags flags, string? description ) { Name = name; Flags = flags; @@ -21,8 +21,13 @@ public BaseAttribute( string name, CVarFlags flags, string description ) public sealed class TestAttribute : BaseAttribute { + public TestAttribute() + : base( null, CVarFlags.None, null ) + { + + } public TestAttribute( string name ) - : base( name, CVarFlags.None, "" ) + : base( name, CVarFlags.None, null ) { } @@ -33,4 +38,24 @@ public TestAttribute( string name, string description ) } } + + public sealed class CheatAttribute : BaseAttribute + { + public CheatAttribute() + : base( null, CVarFlags.Cheat, null ) + { + + } + public CheatAttribute( string name ) + : base( name, CVarFlags.Cheat, null ) + { + + } + + public CheatAttribute( string name, string description ) + : base( name, CVarFlags.Cheat, description ) + { + + } + } } diff --git a/Source/Common/Console/ConsoleSystem.Internal.cs b/Source/Common/Console/ConsoleSystem.Internal.cs index e894f21d..cd3805c2 100644 --- a/Source/Common/Console/ConsoleSystem.Internal.cs +++ b/Source/Common/Console/ConsoleSystem.Internal.cs @@ -79,7 +79,7 @@ public static void RegisterAssembly( Assembly assembly, CVarFlags extraFlags = C parameters = parameters }; - RegisterCommand( customAttribute.Name, customAttribute.Flags | extraFlags, customAttribute.Description, callbackInfo ); + RegisterCommand( customAttribute.Name ?? method.Name, customAttribute.Flags | extraFlags, customAttribute.Description ?? "", callbackInfo ); } } }