357 lines
13 KiB
C++
357 lines
13 KiB
C++
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
|
|
|
#ifndef CSYS_SYSTEM_H
|
|
#define CSYS_SYSTEM_H
|
|
|
|
#pragma once
|
|
|
|
#include "csys/command.h"
|
|
#include "csys/autocomplete.h"
|
|
#include "csys/history.h"
|
|
#include "csys/item.h"
|
|
#include "csys/script.h"
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <string>
|
|
|
|
namespace csys
|
|
{
|
|
class CSYS_API System
|
|
{
|
|
public:
|
|
|
|
/*!
|
|
* \brief Initialize system object
|
|
*/
|
|
System();
|
|
|
|
/*!
|
|
* \brief
|
|
* Move constructor
|
|
* \param rhs
|
|
* System to be copied.
|
|
*/
|
|
System(System &&rhs) = default;
|
|
|
|
/*!
|
|
* \brief
|
|
* Copy constructor
|
|
* \param rhs
|
|
* System to be copied.
|
|
*/
|
|
System(const System &rhs);
|
|
|
|
/*!
|
|
* \brief
|
|
* Move assignment operator
|
|
* \param rhs
|
|
* System to be copied.
|
|
*/
|
|
System &operator=(System &&rhs) = default;
|
|
|
|
/*!
|
|
* \brief
|
|
* Copy assigment operator.
|
|
* \param rhs
|
|
* System to be copied.
|
|
*/
|
|
System &operator=(const System &rhs);
|
|
|
|
/*!
|
|
* \brief
|
|
* Parse given command line input and run it
|
|
* \param line
|
|
* Command line string
|
|
*/
|
|
void RunCommand(const std::string &line);
|
|
|
|
/*!
|
|
* \brief
|
|
* Get console registered command autocomplete tree
|
|
* \return
|
|
* Autocomplete Ternary Search Tree
|
|
*/
|
|
AutoComplete &CmdAutocomplete();
|
|
|
|
/*!
|
|
* \brief
|
|
* Get console registered variables autocomplete tree
|
|
* \return
|
|
* Autocomplete Ternary Search Tree
|
|
*/
|
|
AutoComplete &VarAutocomplete();
|
|
|
|
/*!
|
|
* \brief
|
|
* Get command history container
|
|
* \return
|
|
* Command history vector
|
|
*/
|
|
CommandHistory &History();
|
|
|
|
/*!
|
|
* \brief
|
|
* Get console items
|
|
* \return
|
|
* Console items container
|
|
*/
|
|
std::vector<Item> &Items();
|
|
|
|
/*!
|
|
* \brief
|
|
* Creates a new item entry to log information
|
|
* \param type
|
|
* Log type (COMMAND, LOG, WARNING, ERROR)
|
|
* \return
|
|
* Reference to console items obj
|
|
*/
|
|
ItemLog &Log(ItemType type = ItemType::LOG);
|
|
|
|
/*!
|
|
* \brief
|
|
* Run the given script
|
|
* \param script_name
|
|
* Script to be executed
|
|
*
|
|
* \note
|
|
* If script exists but its not loaded, this methods will load the script and proceed to run it.
|
|
*/
|
|
void RunScript(const std::string &script_name);
|
|
|
|
/*!
|
|
* \brief
|
|
* Get registered command container
|
|
* \return
|
|
* Commands container
|
|
*/
|
|
std::unordered_map<std::string, std::unique_ptr<CommandBase>> &Commands();
|
|
|
|
/*!
|
|
* \brief
|
|
* Get registered scripts container
|
|
* \return
|
|
* Scripts container
|
|
*/
|
|
std::unordered_map<std::string, std::unique_ptr<Script>> &Scripts();
|
|
|
|
/*!
|
|
* \brief
|
|
* Registers a command within the system to be invokable
|
|
* \tparam Fn
|
|
* Decltype of the function to invoke when command is ran
|
|
* \tparam Args
|
|
* List of arguments that match that of the argument list within the function Fn of type csys::Arg<T>
|
|
* \param name
|
|
* Non-whitespace separating name of the command. Whitespace will be dropped
|
|
* \param description
|
|
* Description describing what the command does
|
|
* \param function
|
|
* A non-member function to run when command is called
|
|
* \param args
|
|
* List of csys::Arg<T>s that matches that of the argument list of 'function'
|
|
*/
|
|
template<typename Fn, typename ...Args>
|
|
void RegisterCommand(const String &name, const String &description, Fn function, Args... args)
|
|
{
|
|
// Check if function can be called with the given arguments and is not part of a class
|
|
static_assert(std::is_invocable_v<Fn, typename Args::ValueType...>, "Arguments specified do not match that of the function");
|
|
static_assert(!std::is_member_function_pointer_v<Fn>, "Non-static member functions are not allowed");
|
|
|
|
// Move to command
|
|
size_t name_index = 0;
|
|
auto range = name.NextPoi(name_index);
|
|
|
|
// Command already registered
|
|
if (m_Commands.find(name.m_String) != m_Commands.end())
|
|
throw csys::Exception("ERROR: Command already exists");
|
|
|
|
// Check if command has a name
|
|
else if (range.first == name.End())
|
|
{
|
|
Log(ERROR) << "Empty command name given" << csys::endl;
|
|
return;
|
|
}
|
|
|
|
// Get command name
|
|
std::string command_name = name.m_String.substr(range.first, range.second - range.first);
|
|
|
|
// Command contains more than one word
|
|
if (name.NextPoi(name_index).first != name.End())
|
|
throw csys::Exception("ERROR: Whitespace separated command names are forbidden");
|
|
|
|
// Register for autocomplete.
|
|
if (m_RegisterCommandSuggestion)
|
|
{
|
|
m_CommandSuggestionTree.Insert(command_name);
|
|
m_VariableSuggestionTree.Insert(command_name);
|
|
}
|
|
|
|
// Add commands to system
|
|
m_Commands[name.m_String] = std::make_unique<Command<Fn, Args...>>(name, description, function, args...);
|
|
|
|
// Make help command for command just added
|
|
auto help = [this, command_name]() {
|
|
Log(LOG) << m_Commands[command_name]->Help() << csys::endl;
|
|
};
|
|
|
|
m_Commands["help " + command_name] = std::make_unique<Command<decltype(help)>>("help " + command_name,
|
|
"Displays help info about command " +
|
|
command_name, help);
|
|
}
|
|
|
|
/*!
|
|
* \brief
|
|
* Register's a variable within the system
|
|
* \tparam T
|
|
* Type of the variable
|
|
* \tparam Types
|
|
* Type of arguments that type T can be constructed with
|
|
* \param name
|
|
* Name of the variable
|
|
* \param var
|
|
* The variable to register
|
|
* \param args
|
|
* List of csys::Arg to be used for the construction of type T
|
|
* \note
|
|
* Type T requires an assignment operator, and constructor that takes type 'Types...'
|
|
* Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends
|
|
*/
|
|
template<typename T, typename ...Types>
|
|
void RegisterVariable(const String &name, T &var, Arg<Types>... args)
|
|
{
|
|
static_assert(std::is_constructible_v<T, Types...>, "Type of var 'T' can not be constructed with types of 'Types'");
|
|
static_assert(sizeof... (Types) != 0, "Empty variadic list");
|
|
|
|
// Register get command
|
|
auto var_name = RegisterVariableAux(name, var);
|
|
|
|
// Register set command
|
|
auto setter = [&var](Types... params){ var = T(params...); };
|
|
m_Commands["set " + var_name] = std::make_unique<Command<decltype(setter), Arg<Types>...>>("set " + var_name,
|
|
"Sets the variable " + var_name,
|
|
setter, args...);
|
|
}
|
|
|
|
/*!
|
|
* \brief
|
|
* Register's a variable within the system
|
|
* \tparam T
|
|
* Type of the variable
|
|
* \tparam Types
|
|
* Type of arguments that type T can be constructed with
|
|
* \param name
|
|
* Name of the variable
|
|
* \param var
|
|
* The variable to register
|
|
* \param setter
|
|
* Custom setter that runs when command set 'name' is invoked
|
|
* \note
|
|
* The setter must have dceltype of void(decltype(var)&, Types...)
|
|
* Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends
|
|
*/
|
|
template<typename T, typename ...Types>
|
|
void RegisterVariable(const String &name, T &var, void(*setter)(T&, Types...))
|
|
{
|
|
// Register get command
|
|
auto var_name = RegisterVariableAux(name, var);
|
|
|
|
// Register set command
|
|
auto setter_l = [&var, setter](Types... args){ setter(var, args...); };
|
|
m_Commands["set " + var_name] = std::make_unique<Command<decltype(setter_l), Arg<Types>...>>("set " + var_name,
|
|
"Sets the variable " + var_name,
|
|
setter_l, Arg<Types>("")...);
|
|
}
|
|
|
|
/*!
|
|
* \brief
|
|
* Register script into console system
|
|
* \param name
|
|
* Script name
|
|
* \param path
|
|
* Scrip path
|
|
*/
|
|
void RegisterScript(const std::string &name, const std::string &path);
|
|
|
|
/*!
|
|
* \brief
|
|
* Unregister command from console system
|
|
* \param cmd_name
|
|
* Command to unregister
|
|
*/
|
|
void UnregisterCommand(const std::string &cmd_name);
|
|
|
|
/*!
|
|
* \brief
|
|
* Unregister variable from console system
|
|
* \param var_name
|
|
* Variable to unregister
|
|
*/
|
|
void UnregisterVariable(const std::string &var_name);
|
|
|
|
/*!
|
|
* \brief
|
|
* Unregister script from console system
|
|
* \param script_name
|
|
* Script to unregister
|
|
*/
|
|
void UnregisterScript(const std::string &script_name);
|
|
|
|
protected:
|
|
template<typename T>
|
|
std::string RegisterVariableAux(const String &name, T &var)
|
|
{
|
|
// Disable.
|
|
m_RegisterCommandSuggestion = false;
|
|
|
|
// Make sure only one word was passed in
|
|
size_t name_index = 0;
|
|
auto range = name.NextPoi(name_index);
|
|
if (name.NextPoi(name_index).first != name.End())
|
|
throw csys::Exception("ERROR: Whitespace separated variable names are forbidden");
|
|
|
|
// Get variable name
|
|
std::string var_name = name.m_String.substr(range.first, range.second - range.first);
|
|
|
|
// Get Command
|
|
const auto GetFunction = [this, &var]() {
|
|
m_ItemLog.log(LOG) << var << endl;
|
|
};
|
|
|
|
// Register get command
|
|
m_Commands["get " + var_name] = std::make_unique<Command<decltype(GetFunction)>>("get " + var_name,
|
|
"Gets the variable " +
|
|
var_name, GetFunction);
|
|
|
|
// Enable again.
|
|
m_RegisterCommandSuggestion = true;
|
|
|
|
// Register variable
|
|
m_VariableSuggestionTree.Insert(var_name);
|
|
|
|
return var_name;
|
|
}
|
|
|
|
void ParseCommandLine(const String &line); //!< Parse command line and execute command
|
|
|
|
std::unordered_map<std::string, std::unique_ptr<CommandBase>> m_Commands; //!< Registered command container
|
|
AutoComplete m_CommandSuggestionTree; //!< Autocomplete Ternary Search Tree for commands
|
|
AutoComplete m_VariableSuggestionTree; //!< Autocomplete Ternary Search Tree for registered variables
|
|
CommandHistory m_CommandHistory; //!< History of executed commands
|
|
ItemLog m_ItemLog; //!< Console Items (Logging)
|
|
std::unordered_map<std::string, std::unique_ptr<Script>> m_Scripts; //!< Scripts
|
|
bool m_RegisterCommandSuggestion = true; //!< Flag that determines if commands will be registered for autocomplete.
|
|
};
|
|
}
|
|
|
|
#ifdef CSYS_HEADER_ONLY
|
|
#include "csys/system.inl"
|
|
#endif
|
|
|
|
#endif
|
|
|
|
// void (T&, int, float...);
|
|
|
|
// registaerVariable("name", var, Arg<int>...); //c
|
|
// registaerVariable("name", var, setter); //d
|