From f3fa22c3c598194ba6c44f2385b673946205849f Mon Sep 17 00:00:00 2001 From: connellpaxton Date: Sun, 30 Mar 2025 23:40:19 -0400 Subject: [PATCH] Revived the project, it seems to be mostly functional right now in terms of load/unloading files of scenes. Not yet tested on linux. --- CMakeLists.txt | 3 +- Input/Input.cpp | 11 +- Renderer/Renderer.cpp | 59 ++- Renderer/Renderer.hpp | 17 +- Scene/BSP.cpp | 62 --- Scene/BSP.hpp | 194 ---------- Scene/March.hpp | 168 ++++++++ UI/UI.cpp | 129 ++++++- UI/UI.hpp | 17 +- assets/shaders/ray.frag | 9 +- assets/shaders/ray.frag.spv | Bin 8072 -> 7920 bytes include/csys/api.h | 69 ++++ include/csys/argument_parser.h | 516 +++++++++++++++++++++++++ include/csys/arguments.h | 208 ++++++++++ include/csys/autocomplete.h | 386 +++++++++++++++++++ include/csys/autocomplete.inl | 318 ++++++++++++++++ include/csys/command.h | 292 ++++++++++++++ include/csys/csys.h | 11 + include/csys/exceptions.h | 64 ++++ include/csys/history.h | 152 ++++++++ include/csys/history.inl | 83 ++++ include/csys/item.h | 197 ++++++++++ include/csys/item.inl | 116 ++++++ include/csys/script.h | 120 ++++++ include/csys/script.inl | 83 ++++ include/csys/string.h | 110 ++++++ include/csys/system.h | 356 +++++++++++++++++ include/csys/system.inl | 300 +++++++++++++++ include/imgui/imgui_console.cpp | 656 ++++++++++++++++++++++++++++++++ include/imgui/imgui_console.h | 112 ++++++ pléascach.cpp | 31 +- 31 files changed, 4526 insertions(+), 323 deletions(-) delete mode 100644 Scene/BSP.cpp delete mode 100644 Scene/BSP.hpp create mode 100644 Scene/March.hpp create mode 100644 include/csys/api.h create mode 100644 include/csys/argument_parser.h create mode 100644 include/csys/arguments.h create mode 100644 include/csys/autocomplete.h create mode 100644 include/csys/autocomplete.inl create mode 100644 include/csys/command.h create mode 100644 include/csys/csys.h create mode 100644 include/csys/exceptions.h create mode 100644 include/csys/history.h create mode 100644 include/csys/history.inl create mode 100644 include/csys/item.h create mode 100644 include/csys/item.inl create mode 100644 include/csys/script.h create mode 100644 include/csys/script.inl create mode 100644 include/csys/string.h create mode 100644 include/csys/system.h create mode 100644 include/csys/system.inl create mode 100644 include/imgui/imgui_console.cpp create mode 100644 include/imgui/imgui_console.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 45b6a13..f2f528f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,12 +38,13 @@ foreach(SHADER_SOURCE ${SHADER_SOURCE_FILES}) set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv") add_custom_command( OUTPUT ${SPIRV} - COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${SPIRV} ${SHADER_SOURCE} + COMMAND glslc -o ${SPIRV} ${SHADER_SOURCE} DEPENDS ${SHADER_SOURCE} ) list(APPEND SPIRV_BIN_FILES ${SPIRV}) endforeach(SHADER_SOURCE) + add_custom_target ( shaders DEPENDS ${SPIRV_BIN_FILES} diff --git a/Input/Input.cpp b/Input/Input.cpp index 3efb246..e6c15c9 100644 --- a/Input/Input.cpp +++ b/Input/Input.cpp @@ -74,7 +74,7 @@ void Input::setCursor(bool enabled) { } void Input::handleMovementKeys(Renderer& ren) { - if (ImGui::GetIO().WantCaptureKeyboard) + if (ImGui::GetIO().WantCaptureKeyboard && ren.in_menu) return; auto dir = ren.cam.dir(); @@ -139,16 +139,17 @@ void Input::handleCursorMovement(Renderer& ren, double x, double y) { int rel_mouse_y = static_cast(y) - last_mouse.y; auto& io = ImGui::GetIO(); - if (io.WantCaptureMouse) + if (io.WantCaptureMouse && ren.in_menu) return; - if (!ren.capture_mouse) { + if (ren.in_menu) { io.AddMousePosEvent(x, y); return; } - ren.cam.phi += rel_mouse_x / 100.0; - ren.cam.theta += rel_mouse_y / 100.0; + // scaling factor + ren.cam.phi += rel_mouse_x / 200.0; + ren.cam.theta += rel_mouse_y / 200.0; last_mouse = glm::vec2(x,y); diff --git a/Renderer/Renderer.cpp b/Renderer/Renderer.cpp index bf8363c..bad327c 100644 --- a/Renderer/Renderer.cpp +++ b/Renderer/Renderer.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include #include @@ -192,10 +194,42 @@ Renderer::Renderer(Window& win) : win(win) { uniform_buffer = std::make_unique(phys_dev, dev); - /* load map */ - bsp = std::make_unique("assets/maps/git.bsp"); + objects.reserve(2); + uint id = 0; + + objects.push_back(Object{ + .center = glm::vec4(0.0), + .dimensions = glm::vec4(1.0), + .id = id, + .shape = Shape::eSPHERE, + }); + + objects.push_back(Object{ + .center = glm::vec4(1.0), + .dimensions = glm::vec4(0.5), + .id = id, + .shape = Shape::eBOX, + }); + + + +#if 0 + for (const auto& plane : bsp->planes) { + /* for planes, center.xyz holds normal, dimensions.x holds distance */ + objects.push_back(Object{ + .center = glm::vec4(plane.norm, 0.0), + .dimensions = glm::vec4(plane.dist, 0.0, 0.0, 0.0), + .id = id, + .shape = Shape::ePLANE, + }); + id++; + } +#endif + + shader_buffer = std::make_unique(phys_dev, dev, MAX_OBJECTS); + + shader_buffer->upload(objects); - shader_buffer = std::make_unique(phys_dev, dev, bsp->planes.size()); textures = createResources({ "assets/textures/oil.jpg", @@ -224,21 +258,6 @@ Renderer::Renderer(Window& win) : win(win) { { { -1.0,-1.0 } }, }); - objects.reserve(bsp->planes.size()); - uint id = 0; - for (const auto& plane : bsp->planes) { - /* for planes, center.xyz holds normal, dimensions.x holds distance */ - objects.push_back(Object{ - .center = glm::vec4(plane.norm, 0.0), - .dimensions = glm::vec4(plane.dist, 0.0, 0.0, 0.0), - .id = id, - .shape = Shape::ePLANE, - }); - id++; - } - - shader_buffer->upload(objects); - pipeline = std::make_unique(dev, shaders, swapchain->extent, *render_pass, bindings, *vertex_buffer); pipeline->update(0, *uniform_buffer); @@ -349,7 +368,7 @@ void Renderer::draw() { .time = time, .viewport = glm::vec4(viewport.width, viewport.y, 0.0, 0.0), .cam_dir = cam.dir(), - .n_objects = 2, + .n_objects = static_cast(objects.size()), .rad = rad, }); @@ -406,7 +425,9 @@ void Renderer::present() { default: Log::error("Failed to present surface.\n"); break; + } + time += frametime / 1000.0 * speed * static_cast(!paused); frame++; } diff --git a/Renderer/Renderer.hpp b/Renderer/Renderer.hpp index e0aa57e..9b707b0 100644 --- a/Renderer/Renderer.hpp +++ b/Renderer/Renderer.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -20,6 +20,8 @@ struct UniformBuffer; struct VertexBuffer; struct Texture; +#define MAX_OBJECTS 256 + /* Contains all of the Vulkan objects involved in rendering the scene */ struct Renderer { Renderer(Window& win); @@ -55,8 +57,6 @@ struct Renderer { std::vector textures; - std::unique_ptr bsp; - uint32_t current_image_idx; uint64_t frame = 0; @@ -70,7 +70,18 @@ struct Renderer { bool flycam = false; /* time speed */ float time = 0.0; + float frametime = 0.0; + float fps = 0.0; + float max_fps = 60.0; float speed = 1.0; float rad = 1.0; + + bool in_menu = false; + bool should_close = false; + bool paused = false; bool running = true; + + std::map scene_map; + + std::string scene_file = "scene.txt"; }; diff --git a/Scene/BSP.cpp b/Scene/BSP.cpp deleted file mode 100644 index 0f00088..0000000 --- a/Scene/BSP.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include -#include - -#include - -using namespace Q3BSP; - -static inline void copy_data(void* file_data, std::string& dst, Lump& lump) { - dst.resize(lump.len); - std::memcpy(dst.data(), (u8*)file_data + (size_t)lump.offset, lump.len); -} - -template -static inline void copy_data(void* file_data, std::vector& dst, Lump& lump) { - Log::debug("Size: %zu (%zu)\n", lump.len/sizeof(T), lump.len); - dst.resize(lump.len / sizeof(T)); - puts("ALLOC'D"); - //Log::debug("%p %p\n", dst.data(), (u8*)file_data + lump.offset); - std::memcpy(dst.data(), ((u8*)file_data) + lump.offset, lump.len); -} - -BSP::BSP(const std::string& fname) : filename(fname) { - file_data = file::slurpb(fname); - Log::debug("File size: %zu\n", file_data.size()); - header = reinterpret_cast(file_data.data()); - - Log::info("Loading BSP: %s\n", fname.c_str()); - - if(header->magic != BSP_MAGIC) { - Log::error("BSP file missing magic!\n"); - } - - for (size_t i = 0; i < std::size(header->lumps); i++) { - Log::debug("%i: Offset: %u | Length: %u\n", i, header->lumps[i].offset, header->lumps[i].len); - Log::debug("\tPointer: 0x%p\n", (u8*)file_data.data() + (size_t)header->lumps[i].offset); - } - - copy_data(file_data.data(), entities, header->entities); - copy_data(file_data.data(), textures, header->textures); - copy_data(file_data.data(), planes, header->planes); - copy_data(file_data.data(), nodes, header->nodes); - copy_data(file_data.data(), leafs, header->leafs); - copy_data(file_data.data(), leaf_faces, header->leaf_faces); - copy_data(file_data.data(), leaf_brushes, header->leaf_brushes); - copy_data(file_data.data(), models, header->models); - copy_data(file_data.data(), brushes, header->brushes); - copy_data(file_data.data(), brush_sides, header->brush_sides); - copy_data(file_data.data(), vertices, header->vertices); - copy_data(file_data.data(), mesh_vertices, header->mesh_vertices); - copy_data(file_data.data(), effects, header->effects); - copy_data(file_data.data(), faces, header->faces); - copy_data(file_data.data(), lightmaps, header->lightmaps); - copy_data(file_data.data(), lightvols, header->lightvols); - - vis_info.sz_vectors = reinterpret_cast(file_data.data() + header->vis_info.offset)[1]; - auto sz = header->vis_info.len; - Log::debug("Size: %u\n", sz); - vis_info.vectors.resize(sz); - std::memcpy(vis_info.vectors.data(), file_data.data() + header->vis_info.offset + 2*sizeof(u32), sz); -} \ No newline at end of file diff --git a/Scene/BSP.hpp b/Scene/BSP.hpp deleted file mode 100644 index 7595592..0000000 --- a/Scene/BSP.hpp +++ /dev/null @@ -1,194 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -/* contains loading functions for Quake III-style BSPs */ -namespace Q3BSP { - struct Lump { - u32 offset; - u32 len; - }; - using rgb = glm::u8vec3; - using rgba = glm::u8vec4; - - /* "IBSP" */ - const uint32_t BSP_MAGIC = 0x50534249U; - struct Header { - u32 magic; - u32 version; - - union { - Lump lumps[17]; - struct { - Lump entities, - textures, - planes, - nodes, - leafs, - leaf_faces, - leaf_brushes, - models, - brushes, - brush_sides, - vertices, - mesh_vertices, - effects, - faces, - lightmaps, - lightvols, - vis_info; - }; - }; - }; - - struct Texture { - char name[64]; - /* values of unknown meaning - TODO: check darkplaces or some other 3rd party loader */ - i32 flags; - i32 contents; - }; - - struct Plane { - glm::vec3 norm; - float dist; - }; - - struct Node { - u32 plane; - /* negative numbers are leaf indices */ - i32 children[2]; - - /* bounding box coords (integer) */ - glm::ivec3 bb_mins; - glm::ivec3 bb_maxes; - }; - - struct Leaf { - i32 cluster_idx; - u32 area; - glm::ivec3 bb_mins; - glm::ivec3 bb_maxes; - i32 first_leaf_face_idx; - u32 n_leaf_faces; - i32 first_leaf_brush_idx; - u32 n_leaf_brushes; - }; - - - struct LeafFaces { - /* list of face indices (one list per leaf) */ - i32 face_idx; - }; - - struct LeafBrush { - /* list of brush indices (one list leaf) */ - i32 brush_idx; - }; - - struct Model { - glm::vec3 bb_mins; - glm::vec3 bb_maxes; - i32 first_face_idx; - u32 n_faces; - i32 first_brush_idx; - u32 n_brushes; - }; - - struct Brush { - i32 first_brushside_idx; - u32 n_brushsides; - i32 texture_idx; - }; - - struct BrushSide { - i32 plane_idx; - i32 texture_idx; - }; - - struct Vertex { - glm::vec3 position; - glm::vec2 tex_coords; - glm::vec2 lightmap_coords; - glm::vec3 normal; - glm::u8vec4 color; - }; - - struct MeshVertex { - i32 idx; - }; - - struct Effect { - char name[64]; - i32 brush_idx; - /* almost always 5 for some reason */ - i32 unknown; - }; - - struct Face { - i32 texture_idx; - /* -1 if no effect */ - i32 effect_idx; - enum FaceType { - ePOLYGON = 1, - ePATCH = 2, - eMESH = 3, - eBILLBOARD = 4, - } type; - i32 first_vertex_idx; - u32 n_vertices; - i32 first_mesh_vertex_idx; - u32 n_mesh_vertices; - i32 lightmap_idx; - glm::vec2 lightmap_start; - glm::vec2 lightmap_end; - - glm::vec3 lightmap_origin; - glm::vec3 lightmap_unit_vectors[2]; - glm::vec3 norm; - glm::ivec2 patch_dimensions; - }; - - struct Lightmap { - u8 map[128][128][3]; - }; - - struct Lightvol { - rgb ambient; - rgb directional; - /* spherical coordinates */ - glm::u8vec2 direction; - }; - - struct VisibilityInfo { - u32 sz_vectors; - std::vector vectors; - }; - - struct BSP { - Header* header; - BSP(const std::string& fname); - std::string filename; - std::vector file_data; - std::string entities; - std::vector textures; - std::vector planes; - std::vector nodes; - std::vector leafs; - std::vector leaf_brushes; - std::vector leaf_faces; - std::vector models; - std::vector brushes; - std::vector brush_sides; - std::vector vertices; - std::vector mesh_vertices; - std::vector effects; - std::vector faces; - std::vector lightmaps; - std::vector lightvols; - VisibilityInfo vis_info; - }; -} \ No newline at end of file diff --git a/Scene/March.hpp b/Scene/March.hpp new file mode 100644 index 0000000..b2868b0 --- /dev/null +++ b/Scene/March.hpp @@ -0,0 +1,168 @@ +#pragma once +/* file format for storing raymarched scene for dynamic SDF */ +#include +#include + +#include +#include + +namespace March { + static std::vector readFile(const std::string& fname, std::map& name_map) { + /* this should be rewritten once we are loading more than 250 objects */ + std::ifstream in(fname); + + std::vector ret; + Object tmp; + std::string name; + + std::string type; + int id = 0; + + while(in >> type) { + Log::info("Object Type: %s\n", type.c_str()); + char dummy; + in >> dummy; + if (dummy != '(') { + Log::error("Expected '(', found %c\n", dummy); + } + in >> tmp.center.x; + in >> tmp.center.y; + in >> tmp.center.z; + in >> dummy; + if (dummy != ')') { + Log::error("Expected ')', found %c\n", dummy); + } + + if (type == "SPHERE") { + tmp.shape = Shape::eSPHERE; + char dummy; + in >> dummy; + if (dummy != '(') { + Log::error("Expected '(', found %c\n", dummy); + } + in >> tmp.dimensions.x; + in >> dummy; + if (dummy != ')') { + Log::error("Expected ')', found %c\n", dummy); + } + } else if (type == "BOX") { + tmp.shape = Shape::eBOX; + char dummy; + in >> dummy; + if (dummy != '(') { + Log::error("Expected '(', found %c\n", dummy); + } + in >> tmp.dimensions.x; + in >> tmp.dimensions.y; + in >> tmp.dimensions.z; + in >> dummy; + if (dummy != ')') { + Log::error("Expected ')', found %c\n", dummy); + } + } else { + Log::error("Expected type name, but not recognized %s!\n", type.c_str()); + } + + in >> name; + if (name_map.find(name) == name_map.end()) + name_map[name] = id++; + + tmp.id = name_map[name]; + + ret.push_back(tmp); + } + + + return ret; + } + + static std::string find_name(const std::map& map, const int id, bool& found) { + found = false; + std::string name = "##"; + for (const auto& p : map) { + if (p.second == id) { + found = true; + name = p.first; + break; + } + } + + return name; + } + + static void add_sphere(const std::string& name, glm::vec3 center, float rad, Renderer* ren) { + unsigned int id = 0xDEADBEEF; + if (ren->scene_map.find(name) == ren->scene_map.end()) { + /* start iterating from the number of total objects, which will break immediately if stuff is deleted and new things added before saving, so please change this */ + id = ren->scene_map.size(); + ren->scene_map[name] = id; + } else { + id = ren->scene_map[name]; + } + + ren->objects.push_back(Object{ + .center = glm::vec4(center.x, center.y, center.z, 0.0), + .dimensions = glm::vec4(rad), + .id = id, + .shape = eSPHERE, + }); + } + + static void add_box(const std::string& name, glm::vec3 center, glm::vec3 dim, Renderer* ren) { + unsigned int id = 0xDEADBEEF; + if (ren->scene_map.find(name) == ren->scene_map.end()) { + /* start iterating from the number of total objects, which will break immediately if stuff is deleted and new things added before saving, so please change this */ + id = ren->scene_map.size(); + ren->scene_map[name] = id; + } else { + id = ren->scene_map[name]; + } + + ren->objects.push_back(Object{ + .center = glm::vec4(center.x, center.y, center.z, 0.0), + .dimensions = glm::vec4(dim.x, dim.y, dim.z, 0.0), + .id = id, + .shape = eBOX, + }); + } + + static void writeFile(const std::string& fname, const std::vector& objs, const std::map& map) { + std::ofstream out(fname); + const char* shape_names[] = { + "SPHERE", + "BOX", + "PLANE", + }; + + int anon = 0; + for (const auto& obj : objs) { + out << shape_names[obj.shape] << " ( " << obj.center.x << " " << obj.center.y << " " << obj.center.z << " ) ( "; + + switch (obj.shape) { + case eSPHERE: + out << obj.dimensions.x << " "; + break; + case eBOX: + out << obj.dimensions.x << " " + << obj.dimensions.y << " " + << obj.dimensions.z << " "; + break; + case ePLANE: + out << obj.dimensions.x << " " + << obj.dimensions.y << " " + << obj.dimensions.z << " "; + break; + } + + bool found = false; + auto name = find_name(map, obj.id, found); + + if (!found || name[0] == '#') { + name = std::string("#") + std::to_string(anon++); + } + out << ") " << name << std::endl; + } + out.close(); + } +}; + diff --git a/UI/UI.cpp b/UI/UI.cpp index 17dd7d1..5864259 100644 --- a/UI/UI.cpp +++ b/UI/UI.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -10,8 +11,36 @@ #include #include +#include -UI::UI(Renderer* ren) : info { .flycam = ren->flycam, .time = ren->time, .rad = ren->rad, .cam = ren->cam, .objects = ren->objects}, dev(ren->dev) { +/* this pains me to do, but its the only way :( */ +Renderer* __ren; + +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 scene_load(csys::String fname) { + __ren->objects = March::readFile(fname.m_String, __ren->scene_map); + + __ren->shader_buffer->upload(__ren->objects); +} + +static void scene_write(csys::String fname) { + March::writeFile(fname.m_String, __ren->objects, __ren->scene_map); +} + +static void add_sphere() { + +} + +UI::UI(Renderer* ren) : ren(ren) { + __ren = ren; IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -79,6 +108,66 @@ UI::UI(Renderer* ren) : info { .flycam = ren->flycam, .time = ren->time, .rad = 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->ren->paused = !this->ren->paused; + console->System().Log(csys::ItemType::eINFO) << "Paused: " << (this->ren->paused ? "True" : "False") << csys::endl; + }); + + console->System().RegisterCommand("quit", "Quits the engine", [this]() { + this->ren->should_close = true; + console->System().Log(csys::ItemType::eINFO) << "Quitting..." << csys::endl; + }); + + console->System().RegisterCommand("read_scene", "Adds elements from a scene file", [this](csys::String fname) { + console->System().Log(csys::ItemType::eINFO) << "Loading file: " << fname.m_String << csys::endl; + scene_load(fname); + }, csys::Arg("file-name")); + console->System().RegisterCommand("write_scene", "Writes elements to a scene file (destroys underlying data if present)", [this](csys::String fname) { + Log::info("Writing to " + fname.m_String + "\n"); + console->System().Log(csys::ItemType::eINFO) << "Writing to file: " << fname.m_String << "\n"; + scene_write(fname); + }, csys::Arg("file-name")); + + console->System().RegisterCommand("add_sphere", "Adds sphere to the scene.", [this](csys::String name, float cx, float cy, float cz, float rad) { + March::add_sphere(name.m_String, glm::vec3(cx, cy, cz), rad, __ren); + }, csys::Arg("Name"), csys::Arg("center.x"), csys::Arg("center.y"), csys::Arg("center.z"), csys::Arg("radius")); + + console->System().RegisterCommand("add_box", "Adds box to the scene.", [this](csys::String name, float cx, float cy, float cz, float dx, float dy, float dz) { + March::add_box(name.m_String, glm::vec3(cx, cy, cz), glm::vec3(dx, dy, dz), __ren); + }, csys::Arg("Name"), + csys::Arg("center.x"), csys::Arg("center.y"), csys::Arg("center.z"), + csys::Arg("dim.x"), csys::Arg("dim.y"), csys::Arg("dim.z")); + + console->System().RegisterCommand("list-vars", "List variables accessible from developer console", [this]() { + const std::vector names = { +// "show_bboxes", +// "visibility_testing", + "flycam", + "speed", + "max_fps", +// "wireframe", +// "backface_culling", + }; + + for (const auto& name : names) + console->System().Log(csys::ItemType::eINFO) << name << csys::endl; + }); + +// console->System().RegisterVariable("show_bboxes", ren->show_bboxes, csys::Arg("value")); +// console->System().RegisterVariable("visibility_testing", ren->visibility_testing, csys::Arg("value")); + console->System().RegisterVariable("flycam", ren->flycam, csys::Arg("value")); + console->System().RegisterVariable("speed", ren->speed, csys::Arg("value")); + console->System().RegisterVariable("max_fps", ren->max_fps, csys::Arg("value")); +// console->System().RegisterVariable("wireframe", ren->wireframe_mode, pipeline_setter); +// console->System().RegisterVariable("backface_culling", ren->backface_culling, pipeline_setter); + + console->System().Log(csys::ItemType::eINFO) << "Welcome to Pleascach!" << csys::endl; } void UI::newFrame() { @@ -86,25 +175,40 @@ void UI::newFrame() { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + ImGui::SetNextWindowBgAlpha(0.5f); ImGui::Begin("Rendering Info", nullptr); - ImGui::Text("FPS: %f", info.fps); - ImGui::Text("Time: %f", info.time); - ImGui::SliderFloat("Rad", &info.rad, 0.0, 3.0); - ImGui::SliderFloat("Theta", &info.cam.theta, 0.0, glm::pi()); - ImGui::SliderFloat("Phi", &info.cam.phi, 0.0, glm::two_pi()); + ImGui::Text("FPS: %f", ren->fps); + ImGui::Text("Time: %f", ren->time); + ImGui::SliderFloat("Rad", &ren->rad, 0.0, 3.0); + ImGui::SliderFloat("Theta", &ren->cam.theta, 0.0, glm::pi()); + ImGui::SliderFloat("Phi", &ren->cam.phi, 0.0, glm::two_pi()); - ImGui::SliderFloat("X", &info.cam.pos.x, -1000.0, 1000.0); - ImGui::SliderFloat("Y", &info.cam.pos.y, -1000.0, 1000.0); - ImGui::SliderFloat("Z", &info.cam.pos.z, -1000.0, 1000.0); + ImGui::SliderFloat("X", &ren->cam.pos.x, -1000.0, 1000.0); + ImGui::SliderFloat("Y", &ren->cam.pos.y, -1000.0, 1000.0); + ImGui::SliderFloat("Z", &ren->cam.pos.z, -1000.0, 1000.0); + if(ImGui::CollapsingHeader("Objects")) { - for(const auto& obj : info.objects) { - ImGui::Text("(%f %f %f) + %f", obj.center.x, obj.center.y, obj.center.z, obj.dimensions.x); + int anon = 0; + for (const auto& obj : ren->objects) { + bool found = false; + auto name = March::find_name(ren->scene_map, obj.id, found); + + if (!found || name[0] == '#') { + name = std::string("#") + std::to_string(anon++); + } + + ImGui::Text("(%f %f %f %f) (%f %f %f %f) \"%s\"", + obj.center.x, obj.center.y, obj.center.z, obj.center.w, + obj.dimensions.x, obj.dimensions.y, obj.dimensions.z, obj.dimensions.w, + name.c_str()); } } + if (ren->in_menu) + console->Draw(); ImGui::End(); } @@ -115,7 +219,8 @@ void UI::render(vk::CommandBuffer cmd) { } UI::~UI() { - dev.destroyDescriptorPool(desc_pool); + console.reset(); +// dev.destroyDescriptorPool(desc_pool); ImGui_ImplVulkan_Shutdown(); ImGui_ImplGlfw_Shutdown(); diff --git a/UI/UI.hpp b/UI/UI.hpp index a7188e7..8bb66d7 100644 --- a/UI/UI.hpp +++ b/UI/UI.hpp @@ -3,8 +3,11 @@ #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #include + #include +#include + #include struct Object; @@ -12,22 +15,14 @@ struct Renderer; struct Camera; struct UI { - struct UI_Info { - float fps = 0.0; - bool& flycam; - float& time; - /* for cantor */ - float& rad; - /* camera stuff */ - Camera& cam; - const std::vector& objects; - } info; - + Renderer* ren; vk::Device dev; vk::DescriptorPool desc_pool; UI(Renderer* ren); + std::unique_ptr console; + void newFrame(); void render(vk::CommandBuffer cmd); diff --git a/assets/shaders/ray.frag b/assets/shaders/ray.frag index 6f2755c..a806231 100644 --- a/assets/shaders/ray.frag +++ b/assets/shaders/ray.frag @@ -93,7 +93,8 @@ float terrain(vec3 p) { vec2 sdf(vec3 pos) { vec2 d = vec2(100000000.0, -1.0); for(uint i = 0; i < n_objects; i++) { - d = op_union(d, obj_to_sdf(pos, i), objects[n_objects].id); + d = op_union(d, obj_to_sdf(pos, i), 1.0); +// d = op_union(d, sphere(pos, objects[i].center.xyz, objects[i].dimensions.x), 1.0); } @@ -114,8 +115,8 @@ vec2 raycast(vec3 dir) { if(dt.y == -1.0) return dt; if(dt.x < 0.0001*t) - return vec2(t, dt.y); -// return float(i)/MAX_STEPS; +// return vec2(t, dt.y); + return vec2(t, float(i)/MAX_STEPS); else if(dt.x > 2000.0) return vec2(dt.x, -1.0); t += dt.x; @@ -150,7 +151,7 @@ void main() { vec3 p = d.x*dir + cam_pos; if(d.y != -1.0) - fragColor = vec4(d.y/n_objects); + fragColor = vec4(d.y); else fragColor = vec4(0.0); } \ No newline at end of file diff --git a/assets/shaders/ray.frag.spv b/assets/shaders/ray.frag.spv index 27da9c72759743f36911fc73c6571389d8f2a308..652a8c39ca274efe0ff3b82c601910e6f5e7a2c0 100644 GIT binary patch delta 2913 zcmeCM|6t3@%%sfDz`)4B&A`hLIFZ+#Ri2T7!F6NgXXg473=9kfiA9OI3=9mc3@i+% z(D-IuwnK!G zfuSffJ)?wyA&r5Rftf*)k%6JK0Hm3j0pzrT{9>@UAtM7rN@fw5Z46?AEn{IYVMKN` zGlMyZ50+zPa0OXbl$h?EpOaq%7Gq`z*lft^&1eWRT8;q%+!=hVT7%np~Ft9T)FqASdFlaNdfa%)|3=9qo3=AOodkhQ= zObiSR0#JF7{tpZc4C31_T7cAp_&*`$Il=f0j39wuE;p?LK3PX@62Ll5GD0X3dK?VkJ3?uVF zF%05cGO&Py!ibT9!H9u@!H$8M0b~}4Zwr+(Wn=)0f#g6b1;n?6hN~GP1A{&&OhFPL z1t5z-f{qNV44e!M3>J(K$CxlMFn|mMg_|WKL>$BiMXD9lf<6XTuw$$l85lrfAax*f zKncKxk%3_^0|P@D)EtmLTSf*3ka#0QJw&4&BLf3SGe`pD7?3YPVjx36jHa*Vg8GO1uW;p$iU#q$iVP}fr$ac z2Af^az`)>!#c+2FNG3}y_h44Zv;dpH$AWe+G)L5Tq5T~OJB%s)N(r`Z2UP^g1MPckqtY=A~N zNGFI7vJa%|7*rfoT7hzhB?CJ+8=YZbVBlk5U;wEHsRPM_^nlDcGkK$gwCY&~28JyR z3=9nntPIKw3=HQO7#KieO;8g-a_1(0l#u2<54Hav0~5pf$&!-h^`Np44JsEwe2@!3dO;Zn#0KdDr4vwyU1fko6^Q=Ez`}5yfq~%$sBB<> zgy>DE{7q0I1_ceM6uQO004|I-Les<@1_lO@7^qYNr2`QE8Uq8^Fi=#1*r0R(N~rf4 zFcazn1_lO@ILLfZ!hOiVz@Uee1|KmnFkE7&XJCNkfX56B3?M}yiAHFyc*4NI05S+9 z2l6Gz_aHG?R(!_5z@WpxzyJ~l$%8@*BnEOAC^bB1U|;}=At(M93=9k)@hu>af)epd z1_lO@AT0I2h6WYL*C4r93=9k)F_5o8Y>+%i9nA7~PkDEH=3}<14#36DpvNAF-fW%>rWMgDt0EvO35adV@ALK}oC7>Ju3Na8LM8nMG0GSJl ze`JF>85tNr5}+UhMF$rnD3vlWfD$B#&&|ld01^YK10^_676gfbECFRPkR>1*q#qRI zJdBWB1u7aqd|pNd29Ox6XpjZvTaX~g08oAf834+!AYbrJ4wcn51hsrX$qiIOfJy*R z%LkbcYWRToAb)`}qtN7yvcmPkj0_AyjF8F=WCDl}aug^liZU{Q+fzIY?BKdb99qA- zFfcHH-IuwnLne zfuSffJ)?wyA&r5Rftf*;k%6JK0Hm3j0pzrT{9>@UDI)_zN@fw5Z4P3CEn{J@U_^E_ zGlMmV50+zP@B~>_l$h?EpOaq%7Gq|J*lft^&1eWRT8;q%+!$c_{@wTdw#j-FtCEn5@cjx2xVYk*o34<60D}7-d=}+jR9ns z6jTvN0;Cw^7$rtf#6lco&A`F{;)DDN(hHJTfy#p%0kd2mB+0 zVIW5`LP9DWVi5yK3}iTnA2InZmv%iUW@XJDuYTL4l3vKS=j z$iT|L$-uy1!w7MV2?GNI$UsoI*)l@JL3~g=*g-AmV_*e4#-5RZ0VD=e2Qmkg02~+@ z81^zSFqA>f0qJvOWMBY^H!{>iG&(UdFn~0JBtVV<`4S`sG6dupXQ*SMp@x7QOThB2tFl>M(Mv%)ud^-kaD{x_Y3@QOCNI@Dc8Q8%&{vrbd z10Mqe14skN`5<|auR#V~oXja9t$K-pfnf^+149D?D+9P#oN1fTSW2{f&Wz;SK`>!(C7@#sG<bFff3` zK)wdCK|TUS!AGd!pvVNVLGmDVFv~wf<&jIMFANL}AaRfxpirp)1`Y-WhE`~%{0^;y zK;k<KXnrFff25 z0-y!&KL!Q{kVcR=$Y&rv$Y2IW1_qcxjEs( zhE&{v$@#c&Qr1_qEg%#oao3=AMKP?Uij3F3ns39nWMBY^ z!7PwRvp@mK0+3peJj?<`s2s?AkUEh0AP<7XK^|0LJKw= z0c1W%4yI3!ks%Nqcrc9yj0_BVpyml9B%OdX8Zt64fHZ^TVE!~h@+U|h6vZGh3kF68 E0J@z}+W-In 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..9ebe7bc --- /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(eERROR) << (m_Name.m_String + ": " + ae.what()); + } + return Item(eNONE); + } + + /*! + * \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(eERROR) << (m_Name.m_String + ": " + ae.what()); + } + + // Call function + m_Function(); + return Item(eNONE); + } + + /*! + * \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..d3743c5 --- /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 + { + eCOMMAND = 0, + eLOG, + eWARNING, + eERROR, + eINFO, + eNONE + }; + + struct CSYS_API Item + { + /*! + * \brief + * Create console item type + * \param type + * Item type to be stored + */ + explicit Item(ItemType type = ItemType::eLOG); + + /*! + * \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..58bec75 --- /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 eCOMMAND: + return s_Command.data() + m_Data; + case eLOG: + return '\t' + m_Data; + case eWARNING: + return s_Warning.data() + m_Data; + case eERROR: + return s_Error.data() + m_Data; + case eINFO: + return m_Data; + case eNONE: + 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..c0f0419 --- /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::eLOG); + + /*! + * \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(eERROR) << "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(eLOG) << 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(eLOG) << 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..7f53a21 --- /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