diff --git a/README.md b/README.md index 771e8b6..bde68ad 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,19 @@ ###### Check out the raymarching branch (pléascach-dronuilleog)! Small Vulkan 3D renderer. -![really cool wireframe tessellated terrain)](capture.png "Terrain Capture") +![really cool wireframe tessellated terrain)](terrain_capture.png "Terrain Capture") +![Quake 3 style map (unlit)](bsp_capture.png "BSP Capture") + +#### List of 3rd party code included in this repository: +- Imgui (and Imgui-console): for debugging UI +- STB: for image loading +- GLM: for math +- GLFW: for cross-platform windowing +- TinyglTF: for glTF parsing + ## Features - glTF Model loading +- QuakeIII BSP Map Loading - Tessellation-controlled heightmap terrains with dynamic normal calculation - Working lighting! diff --git a/Renderer/Renderer.cpp b/Renderer/Renderer.cpp index 2bf7c5f..88a51d2 100644 --- a/Renderer/Renderer.cpp +++ b/Renderer/Renderer.cpp @@ -383,11 +383,6 @@ void Renderer::draw() { command_buffer->command_buffer.setViewport(0, viewport); command_buffer->command_buffer.setScissor(0, scissor); - - /*if (line_mode) - command_buffer->command_buffer.setPolygonModeEXT(vk::PolygonMode::eLine); - else - command_buffer->command_buffer.setPolygonModeEXT(vk::PolygonMode::eFill);*/ /*command_buffer->bind(*terrain_pipeline); @@ -400,7 +395,7 @@ void Renderer::draw() { command_buffer->bind(models[0]); command_buffer->command_buffer.drawIndexed(models[0]->indices.size(), 10, 0, 0, 0);*/ - bsp->load_indices(cam.pos, visibility_testing); + bsp->load_indices(cam.pos, visibility_testing, p*uni.view); command_buffer->bind(bsp.get()); command_buffer->command_buffer.drawIndexed(bsp->indices.size(), 1, 0, 0, 0); @@ -455,7 +450,7 @@ void Renderer::present() { } frame++; - time += 0.0167f * speed * static_cast(running); + time += 0.0167f * speed * static_cast(!paused); } Renderer::~Renderer() { diff --git a/Renderer/Renderer.hpp b/Renderer/Renderer.hpp index dc319c4..3cf13ce 100644 --- a/Renderer/Renderer.hpp +++ b/Renderer/Renderer.hpp @@ -75,7 +75,7 @@ struct Renderer { /* time speed */ float time = 0.0; float speed = 1.0; - bool running = true; + bool paused = false; size_t n_indices; bool visibility_testing; diff --git a/Scene/BSP.cpp b/Scene/BSP.cpp index a94c459..14857d7 100644 --- a/Scene/BSP.cpp +++ b/Scene/BSP.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -9,6 +10,7 @@ #include + using namespace Q3BSP; static inline void copy_data(void* file_data, std::string& dst, Lump& lump) { @@ -22,20 +24,39 @@ static inline void copy_data(void* file_data, std::vector& dst, Lump& lump) { std::memcpy(dst.data(), ((u8*)file_data) + lump.offset, lump.len); } -void BSP::load_indices(const glm::vec3& cam_pos, bool visibility_test) { - indices.clear(); +void BSP::load_indices(const glm::vec3& cam_pos, bool visibility_test, const glm::mat4& view) { std::set present_faces; std::vector visible_faces; if (visibility_test) { auto leaf_idx = determine_leaf(cam_pos); + auto fr_planes = frustum(view); + + if (leaf_idx == last_leaf) + index_buffer->upload(indices); + last_leaf = leaf_idx; auto& cam_leaf = leafs[leaf_idx]; std::vector visible_leafs; for (auto& leaf : leafs) { - if (determine_visibility(cam_leaf.cluster_idx, leaf.cluster_idx)) + + const auto min = leaf.bb_mins; + const auto max = leaf.bb_maxes; + + const glm::vec3 bounding_planes[8] = { + { min.x, min.y, min.z }, + { max.x, min.y, min.z }, + { max.x, max.y, min.z }, + { min.x, max.y, min.z }, + { min.x, min.y, max.z }, + { max.x, min.y, max.z }, + { max.x, max.y, max.z }, + { min.x, max.y, max.z }, + }; + + if (determine_visibility(cam_leaf, leaf, fr_planes, bounding_planes)) visible_leafs.push_back(leaf); } @@ -53,6 +74,8 @@ void BSP::load_indices(const glm::vec3& cam_pos, bool visibility_test) { visible_faces = faces; } + indices.clear(); + for (auto& face : visible_faces) { switch (face.type) { case Face::ePATCH: @@ -88,14 +111,19 @@ int BSP::determine_leaf(glm::vec3 cam_pos) { } -bool BSP::determine_visibility(int vis, int cluster) { +bool BSP::determine_visibility(const Leaf& cam_leaf, const Leaf& leaf, const std::array& frustum, const glm::vec3 box_verts[8]) { + int vis = cam_leaf.cluster_idx, cluster = leaf.cluster_idx; if (vis_info.vectors.size() == 0 || vis < 0) return true; int i = (vis * vis_info.sz_vectors) + (cluster >> 3); u8 set = vis_info.vectors[i]; - return !!(set & (1 << (cluster & 0x7))); + if (!(set & (1 << (cluster & 0x7)))) + return false; + + /* perform fustrum culling */ + return box_in_frustum(frustum, box_verts); } /* changes handedness by swapping z and y */ @@ -152,7 +180,7 @@ BSP::BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname) vertex_buffer->upload(vertices); /* set limit at 256Mi indices */ - index_buffer = std::make_unique(phys_dev, dev, 0x10000000 * sizeof(u32), + index_buffer = std::make_unique(phys_dev, dev, 100000 * sizeof(u32), vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible ); } \ No newline at end of file diff --git a/Scene/BSP.hpp b/Scene/BSP.hpp index dbc55df..d506f1a 100644 --- a/Scene/BSP.hpp +++ b/Scene/BSP.hpp @@ -84,7 +84,6 @@ namespace Q3BSP { u32 n_leaf_brushes; }; - struct LeafFaces { /* list of face indices (one list per leaf) */ i32 face_idx; @@ -207,9 +206,9 @@ namespace Q3BSP { struct BSP { BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname); - void load_indices(const glm::vec3& cam_pos, bool visibility_testing); + void load_indices(const glm::vec3& cam_pos, bool visibility_testing, const glm::mat4& view); int determine_leaf(glm::vec3 cam_pos); - bool determine_visibility(int vis, int cluster); + bool determine_visibility(const Leaf& cam_leaf, const Leaf& leaf, const std::array& frustum, const glm::vec3 box_verts[8]); vk::Device dev; Header* header; diff --git a/UI/UI.cpp b/UI/UI.cpp index b363bf0..4275c37 100644 --- a/UI/UI.cpp +++ b/UI/UI.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -11,12 +12,26 @@ #include +static csys::ItemLog& operator<<(csys::ItemLog& log, ImVector& vec) { + if (!vec.size()) + return log << "vector {}"; + log << "vector { "; + for (int i = 0; i < vec.size() - 1; i++) + log << vec[i] << ", "; + return log << vec[vec.size() - 1] << " }"; +} + +static void vec_setter(ImVector& v, std::vector in) { + v.reserve(in.size()); + std::memcpy(v.Data, in.data(), sizeof(float) * in.size()); +} + UI::UI(Renderer* ren) : info { .flycam = ren->flycam, .visibility_testing = ren->visibility_testing, .time = ren->time, .cam = ren->cam, .tess_factor = ren->tess_factor, .tess_edge_size = ren->tess_edge_size, .n_indices = ren->n_indices, - .near_plane = ren->near_plane, .far_plane = ren->far_plane + .near_plane = ren->near_plane, .far_plane = ren->far_plane, .paused = ren->paused }, dev(ren->dev) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -85,6 +100,18 @@ UI::UI(Renderer* ren) : ImGui_ImplVulkan_DestroyFontUploadObjects(); ImGui::StyleColorsDark(); + + /* set up input so we can use the keyboard */ + auto& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + console = std::make_unique("developer console"); + console->System().RegisterCommand("pause", "Pauses or unpauses the engine", [this]() { + this->info.paused = !this->info.paused; + console->System().Log(csys::ItemType::INFO) << "Paused: " << (this->info.paused? "True" : "False") << csys::endl; + }); + + console->System().Log(csys::ItemType::INFO) << "Welcome to Ple'ascach!" << csys::endl; } void UI::newFrame() { @@ -105,6 +132,8 @@ void UI::newFrame() { ImGui::SliderFloat("Tessellation Factor", &info.tess_factor, 0.1, 10.0); ImGui::SliderFloat("Edge Size", &info.tess_edge_size, 0.0, 40.0); + console->Draw(); + ImGui::End(); } @@ -116,6 +145,7 @@ void UI::render(vk::CommandBuffer cmd) { UI::~UI() { dev.destroyDescriptorPool(desc_pool); + console.reset(); ImGui_ImplVulkan_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); diff --git a/UI/UI.hpp b/UI/UI.hpp index 5947c54..059f2a9 100644 --- a/UI/UI.hpp +++ b/UI/UI.hpp @@ -5,6 +5,10 @@ #include +#include + +#include + struct Renderer; struct Camera; @@ -21,13 +25,18 @@ struct UI { const size_t& n_indices; float& near_plane; float& far_plane; + bool& paused; } info; + + vk::Device dev; vk::DescriptorPool desc_pool; UI(Renderer* ren); + + std::unique_ptr console; void newFrame(); void render(vk::CommandBuffer cmd); diff --git a/bsp_capture.png b/bsp_capture.png new file mode 100644 index 0000000..2a773e8 Binary files /dev/null and b/bsp_capture.png differ diff --git a/include/csys/api.h b/include/csys/api.h new file mode 100644 index 0000000..6c2368f --- /dev/null +++ b/include/csys/api.h @@ -0,0 +1,69 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_API_H +#define CSYS_API_H + +#ifdef CSYS_COMPILED_LIB +# undef CSYS_HEADER_ONLY +# define CSYS_INLINE + +# ifdef CSYS_SHARED_LIB +// Windows Shared Library. +# if defined(_WIN32) +# ifdef csys_EXPORTS +# define CSYS_API __declspec(dllexport) +# else +# define CSYS_API __declspec(dllimport) +# endif + // Linux shared library. +# else +# ifdef csys_EXPORTS +# define CSYS_API __attribute__((visibility("default"))) +# else +# define CSYS_API __attribute__((visibility("default"))) +# endif +# endif +# else +# define CSYS_API +# endif + +// No export. +# ifndef CSYS_NO_EXPORT +# if defined(_WIN32) +# define CSYS_NO_EXPORT +# else +# define CSYS_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +# endif + +#else +# define CSYS_API +# define CSYS_NO_EXPORT +# define CSYS_HEADER_ONLY +# define CSYS_INLINE inline +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define CSYS_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define CSYS_DEPRECATED __declspec(deprecated) +#else +#define CSYS_DEPRECATED +#endif + +#ifndef CSYS_DEPRECATED_EXPORT +# define CSYS_DEPRECATED_EXPORT CSYS_API CSYS_DEPRECATED +#endif + +#ifndef CSYS_DEPRECATED_NO_EXPORT +# define CSYS_DEPRECATED_NO_EXPORT CSYS_NO_EXPORT CSYS_DEPRECATED +#endif + +#if 0 /* DEFINE_NO_DEPRECATED */ +# ifndef CSYS_NO_DEPRECATED +# define CSYS_NO_DEPRECATED +# endif +#endif + +#endif /* CSYS_API_H */ diff --git a/include/csys/argument_parser.h b/include/csys/argument_parser.h new file mode 100644 index 0000000..6631fba --- /dev/null +++ b/include/csys/argument_parser.h @@ -0,0 +1,516 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_ARGUMENT_PARSER_H +#define CSYS_ARGUMENT_PARSER_H +#pragma once + +#include "csys/api.h" +#include "csys/string.h" +#include "csys/exceptions.h" +#include +#include +#include + +namespace csys +{ + namespace + { + inline const std::string_view s_Reserved("\\[]\""); //!< All the reserved chars + inline constexpr char s_ErrMsgReserved[] = "Reserved chars '\\, [, ], \"' must be escaped with \\"; //!< Error message for reserved chars + } + + /*! + * \brief + * Helper class to check for parsing arguments that involve checking for reserved chars + */ + struct CSYS_API Reserved + { + /*! + * \brief + * Checks if the current char 'c' is the escaping char + * \param c + * Char to compare against with the escaping char + * \return + * Returns true if the char 'c' is the escaping char + */ + static inline bool IsEscapeChar(char c) + { return c == '\\'; } + + /*! + * \brief + * Checks if the current char is reserved + * \param c + * Char to check + * \return + * Returns true if the current char 'c' is reserved + */ + static inline bool IsReservedChar(char c) + { + for (auto rc : s_Reserved) if (c == rc) return true; + return false; + } + + /*! + * \brief + * Checks if a char is escaping another + * \param input + * Chars to check for it escaping + * \param pos + * Char that is escaping + * \return + * Returns true if the current char is the escaping char and is escaping + */ + static inline bool IsEscaping(std::string &input, size_t pos) + { + return pos < input.size() - 1 && IsEscapeChar(input[pos]) && IsReservedChar(input[pos + 1]); + } + + /*! + * \brief + * Checks if a certain char is being escaped + * \param input + * Chars to check + * \param pos + * The current char to check if its being escaped + * \return + * Returns true if the current char at 'pos' is being escaped + */ + static inline bool IsEscaped(std::string &input, size_t pos) + { + bool result = false; + + // Go through checking if the prev char is getting escaped and toggle between true and false + // Edge case is escaping the escaping char before another + for (size_t i = pos; i > 0; --i) + if (IsReservedChar(input[i]) && IsEscapeChar(input[i - 1])) + result = !result; + else + break; + return result; + } + + // Delete unwanted operations + Reserved() = delete; + ~Reserved() = delete; + Reserved(Reserved&&) = delete; + Reserved(const Reserved&) = delete; + Reserved& operator=(Reserved&&) = delete; + Reserved& operator=(const Reserved&) = delete; + }; + + /*! + * \brief + * Used for parsing arguments of different types + * \tparam T + * Argument type + */ + template + struct CSYS_API ArgumentParser + { + /*! + * \brief + * Constructor that parses the argument and sets value T + * \param input + * Command line set of arguments to be parsed + * \param start + * Start in 'input' to where this argument should be parsed from + */ + inline ArgumentParser(String &input, size_t &start); + + T m_Value; //!< Value of parsed argument + }; + + /*! + * \brief + * Macro for template specialization, used for cleaner code reuse + */ +#define ARG_PARSE_BASE_SPEC(TYPE) \ + template<> \ + struct CSYS_API ArgumentParser \ + { \ + inline ArgumentParser(String &input, size_t &start); \ + TYPE m_Value = 0; \ + }; \ + inline ArgumentParser::ArgumentParser(String &input, size_t &start) + + /*! + * \brief + * Macro for getting the sub-string within a range, used for readability + */ +#define ARG_PARSE_SUBSTR(range) input.m_String.substr(range.first, range.second - range.first) + + /*! + * \brief + * Macro for build-int types that already have functions in the stl for parsing them from a string + */ +#define ARG_PARSE_GENERAL_SPEC(TYPE, TYPE_NAME, FUNCTION) \ + ARG_PARSE_BASE_SPEC(TYPE) \ + { \ + auto range = input.NextPoi(start); \ + try \ + { \ + m_Value = (TYPE)FUNCTION(ARG_PARSE_SUBSTR(range), &range.first); \ + } \ + catch (const std::out_of_range&) \ + { \ + throw Exception(std::string("Argument too large for ") + TYPE_NAME, \ + input.m_String.substr(range.first, range.second)); \ + } \ + catch (const std::invalid_argument&) \ + { \ + throw Exception(std::string("Missing or invalid ") + TYPE_NAME + " argument", \ + input.m_String.substr(range.first, range.second)); } \ + } + + /*! + * \brief + * Template specialization for string argument parsing + */ + ARG_PARSE_BASE_SPEC(csys::String) + { + m_Value.m_String.clear(); // Empty string before using + + // Lambda for getting a word from the string and checking for reserved chars + static auto GetWord = [](std::string &str, size_t start, size_t end) + { + // For issues with reserved chars + static std::string invalid_chars; + invalid_chars.clear(); + + std::string result; + + // Go through the str from start to end + for (size_t i = start; i < end; ++i) + // general case, not reserved char + if (!Reserved::IsReservedChar(str[i])) + result.push_back(str[i]); + // is a reserved char + else + { + // check for \ char and if its escaping + if (Reserved::IsEscapeChar(str[i]) && Reserved::IsEscaping(str, i)) + result.push_back(str[++i]); + // reserved char but not being escaped + else + throw Exception(s_ErrMsgReserved, str.substr(start, end - start)); + } + + return result; + }; + + // Go to the start of the string argument + auto range = input.NextPoi(start); + + // If its a single string + if (input.m_String[range.first] != '"') + m_Value = GetWord(input.m_String, range.first, range.second); + // Multi word string + else + { + ++range.first; // move past the first " + while (true) + { + // Get the next non-escaped " + range.second = input.m_String.find('"', range.first); + while (range.second != std::string::npos && Reserved::IsEscaped(input.m_String, range.second)) + range.second = input.m_String.find('"', range.second + 1); + + // Check for closing " + if (range.second == std::string::npos) + { + range.second = input.m_String.size(); + throw Exception("Could not find closing '\"'", ARG_PARSE_SUBSTR(range)); + } + + // Add word to already existing string + m_Value.m_String += GetWord(input.m_String, range.first, range.second); + + // Go to next word + range.first = range.second + 1; + + // End of string check + if (range.first < input.m_String.size() && !std::isspace(input.m_String[range.first])) + { + // joining two strings together + if (input.m_String[range.first] == '"') + ++range.first; + } + else + // End of input + break; + } + } + + // Finished parsing + start = range.second + 1; + } + + /*! + * \brief + * Template specialization for boolean argument parsing + */ + ARG_PARSE_BASE_SPEC(bool) + { + // Error messages + static const char *s_err_msg = "Missing or invalid boolean argument"; + static const char *s_false = "false"; + static const char *s_true = "true"; + + // Get argument + auto range = input.NextPoi(start); + + // check if the length is between the len of "true" and "false" + input.m_String[range.first] = char(std::tolower(input.m_String[range.first])); + + // true branch + if (range.second - range.first == 4 && input.m_String[range.first] == 't') + { + // Go through comparing grabbed arg to "true" char by char, bail if not the same + for (size_t i = range.first + 1; i < range.second; ++i) + if ((input.m_String[i] = char(std::tolower(input.m_String[i]))) != s_true[i - range.first]) + throw Exception(s_err_msg + std::string(", expected true"), ARG_PARSE_SUBSTR(range)); + m_Value = true; + } + // false branch + else if (range.second - range.first == 5 && input.m_String[range.first] == 'f') + { + // Go through comparing grabbed arg to "false" char by char, bail if not the same + for (size_t i = range.first + 1; i < range.second; ++i) + if ((input.m_String[i] = char(std::tolower(input.m_String[i]))) != s_false[i - range.first]) + throw Exception(s_err_msg + std::string(", expected false"), ARG_PARSE_SUBSTR(range)); + m_Value = false; + } + // anything else, not true or false + else + throw Exception(s_err_msg, ARG_PARSE_SUBSTR(range)); + } + + /*! + * \brief + * Template specialization for char argument parsing + */ + ARG_PARSE_BASE_SPEC(char) + { + // Grab the argument + auto range = input.NextPoi(start); + size_t len = range.second - range.first; + + // Check if its 3 or more letters + if (len > 2 || len <= 0) + throw Exception("Too many or no chars were given", ARG_PARSE_SUBSTR(range)); + // potential reserved char + else if (len == 2) + { + // Check if the first char is \ and the second is a reserved char + if (!Reserved::IsEscaping(input.m_String, range.first)) + throw Exception("Too many chars were given", ARG_PARSE_SUBSTR(range)); + + // is correct + m_Value = input.m_String[range.first + 1]; + } + // if its one char and reserved + else if (Reserved::IsReservedChar(input.m_String[range.first])) + throw Exception(s_ErrMsgReserved, ARG_PARSE_SUBSTR(range)); + // one char, not reserved + else + m_Value = input.m_String[range.first]; + } + + /*! + * \brief + * Template specialization for unsigned char argument parsing + */ + ARG_PARSE_BASE_SPEC(unsigned char) + { + // Grab the argument + auto range = input.NextPoi(start); + size_t len = range.second - range.first; + + // Check if its 3 or more letters + if (len > 2 || len <= 0) + throw Exception("Too many or no chars were given", ARG_PARSE_SUBSTR(range)); + // potential reserved char + else if (len == 2) + { + // Check if the first char is \ and the second is a reserved char + if (!Reserved::IsEscaping(input.m_String, range.first)) + throw Exception("Too many chars were given", ARG_PARSE_SUBSTR(range)); + + // is correct + m_Value = static_cast(input.m_String[range.first + 1]); + } + // if its one char and reserved + else if (Reserved::IsReservedChar(input.m_String[range.first])) + throw Exception(s_ErrMsgReserved, ARG_PARSE_SUBSTR(range)); + // one char, not reserved + else + m_Value = static_cast(input.m_String[range.first]); + } + + /*! + * \brief + * Template specialization for short argument parsing + */ + ARG_PARSE_GENERAL_SPEC(short, "signed short", std::stoi) + + /*! + * \brief + * Template specialization for unsigned short argument parsing + */ + ARG_PARSE_GENERAL_SPEC(unsigned short, "unsigned short", std::stoul) + + /*! + * \brief + * Template specialization for int argument parsing + */ + ARG_PARSE_GENERAL_SPEC(int, "signed int", std::stoi) + + /*! + * \brief + * Template specialization for unsigned int argument parsing + */ + ARG_PARSE_GENERAL_SPEC(unsigned int, "unsigned int", std::stoul) + + /*! + * \brief + * Template specialization for long argument parsing + */ + ARG_PARSE_GENERAL_SPEC(long, "long", std::stol) + + /*! + * \brief + * Template specialization for unsigned long argument parsing + */ + ARG_PARSE_GENERAL_SPEC(unsigned long, "unsigned long", std::stoul) + + /*! + * \brief + * Template specialization for long long argument parsing + */ + ARG_PARSE_GENERAL_SPEC(long long, "long long", std::stoll) + + /*! + * \brief + * Template specialization for unsigned long long argument parsing + */ + ARG_PARSE_GENERAL_SPEC(unsigned long long, "unsigned long long", std::stoull) + + /*! + * \brief + * Template specialization for float argument parsing + */ + ARG_PARSE_GENERAL_SPEC(float, "float", std::stof) + + /*! + * \brief + * Template specialization for double argument parsing + */ + ARG_PARSE_GENERAL_SPEC(double, "double", std::stod) + + /*! + * \brief + * Template specialization for long double argument parsing + */ + ARG_PARSE_GENERAL_SPEC(long double, "long double", std::stold) + + /*! + * \brief + * Template specialization for vector argument parsing + * \tparam T + * Type that the vector holds + * + */ + template + struct CSYS_API ArgumentParser> + { + /*! + * \brief + * Grabs a vector argument of type T from 'input' starting from 'start' + * \param input + * Input to the command for this class to parse its argument + * \param start + * Start of this argument + */ + ArgumentParser(String &input, size_t &start); + + std::vector m_Value; //!< Vector of data parsed + }; + + /*! + * \brief + * Grabs a vector argument of type T from 'input' starting from 'start' + * \tparam T + * Type of vector + * \param input + * Input to the command for this class to parse its argument + * \param start + * Start of this argument + */ + template + ArgumentParser>::ArgumentParser(String &input, size_t &start) + { + // Clean out vector before use + m_Value.clear(); + + // Grab the start of the vector argument + auto range = input.NextPoi(start); + + // Empty + if (range.first == input.End()) return; + + // Not starting with [ + if (input.m_String[range.first] != '[') + throw Exception("Invalid vector argument missing opening [", ARG_PARSE_SUBSTR(range)); + + // Erase [ + input.m_String[range.first] = ' '; + while (true) + { + // Get next argument in vector + range = input.NextPoi(range.first); + + // No more, empty vector + if (range.first == input.End()) return; + + // Is a nested vector, go deeper + else if (input.m_String[range.first] == '[') + m_Value.push_back(ArgumentParser(input, range.first).m_Value); + else + { + // Find first non-escaped ] + range.second = input.m_String.find(']', range.first); + while (range.second != std::string::npos && Reserved::IsEscaped(input.m_String, range.second)) + range.second = input.m_String.find(']', range.second + 1); + + // Check for closing ] + if (range.second == std::string::npos) + { + range.second = input.m_String.size(); + throw Exception("Invalid vector argument missing closing ]", ARG_PARSE_SUBSTR(range)); + } + + // Erase ] + input.m_String[range.second] = ' '; + start = range.first; + + // Parse all arguments contained within the vector + while (true) + { + // If end of parsing, get out + if ((range.first = input.NextPoi(range.first).first) >= range.second) + { + start = range.first; + return; + } + + // Parse argument and go to next + m_Value.push_back(ArgumentParser(input, start).m_Value); + range.first = start; + } + } + } + } +} + +#endif //CSYS_ARGUMENT_PARSER_H diff --git a/include/csys/arguments.h b/include/csys/arguments.h new file mode 100644 index 0000000..4d97566 --- /dev/null +++ b/include/csys/arguments.h @@ -0,0 +1,208 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_ARGUMENTS_H +#define CSYS_ARGUMENTS_H +#pragma once + +#include "csys/api.h" +#include "csys/string.h" +#include "csys/exceptions.h" +#include "csys/argument_parser.h" +#include + +namespace csys +{ + /*! + * \brief + * Macro for supporting trivial types + */ +#define SUPPORT_TYPE(TYPE, TYPE_NAME)\ + template<> struct is_supported_type { static constexpr bool value = true; }; \ + template<> \ + struct CSYS_API ArgData \ + { \ + explicit ArgData(String name) : m_Name(std::move(name)), m_Value() {} \ + const String m_Name; \ + String m_TypeName = TYPE_NAME; \ + TYPE m_Value; \ + }; + + using NULL_ARGUMENT = void (*)(); //!< Null argument typedef + + /*! + * \brief + * Base case struct where a type is not supported + */ + template struct is_supported_type { static constexpr bool value = false; }; + + /*! + * \brief + * Wrapper around a given data type to name it + * \tparam T + * Type of data that must have a default constructor + */ + template + struct CSYS_API ArgData + { + /*! + * \brief + * Non-default constructor + * \param name + * Name of the argument + */ + explicit ArgData(String name) : m_Name(std::move(name)), m_Value() + { } + + const String m_Name = ""; //!< Name of argument + String m_TypeName = "Unsupported Type"; //!< Name of type + T m_Value; //!< Actual value + }; + + //! Supported types + SUPPORT_TYPE(String, "String") + + SUPPORT_TYPE(bool, "Boolean") + + SUPPORT_TYPE(char, "Char") + + SUPPORT_TYPE(unsigned char, "Unsigned_Char") + + SUPPORT_TYPE(short, "Signed_Short") + + SUPPORT_TYPE(unsigned short, "Unsigned_Short") + + SUPPORT_TYPE(int, "Signed_Int") + + SUPPORT_TYPE(unsigned int, "Unsigned_Int") + + SUPPORT_TYPE(long, "Signed_Long") + + SUPPORT_TYPE(unsigned long, "Unsigned_Long") + + SUPPORT_TYPE(long long, "Signed_Long_Long") + + SUPPORT_TYPE(unsigned long long, "Unsigned_Long_Long") + + SUPPORT_TYPE(float, "Float") + + SUPPORT_TYPE(double, "Double") + + SUPPORT_TYPE(long double, "Long_Double") + + //! Supported containers + template struct is_supported_type> { static constexpr bool value = is_supported_type::value; }; + template + struct CSYS_API ArgData> + { + /*! + * \brief + * Constructor for a vector argument + * \param name + * Name for argument + */ + explicit ArgData(String name) : m_Name(std::move(name)) + {} + + const String m_Name; //!< Name of argument + String m_TypeName = std::string("Vector_Of_") + ArgData("").m_TypeName.m_String; //!< Type name + std::vector m_Value; //!< Vector of data + }; + + /*! + * \brief + * Wrapper around an argument for use of parsing a command line + * \tparam T + * Data type + */ + template + struct CSYS_API Arg + { + /*! + * \brief + * Is true if type of U is a supported type + * \tparam U + * Type to check if it is supported + */ + template + static constexpr bool is_supported_type_v = is_supported_type::value; + public: + + using ValueType = std::remove_cv_t>; //!< Type of this argument + + /*! + * \brief + * Constructor for an argument for naming + * \param name + * Name of the argument + */ + explicit Arg(const String &name) : m_Arg(name) + { + static_assert(is_supported_type_v, + "ValueType 'T' is not supported, see 'Supported types' for more help"); + } + + /*! + * \brief + * Grabs its own argument from the command line and sets its value + * \param input + * Command line argument list + * \param start + * Start of its argument + * \return + * Returns this + */ + Arg &Parse(String &input, size_t &start) + { + size_t index = start; + + // Check if there are more arguments to be read in + if (input.NextPoi(index).first == input.End()) + throw Exception("Not enough arguments were given", input.m_String); + // Set value grabbed from input aka command line argument + m_Arg.m_Value = ArgumentParser(input, start).m_Value; + return *this; + } + + /*! + * \brief + * Gets the info of the argument in the form of [name:type] + * \return + * Returns a string containing the arugment's info + */ + std::string Info() + { + return std::string(" [") + m_Arg.m_Name.m_String + ":" + m_Arg.m_TypeName.m_String + "]"; + } + + ArgData m_Arg; //!< Data relating to this argument + }; + + /*! + * \brief + * Template specialization for a null argument that gets appended to a command's argument list to check if more + * than the required number of arguments + */ + template<> + struct CSYS_API Arg + { + /*! + * \brief + * Checks if the input starting from param 'start' is all whitespace or not + * \param input + * Command line argument list + * \param start + * Start of its argument + * \return + * Returns this + */ + Arg &Parse(String &input, size_t &start) + { + if (input.NextPoi(start).first != input.End()) + throw Exception("Too many arguments were given", input.m_String); + return *this; + } + }; +} + +#endif //CSYS_ARGUMENTS_H diff --git a/include/csys/autocomplete.h b/include/csys/autocomplete.h new file mode 100644 index 0000000..c87fbfa --- /dev/null +++ b/include/csys/autocomplete.h @@ -0,0 +1,386 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_AUTOCOMPLETE_H +#define CSYS_AUTOCOMPLETE_H + +#pragma once + +#include "csys/api.h" +#include +#include +#include + +namespace csys +{ + // TODO: Check how to add support for UTF Encoding. + // TODO: Todo add max word suggestion depth. + // TODO: Only use "const char *" or "std::string" in csys. (On stl containers use iterators - SLOW). (Need to add std::string version) + + //!< Auto complete ternary search tree. + class CSYS_API AutoComplete + { + public: + + // Type definitions. + using r_sVector = std::vector &; + using sVector = std::vector; + + //!< Autocomplete node. + struct ACNode + { + explicit ACNode(const char data, bool isWord = false) : m_Data(data), m_IsWord(isWord), m_Less(nullptr), m_Equal(nullptr), m_Greater(nullptr) + {}; + + explicit ACNode(const char &&data, bool isWord = false) : m_Data(data), m_IsWord(isWord), m_Less(nullptr), m_Equal(nullptr), m_Greater(nullptr) + {}; + + ~ACNode() + { + delete m_Less; + delete m_Equal; + delete m_Greater; + }; + + char m_Data; //!< Node data. + bool m_IsWord; //!< Flag to determine if node is the end of a word. + ACNode *m_Less; //!< Left pointer. + ACNode *m_Equal; //!< Middle pointer. + ACNode *m_Greater; //!< Right pointer. + }; + + /*! + * \brief Default Constructor + */ + AutoComplete() = default; + + /*! + * \brief + * Copy constructor + * \param tree + * Tree to be copied + */ + AutoComplete(const AutoComplete &tree); + + /*! + * \brief + * Move constructor + * \param rhs + * Tree to be copied + */ + AutoComplete(AutoComplete &&rhs) = default; + + /*! + * \brief + * Assignment operator + * \param rhs + * Source tree + * \return + * Self + */ + AutoComplete &operator=(const AutoComplete &rhs); + + /*! + * \brief + * Move assignment operator + * \param rhs + * Source tree + * \return + * Self + */ + AutoComplete& operator=(AutoComplete&& rhs) = default; + + /*! + * + * \tparam inputType + * String input type + * \param[in] il + * List of string from which TST will be constructed + */ + template + AutoComplete(std::initializer_list il) + { + for (const auto &item : il) + { + Insert(item); + } + } + + /*! + * + * \tparam T + * Container type + * \param[in] items + * Arbitrary container of strings + */ + template + explicit AutoComplete(const T &items) + { + for (const auto &item : items) + { + Insert(item); + } + } + + /*! + * /brief + * Destructor + */ + ~AutoComplete(); + + /*! + * \brief + * Get tree node count + * \return + * Tree node count + */ + [[nodiscard]] size_t Size() const; + + /*! + * \brief + * Get tree word count + * \return + * Word count + */ + [[nodiscard]] size_t Count() const; + + /*! + * \brief + * Search if the given word is in the tree + * \param[in] word + * Word to search + * \return + * Found word + */ + bool Search(const char *word); + + /*! + * \brief + * Insert word into tree + * \param[in] word + * Word to be inserted + */ + void Insert(const char *word); + + /*! + * \brief + * Insert word into tree + * \param[in] word + * Word to be inserted + */ + void Insert(const std::string &word); + + /*! + * \brief + * Insert word into tree + * \tparam strType + * String type to be inserted + * \param[in] word + * Word to be inserted + */ + template + void Insert(const strType &word) + { + ACNode **ptr = &m_Root; + ++m_Count; + + while (*word != '\0') + { + // Insert char into tree. + if (*ptr == nullptr) + { + *ptr = new ACNode(*word); + ++m_Size; + } + + // Traverse tree. + if (*word < (*ptr)->m_Data) + { + ptr = &(*ptr)->m_Less; + } + else if (*word == (*ptr)->m_Data) + { + // String is already in tree, therefore only mark as word. + if (*(word + 1) == '\0') + { + if ((*ptr)->m_IsWord) + --m_Count; + + (*ptr)->m_IsWord = true; + } + + // Advance. + ptr = &(*ptr)->m_Equal; + ++word; + } + else + { + ptr = &(*ptr)->m_Greater; + } + } + } + + /*! + * \brief + * Removes a word from the tree if found + * \param[in] word + * String to be removed + */ + void Remove(const std::string &word); + + /*! + * \brief + * Retrieve suggestions that match the given prefix + * \tparam strType + * Prefix string type + * \param[in] prefix + * Prefix to use for suggestion lookup + * \param[out] ac_options + * Vector of found suggestions + */ + template + void Suggestions(const strType &prefix, r_sVector ac_options) + { + ACNode *ptr = m_Root; + auto temp = prefix; + + // Traverse tree and check if prefix exists. + while (ptr) + { + if (*prefix < ptr->m_Data) + { + ptr = ptr->m_Less; + } + else if (*prefix == ptr->m_Data) + { + // Prefix exists in tree. + if (*(prefix + 1) == '\0') + break; + + ptr = ptr->m_Equal; + ++prefix; + } + else + { + ptr = ptr->m_Greater; + } + } + + // Already a word. (No need to auto complete). + if (ptr && ptr->m_IsWord) return; + + // Prefix is not in tree. + if (!ptr) return; + + // Retrieve auto complete options. + SuggestionsAux(ptr->m_Equal, ac_options, temp); + } + + + /*! + * \brief + * Retrieve suggestions that match the given prefix + * \param[in] prefix + * Prefix to use for suggestion lookup + * \param[out] ac_options + * Vector of found suggestions + */ + void Suggestions(const char *prefix, r_sVector ac_options); + + /*! + * \brief + * Store suggestions that match prefix in ac_options and return partially completed prefix if possible. + * \param[in] prefix + * Prefix to use for suggestion lookup + * \param[out] ac_options + * Vector of found suggestions + * \return + * Partially completed prefix + */ + std::string Suggestions(const std::string &prefix, r_sVector ac_options); + + /*! + * \brief + * Retrieve suggestions that match the given prefix + * \param[in/out] prefix + * Prefix to use for suggestion lookup, will be partially completed if flag partial_complete is on + * \param[out] ac_options + * Vector of found suggestions + * \param[in] partial_complete + * Flag to determine if prefix string will be partially completed + */ + void Suggestions(std::string &prefix, r_sVector ac_options, bool partial_complete); + + /*! + * \brief + * Retrieve suggestions that match the given prefix + * \tparam strType + * Prefix string type + * \param[in] prefix + * Prefix to use for suggestion lookup + * \return + * Vector of found suggestions + */ + template + std::unique_ptr Suggestions(const strType &prefix) + { + auto temp = std::make_unique(); + Suggestions(prefix, *temp); + return temp; + } + + /*! + * \brief + * Retrieve suggestions that match the given prefix + * \param[in] prefix + * Prefix to use for suggestion lookup + * \return + * Vector of found suggestions + */ + std::unique_ptr Suggestions(const char *prefix); + + protected: + + /*! + * \param[in] root + * Permutation root + * \param[out] ac_options + * Vector of found suggestions + * \param[in] buffer + * Prefix buffer + */ + void SuggestionsAux(ACNode *root, r_sVector ac_options, std::string buffer); + + /*! + * \brief + * Remove word auxiliary function + * \param[in] root + * Current node to process + * \param[in] word + * String to look for and remove + * \return + * If node is word + */ + bool RemoveAux(ACNode *root, const char *word); + + /*! + * \brief + * Clone acNode and all its children + * \param src + * Node to copy from + * \param dest + * Where new node will be stored + */ + void DeepClone(ACNode *src, ACNode *&dest); + + ACNode *m_Root = nullptr; //!< Ternary Search Tree Root node + size_t m_Size = 0; //!< Node count + size_t m_Count = 0; //!< Word count + }; +} + +#ifdef CSYS_HEADER_ONLY +#include "csys/autocomplete.inl" +#endif + +#endif //CSYS_AUTOCOMPLETE_H diff --git a/include/csys/autocomplete.inl b/include/csys/autocomplete.inl new file mode 100644 index 0000000..c7a6e3f --- /dev/null +++ b/include/csys/autocomplete.inl @@ -0,0 +1,318 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef CSYS_HEADER_ONLY + +#include "csys/autocomplete.h" + +#endif + +namespace csys +{ + /////////////////////////////////////////////////////////////////////////// + // Constructor/Destructors //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + CSYS_INLINE AutoComplete::~AutoComplete() + { + delete m_Root; + } + + CSYS_INLINE AutoComplete::AutoComplete(const AutoComplete &tree) : m_Size(tree.m_Size), m_Count(tree.m_Count) + { + DeepClone(tree.m_Root, m_Root); + } + + CSYS_INLINE AutoComplete &AutoComplete::operator=(const AutoComplete &rhs) + { + // Prevent self assignment. + if (&rhs == this) return *this; + + // Clean. + delete m_Root; + + // Copy from source tree. + DeepClone(rhs.m_Root, m_Root); + m_Size = rhs.m_Size; + m_Count = rhs.m_Count; + + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + // Public methods ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + CSYS_INLINE size_t AutoComplete::Size() const + { + return m_Size; + } + + CSYS_INLINE size_t AutoComplete::Count() const + { + return m_Count; + } + + CSYS_INLINE bool AutoComplete::Search(const char *word) + { + ACNode *ptr = m_Root; + + // Traverse tree in look for the given string. + while (ptr) + { + if (*word < ptr->m_Data) + { + ptr = ptr->m_Less; + } else if (*word == ptr->m_Data) + { + // Word was found. + if (*(word + 1) == '\0' && ptr->m_IsWord) + return true; + + ptr = ptr->m_Equal; + ++word; + } else + { + ptr = ptr->m_Greater; + } + } + + return false; + } + + CSYS_INLINE void AutoComplete::Insert(const char *word) + { + ACNode **ptr = &m_Root; + ++m_Count; + + while (*word != '\0') + { + // Insert char into tree. + if (*ptr == nullptr) + { + *ptr = new ACNode(*word); + ++m_Size; + } + + // Traverse tree. + if (*word < (*ptr)->m_Data) + { + ptr = &(*ptr)->m_Less; + } else if (*word == (*ptr)->m_Data) + { + // String is already in tree, therefore only mark as word. + if (*(word + 1) == '\0') + { + if ((*ptr)->m_IsWord) + --m_Count; + + (*ptr)->m_IsWord = true; + } + + // Advance. + ptr = &(*ptr)->m_Equal; + ++word; + } else + { + ptr = &(*ptr)->m_Greater; + } + } + } + + CSYS_INLINE void AutoComplete::Insert(const std::string &word) + { + Insert(word.c_str()); + } + + CSYS_INLINE void AutoComplete::Remove(const std::string &word) + { + RemoveAux(m_Root, word.c_str()); + } + + CSYS_INLINE void AutoComplete::Suggestions(const char *prefix, std::vector &ac_options) + { + ACNode *ptr = m_Root; + auto temp = prefix; + + // Traverse tree and check if prefix exists. + while (ptr) + { + if (*prefix < ptr->m_Data) + { + ptr = ptr->m_Less; + } else if (*prefix == ptr->m_Data) + { + // Prefix exists in tree. + if (*(prefix + 1) == '\0') + break; + + ptr = ptr->m_Equal; + ++prefix; + } else + { + ptr = ptr->m_Greater; + } + } + + // Already a word. (No need to auto complete). + if (ptr && ptr->m_IsWord) return; + + // Prefix is not in tree. + if (!ptr) return; + + // Retrieve auto complete options. + SuggestionsAux(ptr->m_Equal, ac_options, temp); + } + + CSYS_INLINE std::string AutoComplete::Suggestions(const std::string &prefix, r_sVector &ac_options) + { + std::string temp = prefix; + Suggestions(temp, ac_options, true); + return temp; + } + + CSYS_INLINE void AutoComplete::Suggestions(std::string &prefix, r_sVector ac_options, bool partial_complete) + { + ACNode *ptr = m_Root; + const char *temp = prefix.data(); + size_t prefix_end = prefix.size(); + + // Traverse tree and check if prefix exists. + while (ptr) + { + if (*temp < ptr->m_Data) + { + ptr = ptr->m_Less; + } else if (*temp == ptr->m_Data) + { + // Prefix exists in tree. + if (*(temp + 1) == '\0') + { + if (partial_complete) + { + ACNode *pc_ptr = ptr->m_Equal; + + // Get partially completed string. + while (pc_ptr) + { + if (pc_ptr->m_Equal && !pc_ptr->m_Less && !pc_ptr->m_Greater) + prefix.push_back(pc_ptr->m_Data); + else + break; + + pc_ptr = pc_ptr->m_Equal; + } + } + + break; + } + + ptr = ptr->m_Equal; + ++temp; + } else + { + ptr = ptr->m_Greater; + } + } + + // Already a word. (No need to auto complete). + if (ptr && ptr->m_IsWord) return; + + // Prefix is not in tree. + if (!ptr) return; + + // Retrieve auto complete options. + SuggestionsAux(ptr->m_Equal, ac_options, prefix.substr(0, prefix_end)); + } + + CSYS_INLINE std::unique_ptr AutoComplete::Suggestions(const char *prefix) + { + auto temp = std::make_unique(); + Suggestions(prefix, *temp); + return temp; + } + + /////////////////////////////////////////////////////////////////////////// + // Private methods //////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + CSYS_INLINE void AutoComplete::SuggestionsAux(AutoComplete::ACNode *root, r_sVector ac_options, std::string buffer) + { + if (!root) return; + + // Continue looking in left branch. + if (root->m_Less) SuggestionsAux(root->m_Less, ac_options, buffer); + + // Word was found, push into autocomplete options. + if (root->m_IsWord) + { + ac_options.push_back(buffer.append(1, root->m_Data)); + buffer.pop_back(); + } + + // Continue in middle branch, and push character. + if (root->m_Equal) + { + SuggestionsAux(root->m_Equal, ac_options, buffer.append(1, root->m_Data)); + buffer.pop_back(); + } + + // Continue looking in right branch. + if (root->m_Greater) + { + SuggestionsAux(root->m_Greater, ac_options, buffer); + } + } + + CSYS_INLINE bool AutoComplete::RemoveAux(AutoComplete::ACNode *root, const char *word) + { + if (!root) return false; + + // String is in TST. + if (*(word + 1) == '\0' && root->m_Data == *word) + { + // Un-mark word node. + if (root->m_IsWord) + { + root->m_IsWord = false; + return (!root->m_Equal && !root->m_Less && !root->m_Greater); + } + // String is a prefix. + else + return false; + } else + { + // String is a prefix. + if (*word < root->m_Data) + RemoveAux(root->m_Less, word); + else if (*word > root->m_Data) + RemoveAux(root->m_Greater, word); + + // String is in TST. + else if (*word == root->m_Data) + { + // Char is unique. + if (RemoveAux(root->m_Equal, word + 1)) + { + delete root->m_Equal; + root->m_Equal = nullptr; + return !root->m_IsWord && (!root->m_Equal && !root->m_Less && !root->m_Greater); + } + } + } + + return false; + } + + CSYS_INLINE void AutoComplete::DeepClone(AutoComplete::ACNode *src, AutoComplete::ACNode *&dest) + { + if (!src) return; + + dest = new ACNode(*src); + DeepClone(src->m_Less, dest->m_Less); + DeepClone(src->m_Equal, dest->m_Equal); + DeepClone(src->m_Greater, dest->m_Greater); + } +} diff --git a/include/csys/command.h b/include/csys/command.h new file mode 100644 index 0000000..dc1b665 --- /dev/null +++ b/include/csys/command.h @@ -0,0 +1,292 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_COMMAND_H +#define CSYS_COMMAND_H +#pragma once + +#include +#include +#include +#include "csys/arguments.h" +#include "csys/exceptions.h" +#include "csys/item.h" + +namespace csys +{ + /*! + * \brief + * Non-templated class that allows for the storage of commands as well as accessing certain functionality of + * said commands. + */ + struct CommandBase + { + /*! + * \brief + * Default virtual destructor + */ + virtual ~CommandBase() = default; + + /*! + * \brief + * Parses and runs the function held within the child class + * \param input + * String of arguments for the command to parse and pass to the function + * \return + * Returns item error if the parsing in someway was messed up, and none if there was no issue + */ + virtual Item operator()(String &input) = 0; + + /*! + * \brief + * Gets info about the command and usage + * \return + * String containing info about the command + */ + [[nodiscard]] virtual std::string Help() = 0; + + /*! + * \brief + * Getter for the number of arguments the command takes + * \return + * Returns the number of arguments taken by the command + */ + [[nodiscard]] virtual size_t ArgumentCount() const = 0; + + /*! + * \brief + * Deep copies a command + * \return + * Pointer to newly copied command + */ + [[nodiscard]] virtual CommandBase* Clone() const = 0; + }; + + /*! + * \brief + * Base template for a command that takes N amount of arguments + * \tparam Fn + * Decltype of function to be called when command is parsed and ran + * \tparam Args + * Argument type list that is proportional to the argument list of the function Fn + */ + template + class CSYS_API Command : public CommandBase + { + public: + /*! + * \brief + * Constructor that sets the name, description, function and arguments as well as add a null argument + * for parsing + * \param name + * Name of the command to call by + * \param description + * Info about the command + * \param function + * Function to run when command is called + * \param args + * Arguments to be used for parsing and passing into the function. Must be of type "Arg" + */ + Command(String name, String description, Fn function, Args... args) : m_Name(std::move(name)), + m_Description(std::move(description)), + m_Function(function), + m_Arguments(args..., Arg()) + {} + + /*! + * \brief + * Parses and runs the function m_Function + * \param input + * String of arguments for the command to parse and pass to the function + * \return + * Returns item error if the parsing in someway was messed up, and none if there was no issue + */ + Item operator()(String &input) final + { + try + { + // Try to parse and call the function + constexpr int argumentSize = sizeof... (Args); + Call(input, std::make_index_sequence{}, std::make_index_sequence{}); + } + catch (Exception &ae) + { + // Error happened with parsing + return Item(ERROR) << (m_Name.m_String + ": " + ae.what()); + } + return Item(NONE); + } + + /*! + * \brief + * Gets info about the command and usage + * \return + * String containing info about the command + */ + [[nodiscard]] std::string Help() final + { + return m_Name.m_String + DisplayArguments(std::make_index_sequence{}) + "\n\t\t- " + + m_Description.m_String + "\n\n"; + } + + /*! + * \brief + * Getter for the number of arguments the command takes + * \return + * Returns the number of arguments taken by the command + */ + [[nodiscard]] size_t ArgumentCount() const final + { + return sizeof... (Args); + } + + /*! + * \brief + * Deep copies a command + * \return + * Pointer to newly copied command + */ + [[nodiscard]] CommandBase* Clone() const final + { + return new Command(*this); + } + private: + /*! + * \brief + * Parses arguments and passes them into the command to be ran + * \tparam Is_p + * Index sequence from 0 to Argument Count + 1, used for parsing + * \tparam Is_c + * Index sequence from 0 to Argument Count, used for passing into the function + * \param input + * String of arguments to be parsed + */ + template + void Call(String &input, const std::index_sequence &, const std::index_sequence &) + { + size_t start = 0; + + // Parse arguments + int _[]{0, (void(std::get(m_Arguments).Parse(input, start)), 0)...}; + (void) (_); + + // Call function with unpacked tuple + m_Function((std::get(m_Arguments).m_Arg.m_Value)...); + } + + /*! + * \brief + * Displays the usage for running the command successfully + * \tparam Is + * Index sequence from 0 to Argument CountH + * \return + * Returns a string containing the usage of the command + */ + template + std::string DisplayArguments(const std::index_sequence &) + { + return (std::get(m_Arguments).Info() + ...); + } + + const String m_Name; //!< Name of command + const String m_Description; //!< Description of the command + std::function m_Function; //!< Function to be invoked as command + std::tuple> m_Arguments; //!< Arguments to be passed into m_Function + }; + + /*! + * \brief + * Template specialization for a command that doesn't take any arguments + * \tparam Fn + * Decltype of function to be called when the command is invoked + */ + template + class CSYS_API Command : public CommandBase + { + public: + /*! + * \brief + * Constructor that sets the name, description and function. A null argument will be added to the arguments + * for parsing + * \param name + * Name of the command to call by + * \param description + * Info about the command + * \param function + * Function to run when command is called + */ + Command(String name, String description, Fn function) : m_Name(std::move(name)), + m_Description(std::move(description)), + m_Function(function), m_Arguments(Arg()) + {} + + /*! + * \brief + * Parses and runs the function m_Function + * \param input + * String of arguments for the command to parse and pass to the function. This should be empty + * \return + * Returns item error if the parsing in someway was messed up, and none if there was no issue + */ + Item operator()(String &input) final + { + // call the function + size_t start = 0; + try + { + // Check to see if input is all whitespace + std::get<0>(m_Arguments).Parse(input, start); + } + catch (Exception &ae) + { + // Command had something passed into it + return Item(ERROR) << (m_Name.m_String + ": " + ae.what()); + } + + // Call function + m_Function(); + return Item(NONE); + } + + /*! + * \brief + * Gets info about the command and usage + * \return + * String containing info about the command + */ + [[nodiscard]] std::string Help() final + { + return m_Name.m_String + "\n\t\t- " + m_Description.m_String + "\n\n"; + } + + /*! + * \brief + * Getter for the number of arguments the command takes + * \return + * 0 + */ + [[nodiscard]] size_t ArgumentCount() const final + { + return 0; + } + + /*! + * \brief + * Deep copies a command + * \return + * Pointer to newly copied command + */ + [[nodiscard]] CommandBase* Clone() const final + { + return new Command(*this); + } + private: + + const String m_Name; //!< Name of command + const String m_Description; //!< Description of the command + std::function m_Function; //!< Function to be invoked as command + std::tuple> m_Arguments; //!< Arguments to be passed into m_Function + }; +} + +#endif //CSYS_COMMAND_H diff --git a/include/csys/csys.h b/include/csys/csys.h new file mode 100644 index 0000000..f16fb43 --- /dev/null +++ b/include/csys/csys.h @@ -0,0 +1,11 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_H +#define CSYS_H + +#pragma once + +#include "csys/system.h" + +#endif diff --git a/include/csys/exceptions.h b/include/csys/exceptions.h new file mode 100644 index 0000000..a06323e --- /dev/null +++ b/include/csys/exceptions.h @@ -0,0 +1,64 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_EXCEPTIONS_H +#define CSYS_EXCEPTIONS_H +#pragma once + +#include +#include +#include +#include "csys/api.h" + +namespace csys +{ + /*! + * \brief + * Csys exception extended from the stl for any issues + */ + class CSYS_API Exception : public std::exception + { + public: + /*! + * \brief + * Basic constructor that makes a message with an arg + * \param message + * Message describing what went wrong + * \param arg + * What went wrong (will get put between ' ') + */ + explicit Exception(const std::string &message, const std::string &arg) : m_Msg(message + ": '" + arg + "'") + {} + + /*! + * \brief + * Constructor for one string + * \param message + * Message describing what went wrong + */ + explicit Exception(std::string message) : m_Msg(std::move(message)) + {} + + /*! + * \brief + * Default destructor + */ + ~Exception() override = default; + + /*! + * \brief + * Gets what happened + * \return + * Returns a c-style string of what happened + */ + [[nodiscard]] const char *what() const noexcept override + { + return m_Msg.c_str(); + } + + protected: + std::string m_Msg; //!< Message of what happened + }; +} + +#endif //CSYS_CSYS_EXCEPTIONS_H diff --git a/include/csys/history.h b/include/csys/history.h new file mode 100644 index 0000000..c06c653 --- /dev/null +++ b/include/csys/history.h @@ -0,0 +1,152 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_HISTORY_H +#define CSYS_HISTORY_H +#pragma once + +#include +#include +#include "csys/api.h" + +namespace csys +{ + class CSYS_API CommandHistory + { + public: + + /*! + * \brief + * Command history constructor. + * \param maxRecord + * Maximum amount of command strings to keep track at once. + */ + explicit CommandHistory(unsigned int maxRecord = 100); + + /*! + * \brief + * Move constructor + * \param rhs + * History to be copied. + */ + CommandHistory(CommandHistory &&rhs) = default; + + /*! + * \brief + * Copy constructor + * \param rhs + * History to be copied. + */ + CommandHistory(const CommandHistory &rhs) = default; + + /*! + * \brief + * Move assignment operator + * \param rhs + * History to be copied. + */ + CommandHistory &operator=(CommandHistory &&rhs) = default; + + /*! + * \brief + * Copy assigment operator. + * \param rhs + * History to be copied. + */ + CommandHistory &operator=(const CommandHistory &rhs) = default; + + /*! + * \brief + * Record command string. (Start at the beginning once end is reached). + * \param line + * Command string to be recorded. + */ + void PushBack(const std::string &line); + + /*! + * \brief + * Get newest register command entry index + * \return + * Newest command entry index + */ + [[nodiscard]] unsigned int GetNewIndex() const; + + /*! + * \brief + * Get newest register command entry + * \return + * Newest command entry + */ + const std::string &GetNew(); + + /*! + * \brief + * Get oldest register command entry index + * \return + * Oldest command entry index + */ + [[nodiscard]] unsigned int GetOldIndex() const; + + /*! + * \brief + * Get oldest register command entry + * \return + * Oldest command entry string + */ + const std::string &GetOld(); + + /*! + * \brief Clear command history + */ + void Clear(); + + /*! + * \brief + * Retrieve command history at given index + * \param index + * Position to lookup in command history vector + * \return + * Command at given index + * + * \note + * No bound checking is performed when accessing with these index operator. + * Use the *index()* method for safe history vector indexing. + */ + const std::string &operator[](size_t index); + + /*! + * \brief + * Output available command history. + * \param os + * Output stream + * \param history + * Reference to history to be printed + * \return + * Reference to history to be printed + */ + friend std::ostream &operator<<(std::ostream &os, const CommandHistory &history); + + /*! + * \return + * Number of registered commands. + */ + size_t Size(); + + /*! + * \return + * Maximum commands that are able to be recorded + */ + size_t Capacity(); + + protected: + unsigned int m_Record; //!< Amount of commands recorded + unsigned int m_MaxRecord; //!< Maximum command record to keep track of + std::vector m_History; //!< Console command history + }; +} + +#ifdef CSYS_HEADER_ONLY +#include "csys/history.inl" +#endif + +#endif //CSYS_HISTORY_H diff --git a/include/csys/history.inl b/include/csys/history.inl new file mode 100644 index 0000000..cbbfcf1 --- /dev/null +++ b/include/csys/history.inl @@ -0,0 +1,83 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef CSYS_HEADER_ONLY + +#include "csys/history.h" + +#endif + +#include +#include + +namespace csys +{ + /////////////////////////////////////////////////////////////////////////// + // Public methods ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + CSYS_INLINE CommandHistory::CommandHistory(unsigned int maxRecord) : m_Record(0), m_MaxRecord(maxRecord), m_History(maxRecord) + { + } + + CSYS_INLINE void CommandHistory::PushBack(const std::string &line) + { + m_History[m_Record++ % m_MaxRecord] = line; + } + + CSYS_INLINE unsigned int CommandHistory::GetNewIndex() const + { + return (m_Record - 1) % m_MaxRecord; + } + + CSYS_INLINE const std::string &CommandHistory::GetNew() + { + return m_History[(m_Record - 1) % m_MaxRecord]; + } + + CSYS_INLINE unsigned int CommandHistory::GetOldIndex() const + { + if (m_Record <= m_MaxRecord) + return 0; + else + return m_Record % m_MaxRecord; + } + + CSYS_INLINE const std::string &CommandHistory::GetOld() + { + if (m_Record <= m_MaxRecord) + return m_History.front(); + else + return m_History[m_Record % m_MaxRecord]; + } + + CSYS_INLINE void CommandHistory::Clear() + { + m_Record = 0; + } + + CSYS_INLINE const std::string &CommandHistory::operator[](size_t index) + { + return m_History[index]; + } + + CSYS_INLINE std::ostream &operator<<(std::ostream &os, const CommandHistory &history) + { + os << "History: " << '\n'; + for (unsigned int i = 0; i < history.m_Record && history.m_Record <= history.m_MaxRecord; ++i) + std::cout << history.m_History[i] << '\n'; + return os; + } + + CSYS_INLINE size_t CommandHistory::Size() + { + return m_Record < m_MaxRecord ? m_Record : m_MaxRecord; + } + + CSYS_INLINE size_t CommandHistory::Capacity() + { + return m_History.capacity(); + } +} \ No newline at end of file diff --git a/include/csys/item.h b/include/csys/item.h new file mode 100644 index 0000000..043ca01 --- /dev/null +++ b/include/csys/item.h @@ -0,0 +1,197 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_ITEM_H +#define CSYS_ITEM_H +#pragma once + +#include +#include +#include "csys/api.h" + +namespace csys +{ + static const char endl = '\n'; + + /*! + * \brief + * Console item type: + * - Command: Only used for commands. + * - Log: Used to log information inside a command. + * - Warning: Warn client through console. + * - Error: Display error to client through console. + * - Info: Any information wished to display through console. + * - None: Empty console item. + */ + enum ItemType + { + COMMAND = 0, + LOG, + WARNING, + ERROR, + INFO, + NONE + }; + + struct CSYS_API Item + { + /*! + * \brief + * Create console item type + * \param type + * Item type to be stored + */ + explicit Item(ItemType type = ItemType::LOG); + + /*! + * \brief + * Move constructor + * \param rhs + * Item to be copied. + */ + Item(Item &&rhs) = default; + + /*! + * \brief + * Copy constructor + * \param rhs + * Item to be copied. + */ + Item(const Item &rhs) = default; + + /*! + * \brief + * Move assignment operator + * \param rhs + * Item to be copied. + */ + Item &operator=(Item &&rhs) = default; + + /*! + * \brief + * Copy assigment operator. + * \param rhs + * Item to be copied. + */ + Item &operator=(const Item &rhs) = default; + + /*! + * \brief + * Log data to console item + * \param str + * Append string to item data + * \return + * Self (To allow for fluent logging) + */ + Item &operator<<(std::string_view str); + + /*! + * \brief + * Get final/styled string of the item + * \return + * Stylized item string + */ + [[nodiscard]] std::string Get() const; + + ItemType m_Type; //!< Console item type + std::string m_Data; //!< Item string data + unsigned int m_TimeStamp; //!< Record timestamp + }; + +#define LOG_BASIC_TYPE_DECL(type) ItemLog& operator<<(type data) + + class CSYS_API ItemLog + { + public: + + /*! + * \brief + * Log console item + * \param type + * Type of item to log + * \return + * Self (To allow for fluent logging) + */ + ItemLog &log(ItemType type); + + ItemLog() = default; + + /*! + * \brief + * Move constructor + * \param rhs + * ItemLog to be copied. + */ + ItemLog(ItemLog &&rhs) = default; + + /*! + * \brief + * Copy constructor + * \param rhs + * ItemLog to be copied. + */ + ItemLog(const ItemLog &rhs) = default; + + /*! + * \brief + * Move assignment operator + * \param rhs + * ItemLog to be copied. + */ + ItemLog &operator=(ItemLog &&rhs) = default; + + /*! + * \brief + * Copy assigment operator. + * \param rhs + * ItemLog to be copied. + */ + ItemLog &operator=(const ItemLog &rhs) = default; + + /*! + * \brief + * Get logged console items + * \return + * Console log + */ + std::vector &Items(); + + /*! + * \brief Delete console item log history + */ + void Clear(); + + LOG_BASIC_TYPE_DECL(int); + + LOG_BASIC_TYPE_DECL(long); + + LOG_BASIC_TYPE_DECL(float); + + LOG_BASIC_TYPE_DECL(double); + + LOG_BASIC_TYPE_DECL(long long); + + LOG_BASIC_TYPE_DECL(long double); + + LOG_BASIC_TYPE_DECL(unsigned int); + + LOG_BASIC_TYPE_DECL(unsigned long); + + LOG_BASIC_TYPE_DECL(unsigned long long); + + LOG_BASIC_TYPE_DECL(std::string_view); + + LOG_BASIC_TYPE_DECL(char); + + protected: + std::vector m_Items; + }; +} + +#ifdef CSYS_HEADER_ONLY + +#include "csys/item.inl" + +#endif + +#endif diff --git a/include/csys/item.inl b/include/csys/item.inl new file mode 100644 index 0000000..6c0e383 --- /dev/null +++ b/include/csys/item.inl @@ -0,0 +1,116 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef CSYS_HEADER_ONLY + +#include "csys/item.h" + +#endif + +#include + +namespace csys +{ + + /////////////////////////////////////////////////////////////////////////// + // Console Item /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + CSYS_INLINE static const std::string_view s_Command = "> "; + CSYS_INLINE static const std::string_view s_Warning = "\t[WARNING]: "; + CSYS_INLINE static const std::string_view s_Error = "[ERROR]: "; + CSYS_INLINE static const auto s_TimeBegin = std::chrono::steady_clock::now(); + + CSYS_INLINE Item::Item(ItemType type) : m_Type(type) + { + auto timeNow = std::chrono::steady_clock::now(); + m_TimeStamp = static_cast(std::chrono::duration_cast(timeNow - s_TimeBegin).count()); + } + + CSYS_INLINE Item &Item::operator<<(const std::string_view str) + { + m_Data.append(str); + return *this; + } + + CSYS_INLINE std::string Item::Get() const + { + switch (m_Type) + { + case COMMAND: + return s_Command.data() + m_Data; + case LOG: + return '\t' + m_Data; + case WARNING: + return s_Warning.data() + m_Data; + case ERROR: + return s_Error.data() + m_Data; + case INFO: + return m_Data; + case NONE: + default: + return ""; + } + } + + /////////////////////////////////////////////////////////////////////////// + // Console Item Log /////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + +#define LOG_BASIC_TYPE_DEF(type)\ + CSYS_INLINE ItemLog& ItemLog::operator<<(type data)\ + {\ + m_Items.back() << std::to_string(data);\ + return *this;\ + } + + CSYS_INLINE ItemLog &ItemLog::log(ItemType type) + { + // New item. + m_Items.emplace_back(type); + return *this; + } + + CSYS_INLINE std::vector &ItemLog::Items() + { + return m_Items; + } + + CSYS_INLINE void ItemLog::Clear() + { + m_Items.clear(); + } + + CSYS_INLINE ItemLog &ItemLog::operator<<(const std::string_view data) + { + m_Items.back() << data; + return *this; + } + + CSYS_INLINE ItemLog &ItemLog::operator<<(const char data) + { + m_Items.back().m_Data.append(1, data); + return *this; + } + + // Basic type operator definitions. + LOG_BASIC_TYPE_DEF(int) + + LOG_BASIC_TYPE_DEF(long) + + LOG_BASIC_TYPE_DEF(float) + + LOG_BASIC_TYPE_DEF(double) + + LOG_BASIC_TYPE_DEF(long long) + + LOG_BASIC_TYPE_DEF(long double) + + LOG_BASIC_TYPE_DEF(unsigned int) + + LOG_BASIC_TYPE_DEF(unsigned long) + + LOG_BASIC_TYPE_DEF(unsigned long long) +} \ No newline at end of file diff --git a/include/csys/script.h b/include/csys/script.h new file mode 100644 index 0000000..f70af54 --- /dev/null +++ b/include/csys/script.h @@ -0,0 +1,120 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_SCRIPT_H +#define CSYS_SCRIPT_H +#pragma once + +#include +#include +#include "csys/api.h" + +namespace csys +{ + class CSYS_API Script + { + public: + + /*! + * \brief + * Create script object from file + * \param path + * Path of script file + * \param load_on_init + * Load script when object is created + */ + explicit Script(std::string path, bool load_on_init = true); + + /*! + * \brief + * Create script object from file + * \param path + * Path of script file + * \param load_on_init + * Load script when object is created + */ + explicit Script(const char *path, bool load_on_init = true); + + /*! + * \brief + * Create script object from file already in memory + * \param data + * Script file memory + */ + explicit Script(std::vector data); + + /*! + * \brief + * Move constructor + * \param rhs + * Script to be copied. + */ + Script(Script &&rhs) = default; + + /*! + * \brief + * Copy constructor + * \param rhs + * Script to be copied. + */ + Script(const Script &rhs) = default; + + /*! + * \brief + * Move assignment operator + * \param rhs + * Script to be copied. + */ + Script &operator=(Script &&rhs) = default; + + /*! + * \brief + * Copy assigment operator. + * \param rhs + * Script to be copied. + */ + Script &operator=(const Script &rhs) = default; + + /*! + * \brief Load script file + */ + void Load(); + + /*! + * \brief Reload script file (Unload & Load) + */ + void Reload(); + + /*! + * /brief Clear script data + */ + void Unload(); + + /*! + * \brief + * Set script file path (Will be used when laoding) + * \param path + * Script file path + */ + void SetPath(std::string path); + + /*! + * \brief + * Retrieve script data (Commands) + * \return + * List of commands in script + */ + const std::vector &Data(); + + protected: + std::vector m_Data; //!< Commands in script + std::string m_Path; //!< Path of script file + bool m_FromMemory; //!< Flag to specify if script was loaded from file or memory + }; +} + +#ifdef CSYS_HEADER_ONLY +#include "csys/script.inl" +#endif + +#endif diff --git a/include/csys/script.inl b/include/csys/script.inl new file mode 100644 index 0000000..72dd4d3 --- /dev/null +++ b/include/csys/script.inl @@ -0,0 +1,83 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef CSYS_HEADER_ONLY + +#include "csys/script.h" + +#endif + +#include +#include +#include "csys/exceptions.h" + +namespace csys +{ + + CSYS_INLINE Script::Script(std::string path, bool load_on_init) : m_Path(std::move(path)), m_FromMemory(false) + { + // Load file. + if (load_on_init) + Load(); + } + + CSYS_INLINE Script::Script(const char *path, bool load_on_init) : m_Path(path), m_FromMemory(false) + { + // Load file. + if (load_on_init) + Load(); + } + + CSYS_INLINE Script::Script(std::vector data) : m_Data(std::move(data)), m_FromMemory(true) + {} + + CSYS_INLINE void Script::Load() + { + std::ifstream script_fstream(m_Path); + + // Error check. + if (!script_fstream.good()) + throw csys::Exception("Failed to load script", m_Path); + + // Check and open file. + if (script_fstream.good() && script_fstream.is_open()) + { + // Line buffer. + std::string line_buf; + + // Read commands. + while (std::getline(script_fstream, line_buf)) + { + m_Data.emplace_back(line_buf); + } + + // Close file. + script_fstream.close(); + } + } + + CSYS_INLINE void Script::Reload() + { + if (m_FromMemory) return; + + Unload(); + Load(); + } + + CSYS_INLINE void Script::Unload() + { + m_Data.clear(); + } + + CSYS_INLINE void Script::SetPath(std::string path) + { + m_Path = std::move(path); + } + + CSYS_INLINE const std::vector &Script::Data() + { + return m_Data; + } +} diff --git a/include/csys/string.h b/include/csys/string.h new file mode 100644 index 0000000..4f8d972 --- /dev/null +++ b/include/csys/string.h @@ -0,0 +1,110 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef CSYS_STRING_H +#define CSYS_STRING_H +#pragma once + +#include +#include +#include "csys/api.h" + +namespace csys +{ + /*! + * \brief + * Wrapper around std::string to be used with parsing a string argument + */ + struct CSYS_API String + { + /*! + * \brief + * Default constructor + */ + String() = default; + + /*! + * \brief + * Conversion constructor from c-style string. A deep copy is made + * \param str + * String to be converted + */ + String(const char *str [[maybe_unused]]) : m_String(str ? str : "") + {} + + /*! + * \brief + * Conversion constructor from an std::string to csys::String. A deep copy is made + * \param str + * String to be converted + */ + String(std::string str) : m_String(std::move(str)) + {} + + /*! + * \brief + * Conversion constructor from csys::String to a c-style string + * \return + * Returns a pointer to the string contained within this string + */ + operator const char *() + { return m_String.c_str(); } + + /*! + * \brief + * Conversion constructor from csys::String to an std::string + * \return + * Returns a copy of this string + */ + operator std::string() + { return m_String; } + + /*! + * \brief + * Moves until first non-whitespace char, and continues until the end of the string or a whitespace has is + * found + * \param start + * Where to start scanning from. Will be set to pair.second + * \return + * Returns the first element and one passed the end of non-whitespace. In other words [first, second) + */ + std::pair NextPoi(size_t &start) const + { + size_t end = m_String.size(); + std::pair range(end + 1, end); + size_t pos = start; + + // Go to the first non-whitespace char + for (; pos < end; ++pos) + if (!std::isspace(m_String[pos])) + { + range.first = pos; + break; + } + + // Go to the first whitespace char + for (; pos < end; ++pos) + if (std::isspace(m_String[pos])) + { + range.second = pos; + break; + } + + start = range.second; + return range; + } + + /*! + * \brief + * Returns a value to be compared with the .first of the pair returned from NextPoi + * \return + * Returns size of string + 1 + */ + [[nodiscard]] size_t End() const + { return m_String.size() + 1; } + + std::string m_String; //!< String data member + }; +} + +#endif //CSYS_CSYS_STRING_H diff --git a/include/csys/system.h b/include/csys/system.h new file mode 100644 index 0000000..51ce00e --- /dev/null +++ b/include/csys/system.h @@ -0,0 +1,356 @@ +// 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 +#include +#include + +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 &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> &Commands(); + + /*! + * \brief + * Get registered scripts container + * \return + * Scripts container + */ + std::unordered_map> &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 + * \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::Args that matches that of the argument list of 'function' + */ + template + 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, "Arguments specified do not match that of the function"); + static_assert(!std::is_member_function_pointer_v, "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>(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>("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 + void RegisterVariable(const String &name, T &var, Arg... args) + { + static_assert(std::is_constructible_v, "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...>>("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 + 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...>>("set " + var_name, + "Sets the variable " + var_name, + setter_l, Arg("")...); + } + + /*! + * \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 + 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>("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> 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> 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...); //c +// registaerVariable("name", var, setter); //d diff --git a/include/csys/system.inl b/include/csys/system.inl new file mode 100644 index 0000000..bff3939 --- /dev/null +++ b/include/csys/system.inl @@ -0,0 +1,300 @@ +// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef CSYS_HEADER_ONLY + +#include "csys/system.h" + +#endif + +namespace csys +{ + /////////////////////////////////////////////////////////////////////////// + // Public methods ///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + // Commands-Error-Warning strings. + static const std::string_view s_Set = "set"; + static const std::string_view s_Get = "get"; + static const std::string_view s_Help = "help"; + static const std::string_view s_ErrorNoVar = "No variable provided"; + static const std::string_view s_ErrorSetGetNotFound = "Command doesn't exist and/or variable is not registered"; + + CSYS_INLINE System::System() + { + // Register help command. + RegisterCommand(s_Help.data(), "Display commands information", [this]() + { + // Custom command information display + Log() << "help [command_name:String] (Optional)\n\t\t- Display command(s) information\n" << csys::endl; + Log() << "set [variable_name:String] [data]\n\t\t- Assign data to given variable\n" << csys::endl; + Log() << "get [variable_name:String]\n\t\t- Display data of given variable\n" << csys::endl; + + for (const auto &tuple : Commands()) + { + // Filter set and get. + if (tuple.first.size() >= 5 && (tuple.first[3] == ' ' || tuple.first[4] == ' ')) + continue; + + // Skip help command. + if (tuple.first.size() == 4 && (tuple.first == "help")) + continue; + + // Print the rest of commands + Log() << tuple.second->Help(); + } + }); + + // Register pre-defined commands. + m_CommandSuggestionTree.Insert(s_Set.data()); + m_CommandSuggestionTree.Insert(s_Get.data()); + } + + CSYS_INLINE System::System(const System &rhs) : m_CommandSuggestionTree(rhs.m_CommandSuggestionTree), + m_VariableSuggestionTree(rhs.m_VariableSuggestionTree), + m_CommandHistory(rhs.m_CommandHistory), + m_ItemLog(rhs.m_ItemLog), + m_RegisterCommandSuggestion(rhs.m_RegisterCommandSuggestion) + { + // Copy commands. + for (const auto &pair : rhs.m_Commands) + { + m_Commands[pair.first] = std::unique_ptr(pair.second->Clone()); + } + + // Copy scripts. + for (const auto &pair: rhs.m_Scripts) + { + m_Scripts[pair.first] = std::make_unique