Commit eb4c79d8 authored by NTAuthority's avatar NTAuthority

console variables, modeled after CoD in behavior

parent 0e9818a7
Pipeline #46 skipped
......@@ -79,9 +79,31 @@ class ConsoleCommandManager
static ConsoleCommandManager* GetDefaultInstance();
};
template <typename TArgument, typename TConstraint = void>
struct ConsoleArgumentTraits
{
using Less = std::less<TArgument>;
using Greater = std::greater<TArgument>;
using Equal = std::equal_to<TArgument>;
};
template <typename TArgument, typename TConstraint = void>
struct ConsoleArgumentName
{
inline static const char* Get()
{
return typeid(TArgument).name();
}
};
template <typename TArgument, typename TConstraint = void>
struct ConsoleArgumentType
{
static std::string Unparse(const TArgument& argument)
{
static_assert(false, "Unknown ConsoleArgumentType unparse handler (try defining one?)");
}
static bool Parse(const std::string& input, TArgument* out)
{
static_assert(false, "Unknown ConsoleArgumentType parse handler (try defining one?)");
......@@ -91,6 +113,11 @@ struct ConsoleArgumentType
template <>
struct ConsoleArgumentType<std::string>
{
static std::string Unparse(const std::string& input)
{
return input;
}
static bool Parse(const std::string& input, std::string* out)
{
*out = input;
......@@ -98,9 +125,23 @@ struct ConsoleArgumentType<std::string>
}
};
template <>
struct ConsoleArgumentName<std::string>
{
inline static const char* Get()
{
return "string";
}
};
template <typename TArgument>
struct ConsoleArgumentType<TArgument, std::enable_if_t<std::is_integral<TArgument>::value>>
{
static std::string Unparse(const TArgument& input)
{
return std::to_string(input);
}
static bool Parse(const std::string& input, TArgument* out)
{
try
......@@ -120,6 +161,11 @@ struct ConsoleArgumentType<TArgument, std::enable_if_t<std::is_integral<TArgumen
template <typename TArgument>
struct ConsoleArgumentType<TArgument, std::enable_if_t<std::is_floating_point<TArgument>::value>>
{
static std::string Unparse(const TArgument& input)
{
return std::to_string(input);
}
static bool Parse(const std::string& input, TArgument* out)
{
try
......@@ -143,6 +189,12 @@ bool ParseArgument(const std::string& input, TArgument* out)
return ConsoleArgumentType<TArgument>::Parse(input, out);
}
template <typename TArgument>
std::string UnparseArgument(const TArgument& input)
{
return ConsoleArgumentType<TArgument>::Unparse(input);
}
template <class TFunc>
struct ConsoleCommandFunction
{
......
#pragma once
#include <Console.h>
#include <Console.Variables.h>
namespace krt
{
template <typename TVariable>
class ConVar
{
private:
int m_token;
ConsoleVariableManager* m_manager;
std::shared_ptr<internal::ConsoleVariableEntry<TVariable>> m_helper;
public:
ConVar(const std::string& name, int flags, const TVariable& defaultValue)
: ConVar(ConsoleVariableManager::GetDefaultInstance(), name, flags, defaultValue, nullptr)
{
}
ConVar(console::Context* context, const std::string& name, int flags, const TVariable& defaultValue)
: ConVar(context->GetVariableManager(), name, flags, defaultValue, nullptr)
{
}
ConVar(ConsoleVariableManager* manager, const std::string& name, int flags, const TVariable& defaultValue)
: ConVar(manager, name, flags, defaultValue, nullptr)
{
}
ConVar(const std::string& name, int flags, const TVariable& defaultValue, TVariable* trackingVar)
: ConVar(ConsoleVariableManager::GetDefaultInstance(), name, flags, defaultValue, trackingVar)
{
}
ConVar(console::Context* context, const std::string& name, int flags, const TVariable& defaultValue, TVariable* trackingVar)
: ConVar(context->GetVariableManager(), name, flags, defaultValue, trackingVar)
{
}
ConVar(ConsoleVariableManager* manager, const std::string& name, int flags, const TVariable& defaultValue, TVariable* trackingVar)
: m_manager(manager)
{
m_helper = CreateVariableEntry<TVariable>(manager, name, defaultValue);
m_token = m_manager->Register(name, flags, m_helper);
if (trackingVar)
{
m_helper->SetTrackingVar(trackingVar);
}
}
~ConVar()
{
if (m_token != -1)
{
m_manager->Unregister(m_token);
m_token = -1;
}
}
inline TVariable GetValue()
{
return m_helper->GetRawValue();
}
inline std::shared_ptr<internal::ConsoleVariableEntry<TVariable>> GetHelper()
{
return m_helper;
}
};
}
\ No newline at end of file
#pragma once
#include <Console.Commands.h>
#include <Console.CommandHelpers.h>
namespace krt
{
namespace internal
{
class ConsoleVariableEntryBase
{
public:
virtual std::string GetValue() = 0;
virtual bool SetValue(const std::string& value) = 0;
};
template<typename T, typename TConstraint = void>
struct Constraints
{
inline static bool Compare(const T& value, const T& minValue, const T& maxValue)
{
return true;
}
};
template<typename T>
struct Constraints<T, std::enable_if_t<std::is_arithmetic<T>::value>>
{
inline static bool Compare(const T& value, const T& minValue, const T& maxValue)
{
if (ConsoleArgumentTraits<T>::Greater()(value, maxValue))
{
printf("Value out of range (%s) - should be at most %s\n", UnparseArgument(value).c_str(), UnparseArgument(maxValue).c_str());
return false;
}
if (ConsoleArgumentTraits<T>::Less()(value, minValue))
{
printf("Value out of range (%s) - should be at least %s\n", UnparseArgument(value).c_str(), UnparseArgument(minValue).c_str());
return false;
}
return true;
}
};
template<typename T>
class ConsoleVariableEntry : public ConsoleVariableEntryBase
{
public:
ConsoleVariableEntry(ConsoleVariableManager* manager, const std::string& name, const T& defaultValue)
: m_manager(manager), m_name(name), m_trackingVar(nullptr), m_defaultValue(defaultValue), m_curValue(defaultValue), m_hasConstraints(false)
{
m_getCommand = std::make_unique<ConsoleCommand>(manager->GetParentContext(), name, [=] ()
{
printf(" \"%s\" is \"%s\"\n default: \"%s\"\n type: %s\n", name.c_str(), GetValue().c_str(), UnparseArgument(m_defaultValue).c_str(), ConsoleArgumentName<T>::Get());
});
m_setCommand = std::make_unique<ConsoleCommand>(manager->GetParentContext(), name, [=] (const T& newValue)
{
SetRawValue(newValue);
});
}
inline void SetConstraints(const T& minValue, const T& maxValue)
{
m_minValue = minValue;
m_maxValue = maxValue;
m_hasConstraints = true;
}
inline void SetTrackingVar(T* variable)
{
m_trackingVar = variable;
if (variable)
{
*variable = m_curValue;
}
}
virtual std::string GetValue() override
{
// update from a tracking variable
if (m_trackingVar)
{
if (!ConsoleArgumentTraits<T>::Equal()(*m_trackingVar, m_curValue))
{
m_curValue = *m_trackingVar;
}
}
return UnparseArgument(m_curValue);
}
virtual bool SetValue(const std::string& value) override
{
T newValue;
if (ParseArgument(value, &newValue))
{
return SetRawValue(newValue);
}
return false;
}
inline const T& GetRawValue()
{
return m_curValue;
}
inline bool SetRawValue(const T& newValue)
{
if (m_hasConstraints && !Constraints<T>::Compare(newValue, m_minValue, m_maxValue))
{
return false;
}
m_curValue = newValue;
if (m_trackingVar)
{
*m_trackingVar = m_curValue;
}
return true;
}
private:
std::string m_name;
T m_curValue;
T m_minValue;
T m_maxValue;
T m_defaultValue;
T* m_trackingVar;
bool m_hasConstraints;
std::unique_ptr<ConsoleCommand> m_getCommand;
std::unique_ptr<ConsoleCommand> m_setCommand;
ConsoleVariableManager* m_manager;
};
}
enum ConsoleVariableFlags
{
ConVar_None = 0,
ConVar_Archive = 0x1
};
class ConsoleVariableManager : public ConsoleVariableManagerProxy
{
public:
using THandlerPtr = std::shared_ptr<internal::ConsoleVariableEntryBase>;
public:
ConsoleVariableManager(console::Context* context);
~ConsoleVariableManager();
int Register(const std::string& name, int flags, const THandlerPtr& variable);
void Unregister(int token);
bool Process(const std::string& commandName, const ProgramArguments& arguments);
THandlerPtr FindEntryRaw(const std::string& name);
inline console::Context* GetParentContext()
{
return m_parentContext;
}
private:
struct Entry
{
std::string name;
int flags;
THandlerPtr variable;
int token;
inline Entry(const std::string& name, int flags, const THandlerPtr& variable, int token)
: name(name), flags(flags), variable(variable), token(token)
{
}
};
private:
console::Context* m_parentContext;
std::map<std::string, Entry, IgnoreCaseLess> m_entries;
std::shared_timed_mutex m_mutex;
std::atomic<int> m_curToken;
std::unique_ptr<ConsoleCommand> m_setCommand;
std::unique_ptr<ConsoleCommand> m_setaCommand;
std::unique_ptr<ConsoleCommand> m_toggleCommand;
std::unique_ptr<ConsoleCommand> m_vstrCommand;
public:
static ConsoleVariableManager* GetDefaultInstance();
};
template<typename TValue>
static std::shared_ptr<internal::ConsoleVariableEntry<TValue>> CreateVariableEntry(ConsoleVariableManager* manager, const std::string& name, const TValue& defaultValue)
{
ConsoleVariableManager::THandlerPtr oldEntry = manager->FindEntryRaw(name);
// try to return/cast an old entry
if (oldEntry)
{
// if this is already an entry of the right type, return said entry
auto oldType = dynamic_cast<internal::ConsoleVariableEntry<TValue>*>(oldEntry.get());
if (oldType != nullptr)
{
return std::shared_ptr<internal::ConsoleVariableEntry<TValue>>(oldType);
}
// not the same type, create a new entry we'll hopefully treat better
std::string oldValue = oldEntry->GetValue();
auto newEntry = std::make_shared<internal::ConsoleVariableEntry<TValue>>(manager, name, defaultValue);
newEntry->SetValue(oldValue);
return newEntry;
}
else
{
// no old entry exists, just create a default
auto newEntry = std::make_shared<internal::ConsoleVariableEntry<TValue>>(manager, name, defaultValue);
return newEntry;
}
}
}
\ No newline at end of file
......@@ -5,6 +5,14 @@
namespace krt
{
class ConsoleVariableManagerProxy
{
public:
virtual ~ConsoleVariableManagerProxy() = default;
};
class ConsoleVariableManager;
namespace console
{
class Context
......@@ -27,6 +35,11 @@ public:
return m_commandManager.get();
}
inline ConsoleVariableManager* GetVariableManager()
{
return (ConsoleVariableManager*)m_variableManager.get();
}
inline Context* GetFallbackContext()
{
return m_fallbackContext;
......@@ -37,6 +50,8 @@ private:
std::unique_ptr<ConsoleCommandManager> m_commandManager;
std::unique_ptr<ConsoleVariableManagerProxy> m_variableManager;
std::string m_commandBuffer;
std::mutex m_commandBufferMutex;
......
......@@ -52,37 +52,47 @@ void ConsoleCommandManager::Invoke(const std::string& commandString)
void ConsoleCommandManager::Invoke(const std::string& commandName, const ProgramArguments& arguments)
{
// lock the mutex
auto lock = shared_lock_acquire<std::shared_timed_mutex>(m_mutex);
std::vector<THandler> functionAttempts;
// acquire a list of command entries
auto entryPair = m_entries.equal_range(commandName);
if (entryPair.first == entryPair.second)
{
// try in the fallback context first
console::Context* fallbackContext = m_parentContext->GetFallbackContext();
// lock the mutex
auto lock = shared_lock_acquire<std::shared_timed_mutex>(m_mutex);
// acquire a list of command entries
auto entryPair = m_entries.equal_range(commandName);
if (fallbackContext)
if (entryPair.first == entryPair.second)
{
return fallbackContext->GetCommandManager()->Invoke(commandName, arguments);
// try in the fallback context first
console::Context* fallbackContext = m_parentContext->GetFallbackContext();
if (fallbackContext)
{
return fallbackContext->GetCommandManager()->Invoke(commandName, arguments);
}
// TODO: replace with console stream output
printf("No such command %s.\n", commandName.c_str());
return;
}
// TODO: replace with console stream output
printf("No such command %s.\n", commandName.c_str());
return;
// NOTE: to prevent recursive locking, we store the functions in a list first!
for (std::pair<const std::string, Entry>& entry : GetIteratorView(entryPair))
{
functionAttempts.push_back(entry.second.function);
}
}
// try executing overloads until finding one that accepts our arguments - if none is found, print the error buffer
ConsoleExecutionContext context(std::move(arguments));
bool result = false;
for (std::pair<const std::string, Entry>& entry : GetIteratorView(entryPair))
// clear the error buffer
for (auto& function : functionAttempts)
{
// clear the error buffer
context.errorBuffer.str("");
result = entry.second.function(context);
result = function(context);
if (result)
{
......
#include <StdInc.h>
#include <Console.h>
#include <Console.Variables.h>
namespace krt
{
ConsoleVariableManager::ConsoleVariableManager(console::Context* parentContext)
: m_parentContext(parentContext), m_curToken(0)
{
auto setCommand = [=] (bool archive, const std::string& variable, const std::string& value)
{
// weird order is to prevent recursive locking
{
auto lock = shared_lock_acquire<std::shared_timed_mutex>(m_mutex);
auto oldVariable = m_entries.find(variable);
if (oldVariable != m_entries.end())
{
oldVariable->second.variable->SetValue(value);
if (archive)
{
oldVariable->second.flags |= ConVar_Archive;
}
return;
}
}
int flags = ConVar_None;
if (archive)
{
flags |= ConVar_Archive;
}
auto entry = CreateVariableEntry<std::string>(this, variable, "");
entry->SetValue(value);
Register(variable, flags, entry);
};
m_setCommand = std::make_unique<ConsoleCommand>(m_parentContext, "set", [=] (const std::string& variable, const std::string& value)
{
setCommand(false, variable, value);
});
// set archived
m_setaCommand = std::make_unique<ConsoleCommand>(m_parentContext, "seta", [=] (const std::string& variable, const std::string& value)
{
setCommand(true, variable, value);
});
}
ConsoleVariableManager::~ConsoleVariableManager()
{
}
ConsoleVariableManager::THandlerPtr ConsoleVariableManager::FindEntryRaw(const std::string& name)
{
auto variable = m_entries.find(name);
if (variable != m_entries.end())
{
return variable->second.variable;
}
return nullptr;
}
int ConsoleVariableManager::Register(const std::string& name, int flags, const THandlerPtr& variable)
{
auto lock = exclusive_lock_acquire<std::shared_timed_mutex>(m_mutex);
int token = m_curToken.fetch_add(1);
m_entries.erase(name); // remove any existing entry
m_entries.insert({ name, Entry{name, flags, variable, token} });
return token;
}
void ConsoleVariableManager::Unregister(int token)
{
auto lock = exclusive_lock_acquire<std::shared_timed_mutex>(m_mutex);
// look through the list for a matching token
for (auto it = m_entries.begin(); it != m_entries.end(); it++)
{
if (it->second.token == token)
{
// erase and return immediately (so we won't increment a bad iterator)
m_entries.erase(it);
return;
}
}
}
bool ConsoleVariableManager::Process(const std::string& commandName, const ProgramArguments& arguments)
{
// we currently don't process any variables specifically
return false;
}
ConsoleVariableManager* ConsoleVariableManager::GetDefaultInstance()
{
return console::GetDefaultContext()->GetVariableManager();
}
}
\ No newline at end of file
#include <StdInc.h>
#include <Console.h>
#include <Console.Commands.h>
#include <Console.Variables.h>
#include <sstream>
......@@ -18,6 +19,7 @@ Context::Context(Context* fallbackContext)
: m_fallbackContext(fallbackContext)
{
m_commandManager = std::make_unique<ConsoleCommandManager>(this);
m_variableManager = std::make_unique<ConsoleVariableManager>(this);
}
void Context::ExecuteSingleCommand(const std::string& command)
......
......@@ -14,6 +14,8 @@
#include "Console.Commands.h"
#include "GameUniverse.h"
#include "Console.VariableHelpers.h"
namespace krt
{
......@@ -61,6 +63,15 @@ class Game
NestedList<Entity> activeEntities;
std::vector<GameUniversePtr> universes;
int maxFPS;
float timescale;
private:
std::unique_ptr<ConVar<int>> maxFPSVariable;
std::unique_ptr<ConVar<float>> timescaleVariable;
};
extern Game* theGame;
......
......@@ -28,6 +28,10 @@ Game::Game(void) : streaming(GAME_NUM_STREAMING_CHANNELS), texManager(streaming)
{
assert(theGame == NULL);
// Initialize console variables.
maxFPSVariable = std::make_unique<ConVar<int>>("maxFPS", ConVar_Archive, 60, &maxFPS);
timescaleVariable = std::make_unique<ConVar<float>>("timescale", ConVar_None, 1.0f, &timescale);
// We can only have one game :)
theGame = this;
......@@ -109,7 +113,7 @@ void Game::Run()
{
// limit frame rate and handle events
// TODO: non-busy wait?
int minMillis = (1000 / 60); // 60 is to be replaced by a 'max fps' console value
int minMillis = (1000 / this->maxFPS);
uint32_t millis = 0;
uint64_t thisTime = 0;
......@@ -122,7 +126,7 @@ void Game::Run()
} while (millis < minMillis);
// handle time scaling
float scale = 1.0f; // to be replaced by a console value, again
float scale = this->timescale; // to be replaced by a console value, again
millis *= scale;
if (millis < 1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment