Compare commits

...

10 Commits

93 changed files with 5136 additions and 1977 deletions

View File

@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32) if(WIN32)
set(CMAKE_CXX_FLAGS "-D_DEBUG") set(CMAKE_CXX_FLAGS "-D_DEBUG")
else() else()
set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall") set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall -D_GLIBCXX_DEBUG -g -fsanitize=address")
endif() endif()
project(Pleascach) project(Pleascach)
@ -38,12 +38,13 @@ foreach(SHADER_SOURCE ${SHADER_SOURCE_FILES})
set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv") set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv")
add_custom_command( add_custom_command(
OUTPUT ${SPIRV} OUTPUT ${SPIRV}
COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${SPIRV} ${SHADER_SOURCE} COMMAND glslc -o ${SPIRV} ${SHADER_SOURCE}
DEPENDS ${SHADER_SOURCE} DEPENDS ${SHADER_SOURCE}
) )
list(APPEND SPIRV_BIN_FILES ${SPIRV}) list(APPEND SPIRV_BIN_FILES ${SPIRV})
endforeach(SHADER_SOURCE) endforeach(SHADER_SOURCE)
add_custom_target ( add_custom_target (
shaders shaders
DEPENDS ${SPIRV_BIN_FILES} DEPENDS ${SPIRV_BIN_FILES}

View File

@ -74,26 +74,26 @@ void Input::setCursor(bool enabled) {
} }
void Input::handleMovementKeys(Renderer& ren) { void Input::handleMovementKeys(Renderer& ren) {
if (ImGui::GetIO().WantCaptureKeyboard) if (ImGui::GetIO().WantCaptureKeyboard && ren.in_menu)
return; return;
auto dir = ren.cam.dir();
glm::vec3 forward; glm::vec3 forward;
if (ren.flycam) if (ren.flycam)
forward = glm::vec3(glm::sin(ren.cam.theta)*glm::cos(ren.cam.phi), glm::cos(ren.cam.theta), glm::sin(ren.cam.theta)*glm::sin(ren.cam.phi)); forward = dir;
else else
forward = glm::vec3(glm::cos(ren.cam.phi), 0.0, glm::sin(ren.cam.phi)); forward = glm::normalize(glm::vec3(dir.x, 0.0, dir.z));
const auto right = glm::cross(forward, glm::vec3(0.0, 1.0, 0.0));
const auto right = glm::normalize(glm::cross(forward, glm::vec3(0.0, 1.0, 0.0)));
const auto up = glm::normalize(glm::cross(right, forward));
const auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f; const auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f;
if(glfwGetKey(in, GLFW_KEY_UP)) { if(glfwGetKey(in, GLFW_KEY_UP)) {
ren.cam.theta -= 0.01; ren.cam.theta -= 0.02;
}
if(glfwGetKey(in, GLFW_KEY_UP)) {
ren.cam.theta -= 0.01;
} }
if(glfwGetKey(in, GLFW_KEY_DOWN)) { if(glfwGetKey(in, GLFW_KEY_DOWN)) {
ren.cam.theta += 0.01; ren.cam.theta += 0.02;
} }
if(glfwGetKey(in, GLFW_KEY_LEFT)) { if(glfwGetKey(in, GLFW_KEY_LEFT)) {
@ -139,16 +139,17 @@ void Input::handleCursorMovement(Renderer& ren, double x, double y) {
int rel_mouse_y = static_cast<int>(y) - last_mouse.y; int rel_mouse_y = static_cast<int>(y) - last_mouse.y;
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();
if (io.WantCaptureMouse) if (io.WantCaptureMouse && ren.in_menu)
return; return;
if (!ren.capture_mouse) { if (ren.in_menu) {
io.AddMousePosEvent(x, y); io.AddMousePosEvent(x, y);
return; return;
} }
ren.cam.phi += rel_mouse_x / 100.0; // scaling factor
ren.cam.theta += rel_mouse_y / 100.0; ren.cam.phi += rel_mouse_x / 200.0;
ren.cam.theta += rel_mouse_y / 200.0;
last_mouse = glm::vec2(x,y); last_mouse = glm::vec2(x,y);

View File

@ -1,160 +0,0 @@
#include <Model/Model.hpp>
#include <util/log.hpp>
#include <util/file.hpp>
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <tinygltf/tiny_gltf.h>
#include <glm/gtc/type_ptr.hpp>
Model::Model(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname) {
Log::debug("Loading model " + fname + "\n");
tinygltf::TinyGLTF loader;
std::string err, warn;
model = std::make_shared<tinygltf::Model>();
auto ret = loader.LoadASCIIFromFile(model.get(), &err, &warn, fname);
if(!warn.empty()) {
Log::debug(fname + ": " + warn);
}
if(!err.empty()) {
Log::debug(fname + ": " + err);
}
if(!ret) {
Log::error("Failed to pase glTF model\n");
}
for(auto& node: model->nodes)
initNode(node, nullptr);
/* vertex, index buffer should be populated now */
Log::debug("%zu vertices loaded from model " + fname + "\n", vertices.size());
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, vertices.size());
vertex_buffer->upload(vertices);
index_buffer = std::make_unique<Buffer>(phys_dev, dev, indices.size()*sizeof(uint16_t),
vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
index_buffer->upload(reinterpret_cast<uint8_t*>(indices.data()), static_cast<vk::DeviceSize>(indices.size()*sizeof(uint16_t)));
}
void Model::initVertices(Node* node, const tinygltf::Primitive& prim) {
Log::debug("Loading vertices\n");
uint32_t first_idx = indices.size();
uint32_t vert_start = vertices.size();
uint32_t idx_count = 0;
const float* pos_buff = nullptr;
const float* norm_buff = nullptr;
const float* uv_buff = nullptr;
size_t vertex_count = 0;
auto loc = prim.attributes.find("POSITION");
if(loc != prim.attributes.end()) {
auto accessor = model->accessors[loc->second];
const auto& view = model->bufferViews[accessor.bufferView];
pos_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
vertex_count = accessor.count;
}
loc = prim.attributes.find("NORMAL");
if(loc != prim.attributes.end()) {
auto accessor = model->accessors[loc->second];
const auto& view = model->bufferViews[accessor.bufferView];
norm_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
}
loc = prim.attributes.find("TEXCOORD_0");
if(loc != prim.attributes.end()) {
auto accessor = model->accessors[loc->second];
const auto& view = model->bufferViews[accessor.bufferView];
uv_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
}
for(size_t i = 0; i < vertex_count; i++) {
vertices.push_back(Vertex {
.pos = pos_buff? glm::make_vec3(pos_buff+i*3) : glm::vec3(0.0),
.norm = norm_buff? glm::normalize(glm::make_vec3(norm_buff+i*3)) : glm::vec3(0.0),
.uv = uv_buff? glm::make_vec2(uv_buff+i*2) : glm::vec2(0.0),
.color = glm::vec3(0.0),
});
}
Log::debug("Finished loading %zu vertices\n", vertex_count);
const auto& accessor = model->accessors[prim.indices];
const auto& view = model->bufferViews[accessor.bufferView];
const auto& buffer = model->buffers[view.buffer];
idx_count += accessor.count;
switch(accessor.componentType) {
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT:
for(size_t i = 0; i < accessor.count; i++)
indices.push_back(
reinterpret_cast<const uint32_t*>(
&buffer.data[accessor.byteOffset+view.byteOffset
])[i]
+ vert_start
);
break;
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT:
for(size_t i = 0; i < accessor.count; i++)
indices.push_back(
reinterpret_cast<const uint16_t*>(
&buffer.data[accessor.byteOffset+view.byteOffset
])[i]
+ vert_start
);
break;
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE:
for(size_t i = 0; i < accessor.count; i++)
indices.push_back(
reinterpret_cast<const uint8_t*>(
&buffer.data[accessor.byteOffset+view.byteOffset
])[i]
+ vert_start
);
break;
default:
Log::error("Unrecognized index type in model\n");
break;
}
Log::debug("Loaded %zu indices of type %d\n", accessor.count, (accessor.componentType-TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE)/2+1);
node->mesh.push_back(Primitive {
.first_idx = first_idx,
.idx_count = idx_count,
});
}
void Model::initNode(const tinygltf::Node& node, Node* parent, int level) {
Log::debug("%*sNode:"+node.name+"\n", level*4, " ");
Node* ret = new Node;
ret->parent = parent;
/* load kids */
for(auto& child_idx : node.children) {
initNode(model->nodes[child_idx], ret, level+1);
}
if(node.mesh >= 0) {
const auto& mesh = model->meshes[node.mesh];
for(auto& prim : mesh.primitives) {
initVertices(ret, prim);
}
}
if(ret->parent)
ret->parent->children.push_back(ret);
else
nodes.push_back(ret);
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <Memory/Buffer.hpp>
#include <Renderer/VertexBuffer.hpp>
#include <memory>
#include <glm/glm.hpp>
#include <tinygltf/tiny_gltf.h>
struct Model {
struct Primitive {
uint32_t first_idx;
uint32_t idx_count;
uint32_t mat_idx;
};
using Mesh = std::vector<Primitive>;
struct Node {
Node* parent;
std::vector<Node*> children;
Mesh mesh;
glm::mat4 mat;
~Node() {
for(auto& n : children)
delete n;
}
};
Model(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname);
std::shared_ptr<tinygltf::Model> model;
void initVertices(Node*, const tinygltf::Primitive&);
std::unique_ptr<VertexBuffer> vertex_buffer;
std::unique_ptr<Buffer> index_buffer;
std::vector<Vertex> vertices;
std::vector<uint16_t> indices;
/* recusively initialize nodes with an accumulative vertex and index buffer collector */
void initNode(const tinygltf::Node& node, Node* parent, int level = 0);
std::vector<Node*> nodes;
~Model() {
for(auto& n : nodes)
delete n;
vertex_buffer.reset();
index_buffer.reset();
}
};

View File

@ -1,15 +1,12 @@
# Pléascach # Pléascach - Dronuilleog
No models, everything RAYMARCHED!!!!
## Short Term Changes
- Make index buffer device-local instead of host-coherent
- Possibly restructure Buffer class to use templates to change
constructor and functions to use staging buffers if needed.
## Constant Improvements ## Constant Improvements
- Add more comments - Add more comments
## Long Term Improvements ## Long Term Improvements
- ~~Properly query surface to find supported formats for surfaces~~ - ~~Properly query surface to find supported formats for surfaces~~
- Fix all this cleanup vs destructor NONSENSE (inconsistency) - Fix all this cleanup vs destructor NONSENSE (inconsistency)
- Add pipeline caching
- Make more robust solution to window minimization (flushing out Input system should provide candidates) - Make more robust solution to window minimization (flushing out Input system should provide candidates)
- Command buffer per swapchain image - Command buffer per swapchain image
- Make smaller memory allocation object to pass around instead of directly passing physical device to everything - Make smaller memory allocation object to pass around instead of directly passing physical device to everything

View File

@ -2,10 +2,6 @@
#include <Renderer/Pipeline.hpp> #include <Renderer/Pipeline.hpp>
#include <Renderer/VertexBuffer.hpp> #include <Renderer/VertexBuffer.hpp>
#include <Scene/Terrain.hpp>
#include <Model/Model.hpp>
#include <Memory/Buffer.hpp> #include <Memory/Buffer.hpp>
#include <Memory/Image.hpp> #include <Memory/Image.hpp>
@ -77,16 +73,6 @@ void CommandBuffer::bind(const VertexBuffer& vertex_buffer, uint32_t binding) {
command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets); command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets);
} }
void CommandBuffer::bind(std::shared_ptr<Model> model) {
bind(*model->vertex_buffer);
command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16);
}
void CommandBuffer::bind(Terrain* terrain) {
bind(*terrain->vertex_buffer);
command_buffer.bindIndexBuffer(*terrain->index_buffer, 0, vk::IndexType::eUint32);
}
void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance); command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
} }
@ -97,7 +83,6 @@ void CommandBuffer::end() {
void CommandBuffer::recycle() { void CommandBuffer::recycle() {
command_buffer.reset(); command_buffer.reset();
} }
void CommandBuffer::cleanup(vk::Device dev) { void CommandBuffer::cleanup(vk::Device dev) {

View File

@ -31,8 +31,6 @@ struct CommandBuffer {
void bind(const GraphicsPipeline& pipeline); void bind(const GraphicsPipeline& pipeline);
void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets); void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets);
void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0); void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0);
void bind(std::shared_ptr<Model> model);
void bind(Terrain* terrain);
void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0); void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0);

View File

@ -2,6 +2,7 @@
#include <Renderer/Shader.hpp> #include <Renderer/Shader.hpp>
#include <Renderer/RenderPass.hpp> #include <Renderer/RenderPass.hpp>
#include <Renderer/UniformBuffer.hpp> #include <Renderer/UniformBuffer.hpp>
#include <Renderer/ShaderBuffer.hpp>
#include <Renderer/VertexBuffer.hpp> #include <Renderer/VertexBuffer.hpp>
#include <Resources/Texture.hpp> #include <Resources/Texture.hpp>
@ -9,7 +10,7 @@
#include <util/log.hpp> #include <util/log.hpp>
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer, enum Type type) : dev(dev) { GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer) : dev(dev) {
/* create layout /* create layout
* Eventually add a graphicspipline constructor that allows specification of layouts etc * Eventually add a graphicspipline constructor that allows specification of layouts etc
* kinda like how Image::Image has all those versions * kinda like how Image::Image has all those versions
@ -75,27 +76,18 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
}; };
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{ const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
.topology = type == Type::eGLTF ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::ePatchList, .topology = vk::PrimitiveTopology::eTriangleList,
/* matters later if we use strip primitives */ /* matters later if we use strip primitives */
.primitiveRestartEnable = vk::False, .primitiveRestartEnable = vk::False,
}; };
const vk::PipelineTessellationStateCreateInfo* ptesselation_info = nullptr; const vk::PipelineTessellationStateCreateInfo* ptesselation_info = nullptr;
const auto tess_info = vk::PipelineTessellationStateCreateInfo {
/* quads*/
.patchControlPoints = 4,
};
if(type == Type::eTERRAIN) {
ptesselation_info = &tess_info;
}
const auto raster_info = vk::PipelineRasterizationStateCreateInfo { const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
.depthClampEnable = vk::False, .depthClampEnable = vk::False,
.polygonMode = type == Type::eGLTF? vk::PolygonMode::eFill : vk::PolygonMode::eFill, .polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone, .cullMode = vk::CullModeFlagBits::eNone,
.frontFace = Type::eGLTF ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise, .frontFace = vk::FrontFace::eCounterClockwise,
.depthBiasEnable = vk::False, .depthBiasEnable = vk::False,
.lineWidth = 1.0, .lineWidth = 1.0,
}; };
@ -106,8 +98,8 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
}; };
const auto depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo{ const auto depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo{
.depthTestEnable = vk::True, .depthTestEnable = vk::False,
.depthWriteEnable = vk::True, .depthWriteEnable = vk::False,
.depthCompareOp = vk::CompareOp::eLessOrEqual, .depthCompareOp = vk::CompareOp::eLessOrEqual,
.depthBoundsTestEnable = vk::False, .depthBoundsTestEnable = vk::False,
.stencilTestEnable = vk::False, .stencilTestEnable = vk::False,
@ -200,6 +192,21 @@ void GraphicsPipeline::update(uint32_t binding, const UniformBuffer& uni) {
}, nullptr); }, nullptr);
} }
void GraphicsPipeline::update(uint32_t binding, const ShaderBuffer& ssbo) {
auto buff_info = vk::DescriptorBufferInfo{
.buffer = ssbo,
.offset = 0,
.range = vk::WholeSize,
};
dev.updateDescriptorSets(vk::WriteDescriptorSet {
.dstSet = desc_set,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &buff_info,
}, nullptr);
}
void GraphicsPipeline::update(uint32_t binding, const Texture& tex) { void GraphicsPipeline::update(uint32_t binding, const Texture& tex) {
auto tex_info = vk::DescriptorImageInfo { auto tex_info = vk::DescriptorImageInfo {
.sampler = tex.sampler, .sampler = tex.sampler,

View File

@ -4,21 +4,17 @@
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
struct Shader; struct Shader;
struct ShaderBuffer;
struct UniformBuffer; struct UniformBuffer;
struct VertexBuffer; struct VertexBuffer;
struct RenderPass; struct RenderPass;
struct Texture; struct Texture;
struct GraphicsPipeline { struct GraphicsPipeline {
enum Type {
eGLTF,
eTERRAIN,
};
GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders,
const vk::Extent2D& extent, const RenderPass& render_pass, const vk::Extent2D& extent, const RenderPass& render_pass,
vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings,
const VertexBuffer& vertex_buffer, enum Type type = Type::eGLTF); const VertexBuffer& vertex_buffer);
vk::Device dev; vk::Device dev;
vk::Pipeline pipeline; vk::Pipeline pipeline;
vk::PipelineLayout layout; vk::PipelineLayout layout;
@ -32,6 +28,7 @@ struct GraphicsPipeline {
/* create overload for every type of object we need to update */ /* create overload for every type of object we need to update */
void update(uint32_t binding, const UniformBuffer& uni); void update(uint32_t binding, const UniformBuffer& uni);
void update(uint32_t binding, const ShaderBuffer& ssbo);
void update(uint32_t binding, const Texture& tex); void update(uint32_t binding, const Texture& tex);
~GraphicsPipeline(); ~GraphicsPipeline();

View File

@ -15,12 +15,12 @@
#include <Renderer/UniformBuffer.hpp> #include <Renderer/UniformBuffer.hpp>
#include <Renderer/VertexBuffer.hpp> #include <Renderer/VertexBuffer.hpp>
#include <Scene/March.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <UI/UI.hpp> #include <UI/UI.hpp>
using namespace std::string_literals; using namespace std::string_literals;
Renderer::Renderer(Window& win) : win(win) { Renderer::Renderer(Window& win) : win(win) {
@ -194,54 +194,78 @@ Renderer::Renderer(Window& win) : win(win) {
uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev); uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev);
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<ShaderBuffer>(phys_dev, dev, MAX_OBJECTS);
shader_buffer->upload(objects);
textures = createResources({ textures = createResources({
"assets/textures/oil.jpg", "assets/textures/oil.jpg",
"assets/textures/eire.png",
}); });
std::vector<Shader> shaders = { std::vector<Shader> shaders = {
{dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex }, {dev, "assets/shaders/ray.vert.spv", vk::ShaderStageFlagBits::eVertex },
{ dev, "assets/shaders/fraglight.geom.spv", vk::ShaderStageFlagBits::eGeometry }, { dev, "assets/shaders/ray.frag.spv", vk::ShaderStageFlagBits::eFragment },
{ dev, "assets/shaders/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment },
}; };
std::vector<vk::DescriptorSetLayoutBinding> bindings = { std::vector<vk::DescriptorSetLayoutBinding> bindings = {
uniform_buffer->binding(0), uniform_buffer->binding(0),
textures[0].binding(1), textures[0].binding(1),
shader_buffer->binding(2),
}; };
/* initialize models */ vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, 6);
Timer model_timer;
models.push_back(std::make_shared<Model>(phys_dev, dev, "assets/models/dragon.gltf"));
auto t = model_timer.stop();
Log::debug("Models loaded in %lf milliseconds\n", model_timer.read()); /* simple quad */
vertex_buffer->upload(std::vector<Vertex> {
{ { -1.0, -1.0 } },
{ { -1.0, 1.0 } },
{ { 1.0, 1.0 } },
{ { 1.0, 1.0 } },
{ { 1.0,-1.0 } },
{ { -1.0,-1.0 } },
});
pipeline = std::make_unique<GraphicsPipeline>(dev, shaders, swapchain->extent, *render_pass, bindings, *models[0]->vertex_buffer); pipeline = std::make_unique<GraphicsPipeline>(dev, shaders, swapchain->extent, *render_pass, bindings, *vertex_buffer);
pipeline->update(0, *uniform_buffer); pipeline->update(0, *uniform_buffer);
pipeline->update(1, textures[1]); pipeline->update(1, textures[0]);
pipeline->update(2, *shader_buffer);
/* create Terrain */
terrain = std::make_unique<Terrain>(phys_dev, dev, textures[1]);
std::vector<Shader> terrain_shaders = {
{ dev, "assets/shaders/terrain.vert.spv", vk::ShaderStageFlagBits::eVertex },
{ dev, "assets/shaders/terrain.tesc.spv", vk::ShaderStageFlagBits::eTessellationControl },
{ dev, "assets/shaders/terrain.tese.spv", vk::ShaderStageFlagBits::eTessellationEvaluation },
{ dev, "assets/shaders/terrain.frag.spv", vk::ShaderStageFlagBits::eFragment },
};
terrain_pipeline = std::make_unique<GraphicsPipeline>(dev, terrain_shaders, swapchain->extent, *render_pass, bindings, *terrain->vertex_buffer, GraphicsPipeline::eTERRAIN);
terrain_pipeline->update(0, *uniform_buffer);
terrain_pipeline->update(1, textures[1]);
for (auto& shader : shaders) for (auto& shader : shaders)
shader.cleanup(); shader.cleanup();
for (auto& shader : terrain_shaders)
shader.cleanup();
ui = std::make_unique<UI>(this); ui = std::make_unique<UI>(this);
} }
@ -339,30 +363,28 @@ void Renderer::draw() {
auto sz = win.getDimensions(); auto sz = win.getDimensions();
const auto p = glm::perspective(glm::radians(90.0f), static_cast<float>(sz.width) / static_cast<float>(sz.height), 0.01f, 2000.0f); /* re-upload objects if out-of-sync */
if(uniform_buffer->data_copy.n_objects != objects.size())
shader_buffer->upload(objects);
auto uni = UniformData{ uniform_buffer->upload(UniformData{
.view = cam.view(),
.proj = p,
.time = time,
.cam_pos = cam.pos, .cam_pos = cam.pos,
.time = time,
.viewport = glm::vec4(viewport.width, viewport.y, 0.0, 0.0),
.cam_dir = cam.dir(), .cam_dir = cam.dir(),
.viewport = glm::vec2(viewport.width, viewport.y), .n_objects = static_cast<unsigned int>(objects.size()),
.tess_factor = tess_factor, .rad = rad,
.tess_edge_size = tess_edge_size, });
};
std::memcpy(uni.frustum, frustum(p * uni.view).data(), sizeof(uni.frustum));
uniform_buffer->upload(uni);
command_buffer->bind(*terrain_pipeline); command_buffer->bind(*pipeline);
command_buffer->command_buffer.setViewport(0, viewport); command_buffer->command_buffer.setViewport(0, viewport);
command_buffer->command_buffer.setScissor(0, scissor); command_buffer->command_buffer.setScissor(0, scissor);
command_buffer->bind(pipeline->layout, pipeline->desc_set);
command_buffer->bind(terrain_pipeline->layout, terrain_pipeline->desc_set); command_buffer->bind(*vertex_buffer);
command_buffer->bind(terrain.get()); //shader_buffer->objects[0].center.y += glm::sin(time)/10.0;
command_buffer->command_buffer.drawIndexed(terrain->indices.size(), 1, 0, 0, 0); command_buffer->command_buffer.draw(6, 1, 0, 0);
/* draw User Interface stuff */ /* draw User Interface stuff */
ui->newFrame(); ui->newFrame();
@ -373,7 +395,6 @@ void Renderer::draw() {
command_buffer->end(); command_buffer->end();
vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput; vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput;
/* submit our command buffer to the queue */ /* submit our command buffer to the queue */
@ -410,10 +431,11 @@ void Renderer::present() {
default: default:
Log::error("Failed to present surface.\n"); Log::error("Failed to present surface.\n");
break; break;
} }
time += frametime / 1000.0 * speed * static_cast<float>(!paused);
frame++; frame++;
time += 0.0167f * speed * static_cast<float>(running);
} }
Renderer::~Renderer() { Renderer::~Renderer() {
@ -421,17 +443,11 @@ Renderer::~Renderer() {
ui.reset(); ui.reset();
for(auto& model : models)
model.reset();
terrain.reset();
uniform_buffer.reset(); uniform_buffer.reset();
shader_buffer.reset();
vertex_buffer.reset(); vertex_buffer.reset();
terrain_pipeline.reset();
pipeline.reset(); pipeline.reset();
for (auto& tex : textures) { for (auto& tex : textures) {
tex.cleanup(); tex.cleanup();
} }

View File

@ -5,14 +5,13 @@
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
#include <Renderer/Swapchain.hpp>
#include <Renderer/CommandBuffer.hpp> #include <Renderer/CommandBuffer.hpp>
#include <Renderer/RenderPass.hpp> #include <Renderer/RenderPass.hpp>
#include <Renderer/ShaderBuffer.hpp>
#include <Renderer/Swapchain.hpp>
#include <map>
#include <Scene/Camera.hpp> #include <Scene/Camera.hpp>
#include <Scene/Terrain.hpp>
#include <Model/Model.hpp>
#include <UI/UI.hpp> #include <UI/UI.hpp>
@ -21,6 +20,8 @@ struct UniformBuffer;
struct VertexBuffer; struct VertexBuffer;
struct Texture; struct Texture;
#define MAX_OBJECTS 256
/* Contains all of the Vulkan objects involved in rendering the scene */ /* Contains all of the Vulkan objects involved in rendering the scene */
struct Renderer { struct Renderer {
Renderer(Window& win); Renderer(Window& win);
@ -48,31 +49,39 @@ struct Renderer {
std::unique_ptr<CommandBuffer> command_buffer; std::unique_ptr<CommandBuffer> command_buffer;
std::unique_ptr<RenderPass> render_pass; std::unique_ptr<RenderPass> render_pass;
std::unique_ptr<GraphicsPipeline> pipeline; std::unique_ptr<GraphicsPipeline> pipeline;
std::unique_ptr<GraphicsPipeline> terrain_pipeline; /* just holds single quad */
std::unique_ptr<ShaderBuffer> shader_buffer;
std::unique_ptr<VertexBuffer> vertex_buffer; std::unique_ptr<VertexBuffer> vertex_buffer;
std::unique_ptr<UniformBuffer> uniform_buffer; std::unique_ptr<UniformBuffer> uniform_buffer;
std::unique_ptr<Terrain> terrain;
std::vector<Texture> textures; std::vector<Texture> textures;
uint32_t current_image_idx; uint32_t current_image_idx;
uint64_t frame = 0; uint64_t frame = 0;
std::vector<std::shared_ptr<Model>> models; std::vector<Object> objects;
std::unique_ptr<UI> ui; std::unique_ptr<UI> ui;
Camera cam{ .pos = glm::vec3(0.0, 5.0, 0.0), }; Camera cam{ .pos = glm::vec3(0.0, 0.0, -1.0), };
bool capture_mouse = false; bool capture_mouse = false;
bool flycam = false; bool flycam = false;
/* time speed */ /* time speed */
float time = 0.0; float time = 0.0;
float frametime = 0.0;
float fps = 0.0;
float max_fps = 60.0;
float speed = 1.0; float speed = 1.0;
float rad = 1.0;
bool in_menu = false;
bool should_close = false;
bool paused = false;
bool running = true; bool running = true;
float tess_factor = 1.8f; std::map<std::string, int> scene_map;
float tess_edge_size = 20.0f;
std::string scene_file = "scene.txt";
}; };

View File

@ -0,0 +1,3 @@
#include <Renderer/ShaderBuffer.hpp>

54
Renderer/ShaderBuffer.hpp Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <memory>
#include <util/glsl_types.hpp>
#include <Scene/Object.hpp>
#include <Memory/Buffer.hpp>
using namespace glsl;
/* Wrapper for SSBO */
struct ShaderBuffer {
ShaderBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, const size_t n_objects = 0x1000) : phys_dev(phys_dev), dev(dev), n_objects(n_objects) {
buffer = std::make_unique<Buffer>(
phys_dev, dev, n_objects * sizeof(Object),
vk::BufferUsageFlagBits::eStorageBuffer,
vk::MemoryPropertyFlagBits::eHostCoherent
| vk::MemoryPropertyFlagBits::eHostVisible
);
objects = reinterpret_cast<Object*>(buffer->p);
}
vk::PhysicalDevice phys_dev;
vk::Device dev;
size_t n_objects;
Object* objects;
std::unique_ptr<Buffer> buffer;
operator vk::Buffer&() const {
return *buffer;
}
inline vk::DescriptorSetLayoutBinding binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eFragment) {
return vk::DescriptorSetLayoutBinding {
.binding = binding,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = stages,
.pImmutableSamplers = nullptr,
};
}
inline void upload(const std::vector<Object>& scene) {
buffer->upload(reinterpret_cast<const uint8_t*>(scene.data()), scene.size() * sizeof(Object));
}
~ShaderBuffer() {
buffer.reset();
}
};

View File

@ -8,19 +8,9 @@ UniformBuffer::UniformBuffer(vk::PhysicalDevice phys_dev, vk::Device dev) {
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible
); );
Log::debug("Offset of view: %zu\n", offsetof(UniformData, view));
Log::debug("Offset of proj: %zu\n", offsetof(UniformData, proj));
Log::debug("Offset of time: %zu\n", offsetof(UniformData, time));
Log::debug("Offset of pad0: %zu\n", offsetof(UniformData, pad0));
Log::debug("Offset of pad1: %zu\n", offsetof(UniformData, pad1));
Log::debug("Offset of pad2: %zu\n", offsetof(UniformData, pad2));
Log::debug("Offset of cam_pos: %zu\n", offsetof(UniformData, cam_pos));
Log::debug("Offset of viewport: %zu\n", offsetof(UniformData, viewport));
Log::debug("Offset of tess_factor: %zu\n", offsetof(UniformData, tess_factor));
Log::debug("Offset of tess_edge_size: %zu\n", offsetof(UniformData, tess_edge_size));
} }
void UniformBuffer::upload(const UniformData& data) { void UniformBuffer::upload(const UniformData& data) {
data_copy = data;
buffer->upload(reinterpret_cast<const uint8_t*>(&data), sizeof(UniformData)); buffer->upload(reinterpret_cast<const uint8_t*>(&data), sizeof(UniformData));
} }

View File

@ -9,43 +9,36 @@
#include <memory> #include <memory>
#include <util/glsl_types.hpp>
using namespace glsl;
/* Uniform: /* Uniform:
* *
* layout (set = 0, binding = 0) uniform Matrices { layout (set = 0, binding = 0) uniform Matrices {
* mat4 view; vec3 cam_pos;
* mat4 proj; float time;
* float time; vec4 viewport;
* vec3 cam_pos; vec3 cam_dir;
* vec3 cam_dir; uint n_objects;
* vec4 frustum[6]; float rad;
* vec2 viewport; };
* float tess_factor;
* float tess_edge_size;
* };
*
*/ */
struct UniformData { struct UniformData {
glm::mat4 view; vec3 cam_pos;
glm::mat4 proj;
float time; float time;
float pad0; vec4 viewport;
float pad1; vec3 cam_dir;
float pad2; uint n_objects;
glm::vec3 cam_pos; float rad;
float pad3;
glm::vec3 cam_dir;
float pad4;
glm::vec4 frustum[6];
glm::vec2 viewport;
float tess_factor;
float tess_edge_size;
}; };
struct UniformBuffer { struct UniformBuffer {
UniformBuffer(vk::PhysicalDevice phys_dev, vk::Device dev); UniformBuffer(vk::PhysicalDevice phys_dev, vk::Device dev);
std::unique_ptr<Buffer> buffer; std::unique_ptr<Buffer> buffer;
UniformData data_copy { 0.0 };
void upload(const UniformData& data); void upload(const UniformData& data);

View File

@ -9,10 +9,7 @@
#include <tinygltf/tiny_gltf.h> #include <tinygltf/tiny_gltf.h>
struct Vertex { struct Vertex {
glm::vec3 pos; glm::vec2 pos;
glm::vec3 norm;
glm::vec2 uv;
glm::vec3 color;
}; };
struct VertexBuffer { struct VertexBuffer {
@ -37,24 +34,9 @@ struct VertexBuffer {
{ {
.location = 0, .location = 0,
.binding = binding, .binding = binding,
.format = vk::Format::eR32G32B32Sfloat,
.offset = offsetof(Vertex, pos),
}, {
.location = 1,
.binding = binding,
.format = vk::Format::eR32G32B32Sfloat,
.offset = offsetof(Vertex, norm),
},{
.location = 2,
.binding = binding,
.format = vk::Format::eR32G32Sfloat, .format = vk::Format::eR32G32Sfloat,
.offset = offsetof(Vertex, uv), .offset = offsetof(Vertex, pos),
},{ },
.location = 3,
.binding = binding,
.format = vk::Format::eR32G32B32Sfloat,
.offset = offsetof(Vertex, color),
}
}; };
} }
}; };

View File

@ -15,7 +15,11 @@ struct Camera {
glm::vec3 pos = glm::vec3(0.0f); glm::vec3 pos = glm::vec3(0.0f);
inline glm::vec3 dir() { inline glm::vec3 dir() {
return glm::vec3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi)); return glm::vec3(
sin(theta) * cos(phi),
cos(theta),
sin(theta) * sin(phi)
);
} }
inline glm::mat4 view() { inline glm::mat4 view() {

168
Scene/March.hpp Normal file
View File

@ -0,0 +1,168 @@
#pragma once
/* file format for storing raymarched scene for dynamic SDF */
#include <Scene/Object.hpp>
#include <util/log.hpp>
#include <fstream>
#include <map>
namespace March {
static std::vector<Object> readFile(const std::string& fname, std::map<std::string, int>& name_map) {
/* this should be rewritten once we are loading more than 250 objects */
std::ifstream in(fname);
std::vector<Object> 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<std::string, int>& 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<Object>& objs, const std::map<std::string, int>& 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();
}
};

21
Scene/Object.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <util/glsl_types.hpp>
using namespace glsl;
enum Shape {
eSPHERE,
eBOX,
ePLANE,
};
struct Object {
vec4 center;
vec4 dimensions;
uint id;
Shape shape;
float pad0;
float pad1;
};

View File

@ -1,118 +0,0 @@
#include <Renderer/CommandBuffer.hpp>
#include <Scene/Terrain.hpp>
#include <util/int.hpp>
float Terrain::getHeight(int32_t x, int32_t y) {
if (x < 0)
x += 64;
if (y < 0)
y += 64;
x %= 64;
y %= 64;
float xf = x;
float yf = y;
xf /= 64.0;
xf *= heightmap_tex->extent.width;
yf /= 64.0;
yf *= heightmap_tex->extent.height;
return static_cast<float>(heightmap_tex->image_data[static_cast<int>(static_cast<int>(yf) * heightmap_tex->extent.width + xf) * 4]) / 256.0f;
}
Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : phys_dev(phys_dev), dev(dev) {
/* tell Texture() not to free so we can apply our sobel filter */
heightmap_tex = &tex;
const auto patch_size = 64_u32;
const auto uv_scale = 1.0f;
const auto vertex_count = patch_size * patch_size;
vertices.resize(vertex_count);
for (size_t x = 0; x < patch_size; x++)
for (size_t y = 0; y < patch_size; y++)
vertices[x + y*patch_size] = (Vertex {
.pos = glm::vec3(
2.0f * x + 1.0f - patch_size,
0.0f,
2.0f * y + 1.0f - patch_size),
.uv = glm::vec2(static_cast<float>(x)/patch_size, static_cast<float>(y) / patch_size) * uv_scale,
});
/* use sobel filters to get normal:
* X sobel:
* +----+----+----+
* | +1 | +0 | -1 |
* +----+----+----+
* | +2 | +0 | -2 |
* +----+----+----+
* | +1 | +0 | -1 |
* +----+----+----+
* Y sobel:
* +----+----+----+
* | +1 | +2 | +1 |
* +----+----+----+
* | +0 | +0 | +0 |
* +----+----+----+
* | -1 | -2 | -1 |
* +----+----+----+
*/
for(auto x = 0_i32; x < patch_size; x++)
for (auto y = 0_i32; y < patch_size; y++) {
float moores_heights[3][3] = {
{ getHeight(x - 1, y - 1), getHeight(x - 1, y), getHeight(x - 1, y + 1) },
{ getHeight(x + 0, y - 1), getHeight(x + 0, y), getHeight(x + 0, y + 1) },
{ getHeight(x + 1, y - 1), getHeight(x + 1, y), getHeight(x + 1, y + 1) },
};
auto normal = glm::vec3(
/* x gets X sobel filter */
moores_heights[0][0] + 2.0f * moores_heights[0][1] + moores_heights[0][2]
- moores_heights[2][0] - 2.0f * moores_heights[2][1] - moores_heights[2][2],
0.0,
/* z gets Y sobel filter */
moores_heights[0][0] + 2.0f * moores_heights[1][0] + moores_heights[2][0]
- moores_heights[0][2] - 2.0f * moores_heights[1][2] - moores_heights[2][2]
);
/* fill in missing component, first scalar scales bump */
normal.y = 0.25 * glm::sqrt(glm::abs(1.0 - normal.x*normal.x - normal.z*normal.z));
//vertices[x + y * patch_size].norm = glm::vec3(getHeight(x, y));
vertices[x + y * patch_size].norm = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
}
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, vertices.size());
vertex_buffer->upload(vertices);
/* index generation */
const auto w = patch_size - 1;
indices.resize(w * w * 4);
for (auto x = 0_u32; x < w; x++)
for (auto y = 0_u32; y < w; y++) {
auto idx = (x + y * w) * 4;
indices[idx] = x+y*patch_size;
indices[idx+1] = indices[idx] + patch_size;
indices[idx+2] = indices[idx+1] + 1;
indices[idx + 3] = indices[idx] + 1;
}
index_buffer = std::make_unique<Buffer>(phys_dev, dev, sizeof(uint32_t)*indices.size(),
vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eHostCoherent
| vk::MemoryPropertyFlagBits::eHostVisible);
index_buffer->upload(indices);
}
void Terrain::draw(CommandBuffer& cmd) {
}
Terrain::~Terrain() {
index_buffer.reset();
vertex_buffer.reset();
}

View File

@ -1,26 +0,0 @@
#pragma once
#include <Resources/Texture.hpp>
#include <Renderer/VertexBuffer.hpp>
#include <Memory/Buffer.hpp>
#include <string>
struct CommandBuffer;
struct Terrain {
vk::PhysicalDevice phys_dev;
vk::Device dev;
Texture* heightmap_tex;
std::unique_ptr<VertexBuffer> vertex_buffer;
std::unique_ptr<Buffer> index_buffer;
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
float getHeight(int32_t x, int32_t y);
void draw(CommandBuffer& cmd);
~Terrain();
};

126
UI/UI.cpp
View File

@ -2,6 +2,7 @@
#include <imgui/imgui_impl_glfw.h> #include <imgui/imgui_impl_glfw.h>
#include <imgui/imgui_impl_vulkan.h> #include <imgui/imgui_impl_vulkan.h>
#include <imgui/imgui_console.h>
#include <Window/Window.hpp> #include <Window/Window.hpp>
@ -10,8 +11,33 @@
#include <Renderer/Renderer.hpp> #include <Renderer/Renderer.hpp>
#include <Scene/Camera.hpp> #include <Scene/Camera.hpp>
#include <Scene/March.hpp>
UI::UI(Renderer* ren) : info{ .flycam = ren->flycam, .time = ren->time, .cam = ren->cam, .tess_factor = ren->tess_factor, .tess_edge_size = ren->tess_edge_size }, dev(ren->dev) { /* this pains me to do, but its the only way :( */
Renderer* __ren;
static csys::ItemLog& operator<<(csys::ItemLog& log, ImVector<float>& vec) {
if (!vec.size())
return log << "vector<f32> {}";
log << "vector<f32> { ";
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);
}
UI::UI(Renderer* ren) : ren(ren) {
__ren = ren;
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
@ -79,6 +105,66 @@ UI::UI(Renderer* ren) : info{ .flycam = ren->flycam, .time = ren->time, .cam =
ImGui_ImplVulkan_DestroyFontUploadObjects(); ImGui_ImplVulkan_DestroyFontUploadObjects();
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
/* set up input so we can use the keyboard */
auto& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
console = new ImGuiConsole("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<csys::String>("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<csys::String>("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<csys::String>("Name"), csys::Arg<float>("center.x"), csys::Arg<float>("center.y"), csys::Arg<float>("center.z"), csys::Arg<float>("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<csys::String>("Name"),
csys::Arg<float>("center.x"), csys::Arg<float>("center.y"), csys::Arg<float>("center.z"),
csys::Arg<float>("dim.x"), csys::Arg<float>("dim.y"), csys::Arg<float>("dim.z"));
console->System().RegisterCommand("list-vars", "List variables accessible from developer console", [this]() {
const std::vector<std::string> 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<bool>("value"));
// console->System().RegisterVariable("visibility_testing", ren->visibility_testing, csys::Arg<bool>("value"));
console->System().RegisterVariable("flycam", ren->flycam, csys::Arg<bool>("value"));
console->System().RegisterVariable("speed", ren->speed, csys::Arg<float>("value"));
console->System().RegisterVariable("max_fps", ren->max_fps, csys::Arg<float>("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() { void UI::newFrame() {
@ -86,14 +172,40 @@ void UI::newFrame() {
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
ImGui::SetNextWindowBgAlpha(0.5f); ImGui::SetNextWindowBgAlpha(0.5f);
ImGui::Begin("Rendering Info", nullptr); ImGui::Begin("Rendering Info", nullptr);
ImGui::Text("FPS: %f", info.fps); ImGui::Text("FPS: %f", ren->fps);
ImGui::Text("Time: %f", info.time); ImGui::Text("Time: %f", ren->time);
ImGui::Checkbox("Fly Camera", &info.flycam); ImGui::SliderFloat("Rad", &ren->rad, 0.0, 3.0);
ImGui::SliderFloat("Tessellation Factor", &info.tess_factor, 0.1, 10.0); ImGui::SliderFloat("Theta", &ren->cam.theta, 0.0, glm::pi<float>());
ImGui::SliderFloat("Edge Size", &info.tess_edge_size, 0.0, 40.0); ImGui::SliderFloat("Phi", &ren->cam.phi, 0.0, glm::two_pi<float>());
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")) {
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(); ImGui::End();
} }
@ -104,7 +216,7 @@ void UI::render(vk::CommandBuffer cmd) {
} }
UI::~UI() { UI::~UI() {
dev.destroyDescriptorPool(desc_pool); // dev.destroyDescriptorPool(desc_pool);
ImGui_ImplVulkan_Shutdown(); ImGui_ImplVulkan_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();

View File

@ -3,27 +3,26 @@
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
#include <Renderer/CommandBuffer.hpp> #include <Renderer/CommandBuffer.hpp>
#include <imgui/imgui_console.h>
#include <vector>
struct Object;
struct Renderer; struct Renderer;
struct Camera; struct Camera;
struct UI { struct UI {
struct UI_Info { Renderer* ren;
float fps = 0.0;
bool& flycam;
float& time;
/* camera stuff */
Camera& cam;
float& tess_factor;
float& tess_edge_size;
} info;
vk::Device dev; vk::Device dev;
vk::DescriptorPool desc_pool; vk::DescriptorPool desc_pool;
UI(Renderer* ren); UI(Renderer* ren);
ImGuiConsole* console;
void newFrame(); void newFrame();
void render(vk::CommandBuffer cmd); void render(vk::CommandBuffer cmd);

BIN
assets/maps/git.bsp Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,108 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.0.44",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0,
1,
2,
3
]
}
],
"nodes":[
{
"name":"Mball"
},
{
"name":"Circle",
"translation":[
0,
0,
-2.97784423828125
]
},
{
"name":"Circle.001"
},
{
"mesh":0,
"name":"Sphere"
}
],
"meshes":[
{
"name":"Sphere",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1
},
"indices":2
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":30729,
"max":[
0.9991506934165955,
0.9976890087127686,
0.9973824620246887
],
"min":[
-0.9990072846412659,
-0.9995680451393127,
-0.9988008141517639
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":30729,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5123,
"count":56796,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":368748,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":368748,
"byteOffset":368748,
"target":34962
},
{
"buffer":0,
"byteLength":113592,
"byteOffset":737496,
"target":34963
}
],
"buffers":[
{
"byteLength":851088,
"uri":"ball.bin"
}
]
}

Binary file not shown.

View File

@ -1,107 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.0.44",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"stanford-bunny",
"rotation":[
0.7071068286895752,
0,
0,
0.7071068286895752
],
"scale":[
10.410737037658691,
10.410737037658691,
10.410737037658691
],
"translation":[
0,
-0.39514416456222534,
0
]
}
],
"meshes":[
{
"name":"stanford-bunny",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1
},
"indices":2
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":207619,
"max":[
0.0610090009868145,
0.058800000697374344,
-0.032986998558044434
],
"min":[
-0.0946900025010109,
-0.06187399849295616,
-0.1873210072517395
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":207619,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5125,
"count":208353,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":2491428,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":2491428,
"byteOffset":2491428,
"target":34962
},
{
"buffer":0,
"byteLength":833412,
"byteOffset":4982856,
"target":34963
}
],
"buffers":[
{
"byteLength":5816268,
"uri":"bunny.bin"
}
]
}

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,219 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.0.44",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0,
1
]
}
],
"nodes":[
{
"mesh":0,
"name":"Suzanne",
"rotation":[
0.06356222927570343,
0.536848247051239,
-0.8056020140647888,
0.2424030303955078
],
"translation":[
0.1063065230846405,
-0.6601571440696716,
-0.6140139698982239
]
},
{
"mesh":1,
"name":"Cube",
"rotation":[
0.1050531193614006,
-0.23102691769599915,
-0.3264787197113037,
0.9104955196380615
],
"scale":[
0.6794998645782471,
1,
1
]
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Suzanne",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3
}
]
},
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":4,
"NORMAL":5,
"TEXCOORD_0":6
},
"indices":7,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":1970,
"max":[
1.3671875,
0.984375,
0.8515625
],
"min":[
-1.3671875,
-0.984375,
-0.8515625
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":1970,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":1970,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":2904,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":5,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":7,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":23640,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":23640,
"byteOffset":23640,
"target":34962
},
{
"buffer":0,
"byteLength":15760,
"byteOffset":47280,
"target":34962
},
{
"buffer":0,
"byteLength":5808,
"byteOffset":63040,
"target":34963
},
{
"buffer":0,
"byteLength":288,
"byteOffset":68848,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":69136,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":69424,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":69616,
"target":34963
}
],
"buffers":[
{
"byteLength":69688,
"uri":"monk.bin"
}
]
}

Binary file not shown.

View File

@ -1,219 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.0.44",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0,
1
]
}
],
"nodes":[
{
"mesh":0,
"name":"Suzanne",
"rotation":[
0.06356222927570343,
0.536848247051239,
-0.8056020140647888,
0.2424030303955078
],
"translation":[
0.1063065230846405,
-0.6601571440696716,
-0.6140139698982239
]
},
{
"mesh":1,
"name":"Cube",
"rotation":[
0.1050531193614006,
-0.23102691769599915,
-0.3264787197113037,
0.9104955196380615
],
"scale":[
0.6794998645782471,
1,
1
]
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Suzanne",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3
}
]
},
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":4,
"NORMAL":5,
"TEXCOORD_0":6
},
"indices":7,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":1970,
"max":[
1.3671875,
0.984375,
0.8515625
],
"min":[
-1.3671875,
-0.984375,
-0.8515625
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":1970,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":1970,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":2904,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":5,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":7,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":23640,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":23640,
"byteOffset":23640,
"target":34962
},
{
"buffer":0,
"byteLength":15760,
"byteOffset":47280,
"target":34962
},
{
"buffer":0,
"byteLength":5808,
"byteOffset":63040,
"target":34963
},
{
"buffer":0,
"byteLength":288,
"byteOffset":68848,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":69136,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":69424,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":69616,
"target":34963
}
],
"buffers":[
{
"byteLength":69688,
"uri":"monkebox.bin"
}
]
}

View File

@ -1,16 +0,0 @@
#version 460 core
layout (location = 0) in vec3 norm;
layout (location = 1) in vec2 texCoord;
layout (location = 0) out vec4 FragColor;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
layout (set = 0, binding = 1) uniform sampler2D tex;
void main() {
FragColor = vec4(norm, 1.0);
}

Binary file not shown.

View File

@ -1,27 +0,0 @@
#version 450 core
layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 0) out vec3 _norm;
layout (location = 1) out vec2 _texCoord;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
void main(void) {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * gl_in[i].gl_Position;
_norm = norm[i];
_texCoord = texCoord[i];
EmitVertex();
}
EndPrimitive();
}

Binary file not shown.

View File

@ -1,18 +0,0 @@
#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNorm;
layout (location = 2) in vec2 aTexCoord;
layout (location = 0) out vec3 norm;
layout (location = 1) out vec2 texCoord;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
void main() {
gl_Position = vec4(aPos + (vec3(10.0) * gl_InstanceIndex), 1.0);
texCoord = aTexCoord;
norm = aNorm;
}

Binary file not shown.

View File

@ -1,44 +0,0 @@
#version 450 core
layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 0) out vec3 _norm;
layout (location = 1) out vec2 _texCoord;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
vec4 explode(vec4 pos, vec3 n) {
float mag = 2.0;
vec3 dir = n * (time-3.0)/10.0 * mag;
return pos + vec4(dir, 0.0);
}
void main(void) {
if (time < 3.0) {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * gl_in[i].gl_Position;
_norm = norm[i];
_texCoord = texCoord[i];
EmitVertex();
}
EndPrimitive();
return;
}
vec3 n = norm[0] + norm[1] + norm[2];
n/=3;
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * explode(gl_in[i].gl_Position, n);
_texCoord = texCoord[i];
_norm = n;
EmitVertex();
}
EndPrimitive();
}

Binary file not shown.

View File

@ -1,30 +0,0 @@
#version 450 core
layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 2) in vec3 pos[];
layout (location = 0) out vec3 _norm;
layout (location = 1) out vec2 _texCoord;
layout (location = 2) out vec3 _pos;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
void main(void) {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * gl_in[i].gl_Position;
_norm = norm[i];
_texCoord = texCoord[i];
_pos = pos[i];
EmitVertex();
}
EndPrimitive();
}

Binary file not shown.

View File

@ -1,20 +0,0 @@
#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNorm;
layout (location = 2) in vec2 aTexCoord;
layout (location = 0) out vec3 norm;
layout (location = 1) out vec2 texCoord;
layout (location = 2) out vec3 pos;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
void main() {
pos = (aPos + (vec3(10.0) * gl_InstanceIndex));
gl_Position = vec4(pos, 1.0);
texCoord = aTexCoord;
norm = aNorm;
}

Binary file not shown.

View File

@ -1,44 +0,0 @@
#version 450 core
layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 6) out;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 0) out vec3 _norm;
layout (location = 1) out vec2 _texCoord;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
};
vec4 explode(vec4 pos, vec3 n) {
float mag = 2.0;
vec3 dir = n * (sin(time/10.0)-3.0)/10.0 * mag;
return pos + vec4(dir, 0.0);
}
void main(void) {
if (time < 3.0) {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * gl_in[i].gl_Position;
_norm = norm[i];
_texCoord = texCoord[i];
EmitVertex();
}
}
vec3 n = norm[0] + norm[1] + norm[2];
n/=3;
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = mvp * explode(gl_in[i].gl_Position*abs(cos(time)), n);
_texCoord = texCoord[i];
_norm = n;
EmitVertex();
}
EndPrimitive();
}

Binary file not shown.

View File

@ -1,38 +0,0 @@
#version 460 core
layout (location = 0) in vec3 norm;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 pos;
layout (location = 0) out vec4 FragColor;
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec3 cam_dir;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
layout (set = 0, binding = 1) uniform sampler2D tex;
void main() {
vec3 light_pos = normalize(vec3(cos(time), sin(time), 0.0))*10.0;
/* calculate highlight using angle between fragment and viewer */
vec3 I = normalize(cam_pos-pos);
vec3 L = normalize(light_pos-pos);
vec3 cool = vec3(0.0, 0.0, 0.55);
vec3 warm = vec3(0.3, 0.3, 0);
float t = (dot(norm, L)+1.0)/2.0;
vec3 r = 2.0*(dot(norm,L)*norm)-L;
float s = 100.0*(dot(r,I))-97.0;
if(s < 0.0)
s = 0.0;
else if(s > 1.0)
s = 1.0;
FragColor = vec4((1.0-s)*(t*warm + (vec3(0.5)-t)*cool) + s*vec3(1.0), 1.0);
}

Binary file not shown.

View File

@ -1,29 +0,0 @@
#version 460 core
layout (location = 0) in vec3 norm;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 pos;
layout (location = 0) out vec4 FragColor;
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec3 cam_dir;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
layout (set = 0, binding = 1) uniform sampler2D tex;
void main() {
vec3 light_pos = normalize(vec3(cos(time), sin(time), 0.0))*10.0;
vec3 L = normalize(light_pos-pos);
float r = length(light_pos-pos);
float t = clamp(dot(L, norm), 0.0, 1.0) * 20.0/(r*r);
FragColor = vec4(t, t/2.0, t*2.0, 1.0);
}

Binary file not shown.

126
assets/shaders/manual.frag Normal file
View File

@ -0,0 +1,126 @@
#version 450 core
#define MAX_STEPS 100
#define MAX_DIST 1000.0
layout (set = 0, binding = 1) uniform sampler2D tex;
layout (set = 0, binding = 0) uniform Matrices {
vec3 cam_pos;
float time;
vec4 viewport;
vec3 cam_dir;
uint n_objects;
float rad;
};
layout (location = 0) in vec2 pos;
layout (location = 0) out vec4 fragColor;
vec2 eps = vec2(0.001, 0.0);
/* joins two parts of a scene */
float op_union(float v1, float v2) {
return min(v1, v2);
}
/* subtracts sdf from scene */
float op_subtract(float v1, float v2) {
return max(v1, -v2);
}
/* twists! in the xz */
vec3 op_twist(vec3 p, float k) {
float c = cos(k*p.y);
float s = sin(k*p.y);
mat2 m = mat2(c, -s, s, c);
return vec3(m*p.xz, p.y);
}
float box(vec3 p, vec3 r) {
vec3 q = abs(p) - r;
return length(max(q,0.0)) + min(max(q.x, max(q.y,q.z)), 0.0);
}
/* square, centered at the origin */
float box(vec2 p, vec2 r) {
vec2 d = abs(p)-r;
return length(max(d, 0.0)) + min(max(d.x,d.y), 0.0);
}
/* infinite cross, used for construction of serpinski cube */
float infcross(vec3 p, float r) {
vec2 unit = vec2(r);
float box1 = box(p.xy, unit);
float box2 = box(p.yz, unit);
float box3 = box(p.zx, unit);
return op_union(op_union(box1, box2), box3);
}
float sphere(vec3 p, float r) {
return length(p) - r;
}
float sdf(vec3 p) {
p = op_twist(p.xzy, cos(time)).xzy;
float d = box(p, vec3(1.0));
float s = 1.0;
for(int i = 0; i < 5; i++) {
/* using mod to repeat across domain, then shift to +- */
vec3 a = mod(p * s, 2.0) - 1.0;
s *= 3.0;
vec3 r = 1.0 - 3.0*abs(a);
float c = infcross(r, rad)/s;
d = max(d, c);
}
return d;
}
vec3 norm(vec3 pos) {
return normalize(
vec3(
sdf(pos+eps.xyy),
sdf(pos+eps.yxy),
sdf(pos+eps.yyx)
) - sdf(pos)
);
}
vec2 raycast(vec3 dir) {
float t = 0.0;
for(int i = 0; i < MAX_STEPS; i++) {
float dt = sdf(cam_pos + dir * t);
if(dt < 0.0001*t)
return vec2(t, float(i));
else if(dt > MAX_DIST)
return vec2(0.0, -1.0);
t += dt;
}
return vec2(0.0, 64.0);
}
vec3 raygen() {
vec3 forward = cam_dir;
vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0)));
vec3 up = normalize(cross(right, forward));
return normalize(pos.x * right + pos.y * up + forward * 2.0);
}
vec3 gamma(vec3 c) {
return pow(c, vec3(0.4545));
}
void main() {
vec3 dir = raygen();
vec2 d = raycast(dir);
if(d.y == -1.0) {
fragColor = vec4(0.0);
return;
}
fragColor = vec4(d.y/MAX_STEPS);
fragColor.xyz = gamma(fragColor.xyz);
}

Binary file not shown.

157
assets/shaders/ray.frag Normal file
View File

@ -0,0 +1,157 @@
#version 450 core
#define MAX_STEPS 64
const vec2 eps = vec2(0.001, 0.0);
layout (set = 0, binding = 1) uniform sampler2D tex;
layout (set = 0, binding = 0) uniform Matrices {
vec3 cam_pos;
float time;
vec4 viewport;
vec3 cam_dir;
uint n_objects;
};
/*
enum Shape {
SPHERE,
BOX,
PLANE,
};
*/
#define SPHERE 0
#define BOX 1
#define PLANE 2
struct Object {
vec4 center;
vec4 dimensions;
uint id;
uint shape;
};
layout (set = 0, binding = 2) readonly buffer Objects {
Object objects[];
};
layout (location = 0) in vec2 pos;
layout (location = 0) out vec4 fragColor;
/* joins two parts of a scene */
vec2 op_union(vec2 v1, float v2, float id) {
if(v1.x > v2)
return vec2(v2, id);
return v1;
}
/* subtracts sdf from scene */
float op_subtract(float v1, float v2) {
return max(v1, -v2);
}
float sphere(vec3 p, vec3 c, float r) {
return abs(length(p-c) - r);
}
float box(vec3 p, vec3 c, vec3 r) {
p -= c;
vec3 q = abs(p) - r;
return length(max(q,0.0)) + min(max(q.x, max(q.y,q.z)), 0.0);
}
float plane(vec3 p, vec3 norm, float d) {
return dot(p, normalize(norm)) + d;
}
float obj_to_sdf(vec3 p, uint n) {
switch(objects[n].shape) {
case SPHERE:
return sphere(p, objects[n].center.xyz, objects[n].dimensions.x);
break;
case BOX:
return box(p, objects[n].center.xyz, objects[n].dimensions.xyz);
break;
case PLANE:
return plane(p, objects[n].center.xyz, objects[n].dimensions.x);
break;
}
}
float map(vec2 p) {
return sin(p.x)*sin(p.y);
}
float terrain(vec3 p) {
/* render terrain with bumps */
return box(p, vec3(0.0), vec3(100.0, 0.1, 100.0)) - abs(map(p.xz));
}
/* <distance, id> */
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), 1.0);
// d = op_union(d, sphere(pos, objects[i].center.xyz, objects[i].dimensions.x), 1.0);
}
/*float dsphere = sphere(pos, vec3(0.0, 3.0*sin(time)-10.0, 0.0), 1.0);
float dbox = box(pos, vec3(0.0, -10.0, 0.0), vec3(10.0, 1.0, 10.0));
float dpellet = sphere(pos, vec3(0.0, 10.0*sin(time) - 10.0, 0.0), 0.1);
float d = op_union(op_subtract(dbox, dsphere), dpellet);*/
//d = op_union(d, plane(pos, normalize(vec3(1.0)), 50.0));
return d;
}
vec2 raycast(vec3 dir) {
float t = 0.0;
for(int i = 0; i < MAX_STEPS; i++) {
vec2 dt = sdf(cam_pos + dir * t);
if(dt.y == -1.0)
return dt;
if(dt.x < 0.0001*t)
// 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;
}
return vec2(0.0, -1.0);
}
vec3 norm(vec3 pos) {
return normalize(
vec3(
sdf(pos+eps.xyy).x,
sdf(pos+eps.yxy).x,
sdf(pos+eps.yyx).x
) - sdf(pos).x
);
}
vec3 raygen() {
vec3 forward = cam_dir;
vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0)));
vec3 up = normalize(cross(right, forward));
return normalize(pos.x * right + pos.y * up + forward * 2.0);
}
void main() {
vec3 dir = raygen();
vec2 d = raycast(dir);
vec3 p = d.x*dir + cam_pos;
if(d.y != -1.0)
fragColor = vec4(d.y);
else
fragColor = vec4(0.0);
}

BIN
assets/shaders/ray.frag.spv Normal file

Binary file not shown.

17
assets/shaders/ray.vert Normal file
View File

@ -0,0 +1,17 @@
#version 460 core
layout (location = 0) in vec2 aCoord;
layout (location = 0) out vec2 pos;
layout (set = 0, binding = 0) uniform Matrices {
vec3 cam_pos;
float time;
vec4 viewport;
vec3 cam_dir;
};
void main() {
gl_Position = vec4(aCoord, 0.0, 1.0);
pos = vec2(aCoord.x * viewport.x / viewport.y, aCoord.y);
}

BIN
assets/shaders/ray.vert.spv Normal file

Binary file not shown.

View File

@ -1,29 +0,0 @@
#version 460 core
layout (location = 0) in vec3 norm;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 pos;
layout (location = 0) out vec4 FragColor;
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec3 cam_dir;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
layout (set = 0, binding = 1) uniform sampler2D heightmap;
void main() {
/* extract L (light direction) from view matrix */
vec3 L = -cam_dir;
float r = length(cam_pos-pos);
float t = clamp(dot(L, norm), 0.0, 1.0) * 20.0/(r*r);
FragColor = vec4(t*norm, 1.0);
}

Binary file not shown.

View File

@ -1,89 +0,0 @@
#version 450 core
#extension GL_EXT_debug_printf : enable
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec3 cam_dir;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
layout(set = 0, binding = 1) uniform sampler2D heightmap;
layout (vertices = 4) out;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 2) in vec3 pos[];
layout (location = 0) out vec3 _norm[4];
layout (location = 1) out vec2 _texCoord[4];
layout (location = 2) out vec3 _pos[4];
float screen_space_tess(vec4 p0, vec4 p1) {
/* calc midpoint + distance btw the two points */
vec4 midp = 0.5*(p0+p1);
float r = distance(p0, p1) / 2.0;
vec4 v0 = view * midp;
/* project into clip spaace */
vec4 clip0 = proj * (v0 - vec4(r, vec3(0.0)));
vec4 clip1 = proj * (v0 + vec4(r, vec3(0.0)));
clip0 /= clip0.w;
clip1 /= clip1.w;
/* convert to viewport coords */
clip0.xy *= viewport;
clip1.xy *= viewport;
return clamp(distance(clip0, clip1) / tess_edge_size * tess_factor, 1.0, 64.0);
}
bool frustum_culling() {
/* square root of patch size */
const float r = 8.0f;
/* ensure this is consistent with tese shader */
vec4 fpos = gl_in[gl_InvocationID].gl_Position;
fpos.y += 15.0 * textureLod(heightmap, texCoord[0], 0.0).r;
for(int i = 0; i < 6; i++) {
if(dot(fpos, frustum[i]) + r < 0.0)
return false;
}
return true;
}
void main() {
if(gl_InvocationID == 0) {
/* perform frustum culling */
if(frustum_culling()) {
gl_TessLevelOuter[0] = screen_space_tess(gl_in[3].gl_Position, gl_in[0].gl_Position);
gl_TessLevelOuter[1] = screen_space_tess(gl_in[0].gl_Position, gl_in[1].gl_Position);
gl_TessLevelOuter[2] = screen_space_tess(gl_in[1].gl_Position, gl_in[2].gl_Position);
gl_TessLevelOuter[3] = screen_space_tess(gl_in[2].gl_Position, gl_in[3].gl_Position);
gl_TessLevelInner[0] = mix(gl_TessLevelOuter[0], gl_TessLevelOuter[3], 0.5);
gl_TessLevelInner[1] = mix(gl_TessLevelOuter[2], gl_TessLevelOuter[1], 0.5);
} else {
gl_TessLevelOuter[0] = 0.0;
gl_TessLevelOuter[1] = 0.0;
gl_TessLevelOuter[2] = 0.0;
gl_TessLevelOuter[3] = 0.0;
gl_TessLevelInner[0] = 0.0;
gl_TessLevelInner[1] = 0.0;
}
}
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
_norm[gl_InvocationID] = norm[gl_InvocationID];
_texCoord[gl_InvocationID] = texCoord[gl_InvocationID];
_pos[gl_InvocationID] = pos[gl_InvocationID];
}

Binary file not shown.

View File

@ -1,49 +0,0 @@
#version 450 core
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec3 cam_dir;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
layout(set = 0, binding = 1) uniform sampler2D heightmap;
layout(quads, equal_spacing, ccw) in;
layout (location = 0) in vec3 norm[];
layout (location = 1) in vec2 texCoord[];
layout (location = 2) in vec3 pos[];
layout (location = 0) out vec3 _norm;
layout (location = 1) out vec2 _texCoord;
layout (location = 2) out vec3 _pos;
void main() {
/* interpolation of UV coordinates */
vec2 uv1 = mix(texCoord[0], texCoord[1], gl_TessCoord.x);
vec2 uv2 = mix(texCoord[3], texCoord[2], gl_TessCoord.x);
_texCoord = mix(uv1, uv2, gl_TessCoord.y);
vec3 norm1 = mix(norm[0], norm[1], gl_TessCoord.x);
vec3 norm2 = mix(norm[3], norm[2], gl_TessCoord.x);
_norm = mix(norm1, norm2, gl_TessCoord.y);
/* not displacing yet */
vec4 pos1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);
vec4 pos2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
vec4 fpos = mix(pos1, pos2, gl_TessCoord.y);
fpos.y += 15.0 * texture(heightmap, _texCoord).r;
_pos = fpos.xyz;
gl_Position = proj * view * fpos;
}

Binary file not shown.

View File

@ -1,26 +0,0 @@
#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNorm;
layout (location = 2) in vec2 aTexCoord;
layout (location = 0) out vec3 norm;
layout (location = 1) out vec2 texCoord;
layout (location = 2) out vec3 pos;
layout (set = 0, binding = 0) uniform Matrices {
mat4 view;
mat4 proj;
float time;
vec3 cam_pos;
vec4 frustum[6];
vec2 viewport;
float tess_factor;
float tess_edge_size;
};
void main() {
pos = aPos;
gl_Position = vec4(pos, 1.0);
texCoord = aTexCoord;
norm = aNorm;
}

Binary file not shown.

0
assets/shaders/test Normal file
View File

View File

@ -1,65 +0,0 @@
#version 460 core
layout (location = 0) in vec3 norm;
layout (location = 1) in vec2 texCoord;
layout (location = 0) out vec4 FragColor;
layout (set = 0, binding = 0) uniform Matrices {
mat4 mvp;
float time;
float aspect_ratio;
};
/* 4-float alignment in memory*/
struct Ray {
vec3 orig;
float min;
vec3 dir;
float max;
};
vec3 ray_color(vec3 ray) {
float a = 0.5 * (ray.y+1.0);
return vec3(1.0-a) + a*vec3(0.5, 0.7, 1.0);
}
/* returns float of where you hit, or -1.0 */
float sphere(vec3 center, float r, Ray ray) {
vec3 p = ray.orig-center;
float a = dot(ray.dir, ray.dir);
float half_b = dot(p, ray.dir);
float c = dot(p, p) - r*r;
float discr = half_b*half_b - a*c;
if(discr < 0.0)
return -1.0;
return -half_b-sqrt(discr)/a;
}
void main() {
vec2 pixel = texCoord*2.0-1.0;
Ray ray;
ray.orig = vec3(0.0);
ray.min = 0.1;
ray.max = 10000.0;
float fov = radians(90);
ray.dir = normalize(vec3(pixel.x * aspect_ratio, pixel.y, tan(fov/2.0)));
vec3 sphere_center = vec3(0.0, 0.0, 1.0);
float d = sphere(sphere_center, 0.5, ray);
FragColor = vec4(texCoord, 1.0, 1.0);
return;
if(d < 0.0) {
FragColor = vec4(0.0);
return;
}
vec3 n = normalize(ray.dir*d-sphere_center);
FragColor = vec4((n+vec3(1.0)) / 2.0, 1.0);
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

61
flake.lock generated Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1743315132,
"narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "52faf482a3889b7619003c0daec593a1912fddc1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

31
flake.nix Normal file
View File

@ -0,0 +1,31 @@
{
description = "Flake for Vulkan and GLSLC Project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.git
pkgs.cmake
pkgs.vulkan-loader
pkgs.vulkan-headers
pkgs.vulkan-tools
pkgs.vulkan-validation-layers
pkgs.shaderc
pkgs.glfw3
];
shellHook = ''
echo "Vulkan development environment loaded."
'';
};
}
);
}

69
include/csys/api.h Normal file
View File

@ -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 */

View File

@ -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 <vector>
#include <stdexcept>
#include <string_view>
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<typename T>
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<TYPE> \
{ \
inline ArgumentParser(String &input, size_t &start); \
TYPE m_Value = 0; \
}; \
inline ArgumentParser<TYPE>::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<unsigned char>(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<unsigned char>(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<typename T>
struct CSYS_API ArgumentParser<std::vector<T>>
{
/*!
* \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<T> 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<typename T>
ArgumentParser<std::vector<T>>::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<T>(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<T>(input, start).m_Value);
range.first = start;
}
}
}
}
}
#endif //CSYS_ARGUMENT_PARSER_H

208
include/csys/arguments.h Normal file
View File

@ -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 <vector>
namespace csys
{
/*!
* \brief
* Macro for supporting trivial types
*/
#define SUPPORT_TYPE(TYPE, TYPE_NAME)\
template<> struct is_supported_type<TYPE> { static constexpr bool value = true; }; \
template<> \
struct CSYS_API ArgData<TYPE> \
{ \
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<typename> 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<typename T>
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<typename U> struct is_supported_type<std::vector<U>> { static constexpr bool value = is_supported_type<U>::value; };
template<typename T>
struct CSYS_API ArgData<std::vector<T>>
{
/*!
* \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<T>("").m_TypeName.m_String; //!< Type name
std::vector<T> m_Value; //!< Vector of data
};
/*!
* \brief
* Wrapper around an argument for use of parsing a command line
* \tparam T
* Data type
*/
template<typename T>
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<typename U>
static constexpr bool is_supported_type_v = is_supported_type<U>::value;
public:
using ValueType = std::remove_cv_t<std::remove_reference_t<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>,
"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<T> &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<ValueType>(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<ValueType> 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<NULL_ARGUMENT>
{
/*!
* \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<NULL_ARGUMENT> &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

386
include/csys/autocomplete.h Normal file
View File

@ -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 <vector>
#include <string>
#include <memory>
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<std::string> &;
using sVector = std::vector<std::string>;
//!< 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<typename inputType>
AutoComplete(std::initializer_list<inputType> il)
{
for (const auto &item : il)
{
Insert(item);
}
}
/*!
*
* \tparam T
* Container type
* \param[in] items
* Arbitrary container of strings
*/
template<typename T>
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<typename strType>
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<typename strType>
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<typename strType>
std::unique_ptr<sVector> Suggestions(const strType &prefix)
{
auto temp = std::make_unique<sVector>();
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<sVector> 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

View File

@ -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<std::string> &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::sVector> AutoComplete::Suggestions(const char *prefix)
{
auto temp = std::make_unique<sVector>();
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);
}
}

292
include/csys/command.h Normal file
View File

@ -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 <functional>
#include <type_traits>
#include <utility>
#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<typename Fn, typename ...Args>
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<T>"
*/
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<NULL_ARGUMENT>())
{}
/*!
* \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<argumentSize + 1>{}, std::make_index_sequence<argumentSize>{});
}
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<sizeof ...(Args)>{}) + "\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<Fn, Args...>(*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<size_t... Is_p, size_t... Is_c>
void Call(String &input, const std::index_sequence<Is_p...> &, const std::index_sequence<Is_c...> &)
{
size_t start = 0;
// Parse arguments
int _[]{0, (void(std::get<Is_p>(m_Arguments).Parse(input, start)), 0)...};
(void) (_);
// Call function with unpacked tuple
m_Function((std::get<Is_c>(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<size_t ...Is>
std::string DisplayArguments(const std::index_sequence<Is...> &)
{
return (std::get<Is>(m_Arguments).Info() + ...);
}
const String m_Name; //!< Name of command
const String m_Description; //!< Description of the command
std::function<void(typename Args::ValueType...)> m_Function; //!< Function to be invoked as command
std::tuple<Args..., Arg<NULL_ARGUMENT>> 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<typename Fn>
class CSYS_API Command<Fn> : 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<NULL_ARGUMENT>())
{}
/*!
* \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<Fn>(*this);
}
private:
const String m_Name; //!< Name of command
const String m_Description; //!< Description of the command
std::function<void(void)> m_Function; //!< Function to be invoked as command
std::tuple<Arg<NULL_ARGUMENT>> m_Arguments; //!< Arguments to be passed into m_Function
};
}
#endif //CSYS_COMMAND_H

11
include/csys/csys.h Normal file
View File

@ -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

64
include/csys/exceptions.h Normal file
View File

@ -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 <string>
#include <exception>
#include <utility>
#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

152
include/csys/history.h Normal file
View File

@ -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 <string>
#include <vector>
#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<std::string> m_History; //!< Console command history
};
}
#ifdef CSYS_HEADER_ONLY
#include "csys/history.inl"
#endif
#endif //CSYS_HISTORY_H

83
include/csys/history.inl Normal file
View File

@ -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 <algorithm>
#include <iostream>
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();
}
}

197
include/csys/item.h Normal file
View File

@ -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 <vector>
#include <string>
#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<Item> &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<Item> m_Items;
};
}
#ifdef CSYS_HEADER_ONLY
#include "csys/item.inl"
#endif
#endif

116
include/csys/item.inl Normal file
View File

@ -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 <chrono>
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<unsigned int>(std::chrono::duration_cast<std::chrono::milliseconds>(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<Item> &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)
}

120
include/csys/script.h Normal file
View File

@ -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 <string>
#include <vector>
#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<std::string> 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<std::string> &Data();
protected:
std::vector<std::string> 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

83
include/csys/script.inl Normal file
View File

@ -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 <fstream>
#include <utility>
#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<std::string> 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<std::string> &Script::Data()
{
return m_Data;
}
}

110
include/csys/string.h Normal file
View File

@ -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 <string>
#include <cctype>
#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<size_t, size_t> NextPoi(size_t &start) const
{
size_t end = m_String.size();
std::pair<size_t, size_t> 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

356
include/csys/system.h Normal file
View File

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

300
include/csys/system.inl Normal file
View File

@ -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<CommandBase>(pair.second->Clone());
}
// Copy scripts.
for (const auto &pair: rhs.m_Scripts)
{
m_Scripts[pair.first] = std::make_unique<Script>(*pair.second);
}
}
CSYS_INLINE System &System::operator=(const System &rhs)
{
if (this == &rhs)
return *this;
// Copy commands.
for (const auto &pair : rhs.m_Commands)
{
m_Commands[pair.first] = std::unique_ptr<CommandBase>(pair.second->Clone());
}
// Other data.
m_CommandSuggestionTree = rhs.m_CommandSuggestionTree;
m_VariableSuggestionTree = rhs.m_VariableSuggestionTree;
m_CommandHistory = rhs.m_CommandHistory;
m_ItemLog = rhs.m_ItemLog;
// Copy scripts.
for (const auto &pair: rhs.m_Scripts)
{
m_Scripts[pair.first] = std::make_unique<Script>(*pair.second);
}
// Rest of data.
m_RegisterCommandSuggestion = rhs.m_RegisterCommandSuggestion;
return *this;
}
CSYS_INLINE void System::RunCommand(const std::string &line)
{
// Error checking.
if (line.empty())
return;
// Log command.
Log(csys::ItemType::eCOMMAND) << line << csys::endl;
// Parse command line.
ParseCommandLine(line);
}
CSYS_INLINE void System::RunScript(const std::string &script_name)
{
// Attempt to find script.
auto script_pair = m_Scripts.find(script_name);
// Exit if not found.
if (script_pair == m_Scripts.end())
{
m_ItemLog.log(eERROR) << "Script \"" << script_name << "\" not found" << csys::endl;
return;
}
// About to run script.
m_ItemLog.log(eINFO) << "Running \"" << script_name << "\"" << csys::endl;
// Load if script is empty.
if (script_pair->second->Data().empty())
{
try
{
script_pair->second->Load();
}
catch (csys::Exception &e)
{
Log(eERROR) << e.what() << csys::endl;
}
}
// Run script.
for (const auto &cmd : script_pair->second->Data())
{
// Parse command.
RunCommand(cmd);
}
}
CSYS_INLINE void System::RegisterScript(const std::string &name, const std::string &path)
{
// Attempt to find scripts.
auto script = m_Scripts.find(name);
// Don't register if script already exists.
if (script == m_Scripts.end())
{
m_Scripts[name] = std::make_unique<Script>(path, true);
m_VariableSuggestionTree.Insert(name);
} else
throw csys::Exception("ERROR: Script \'" + name + "\' already registered");
}
CSYS_INLINE void System::UnregisterCommand(const std::string &cmd_name)
{
// Exit if non existent.
if (cmd_name.empty()) return;
// Get command.
auto command_it = m_Commands.find(cmd_name);
auto help_command_it = m_Commands.find("help " + cmd_name);
// Erase if found.
if (command_it != m_Commands.end() && help_command_it != m_Commands.end())
{
m_CommandSuggestionTree.Remove(cmd_name);
m_VariableSuggestionTree.Remove(cmd_name);
m_Commands.erase(command_it);
m_Commands.erase(help_command_it);
}
}
CSYS_INLINE void System::UnregisterVariable(const std::string &var_name)
{
// Exit if non existent.
if (var_name.empty()) return;
// Get command.
auto s_it = m_Commands.find("set " + var_name);
auto g_it = m_Commands.find("get " + var_name);
// Erase if found.
if (s_it != m_Commands.end() && g_it != m_Commands.end())
{
m_VariableSuggestionTree.Remove(var_name);
m_Commands.erase(s_it);
m_Commands.erase(g_it);
}
}
CSYS_INLINE void System::UnregisterScript(const std::string &script_name)
{
// Exit if non existent.
if (script_name.empty()) return;
// Get command.
auto it = m_Scripts.find(script_name);
// Erase if found.
if (it != m_Scripts.end())
{
m_VariableSuggestionTree.Remove(script_name);
m_Scripts.erase(it);
}
}
// Getters ////////////////////////////////////////////////////////////////
CSYS_INLINE AutoComplete &System::CmdAutocomplete() { return m_CommandSuggestionTree; }
CSYS_INLINE AutoComplete &System::VarAutocomplete() { return m_VariableSuggestionTree; }
CSYS_INLINE CommandHistory &System::History() { return m_CommandHistory; }
CSYS_INLINE std::vector<Item> &System::Items() { return m_ItemLog.Items(); }
CSYS_INLINE ItemLog &System::Log(ItemType type) { return m_ItemLog.log(type); }
CSYS_INLINE std::unordered_map<std::string, std::unique_ptr<CommandBase>> &System::Commands() { return m_Commands; }
CSYS_INLINE std::unordered_map<std::string, std::unique_ptr<Script>> &System::Scripts() { return m_Scripts; }
///////////////////////////////////////////////////////////////////////////
// Private methods ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
CSYS_INLINE void System::ParseCommandLine(const String &line)
{
// Get first non-whitespace char.
size_t line_index = 0;
std::pair<size_t, size_t> range = line.NextPoi(line_index);
// Just whitespace was passed in. Don't log as command.
if (range.first == line.End())
return;
// Push to history.
m_CommandHistory.PushBack(line.m_String);
// Get name of command.
std::string command_name = line.m_String.substr(range.first, range.second - range.first);
// Set or get
bool is_cmd_set = command_name == s_Set;
bool is_cmd_get = command_name == s_Get;
bool is_cmd_help = !(is_cmd_set || is_cmd_get) ? command_name == s_Help : false;
// Edge case for if user is just runs "help" command
if (is_cmd_help)
{
range = line.NextPoi(line_index);
if (range.first != line.End())
command_name += " " + line.m_String.substr(range.first, range.second - range.first);
}
// Its a set or get command
else if (is_cmd_set || is_cmd_get)
{
// Try to get variable name
if ((range = line.NextPoi(line_index)).first == line.End())
{
Log(eERROR) << s_ErrorNoVar << endl;
return;
} else
// Append variable name.
command_name += " " + line.m_String.substr(range.first, range.second - range.first);
}
// Get runnable command
auto command = m_Commands.find(command_name);
if (command == m_Commands.end())
Log(eERROR) << s_ErrorSetGetNotFound << endl;
// Run the command
else
{
// Get the arguments.
String arguments = line.m_String.substr(range.second, line.m_String.size() - range.first);
// Execute command.
auto cmd_out = (*command->second)(arguments);
// Log output.
if (cmd_out.m_Type != eNONE)
m_ItemLog.Items().emplace_back(cmd_out);
}
}
}

View File

@ -0,0 +1,656 @@
// Copyright (c) 2020 - present, Roland Munguia
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//#pragma once
#include <string>
#include "imgui_console.h"
#include "imgui_internal.h"
#include <cstring>
// The following three functions (InputTextCallback_UserData, InputTextCallback, InputText) are obtained from misc/cpp/imgui_stdlib.h
// Which are licensed under MIT License (https://github.com/ocornut/imgui/blob/master/LICENSE.txt)
namespace ImGui {
struct InputTextCallback_UserData
{
std::string* Str;
ImGuiInputTextCallback ChainCallback;
void* ChainCallbackUserData;
};
static int InputTextCallback(ImGuiInputTextCallbackData* data)
{
auto* user_data = (InputTextCallback_UserData*)data->UserData;
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
{
// Resize string callback
// If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
std::string* str = user_data->Str;
IM_ASSERT(data->Buf == str->c_str());
str->resize(data->BufTextLen);
data->Buf = (char*)str->c_str();
} else if (user_data->ChainCallback)
{
// Forward to user callback, if any
data->UserData = user_data->ChainCallbackUserData;
return user_data->ChainCallback(data);
}
return 0;
}
bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
{
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
flags |= ImGuiInputTextFlags_CallbackResize;
InputTextCallback_UserData cb_user_data;
cb_user_data.Str = str;
cb_user_data.ChainCallback = callback;
cb_user_data.ChainCallbackUserData = user_data;
return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
}
}
ImGuiConsole::ImGuiConsole(std::string c_name, size_t inputBufferSize) : m_ConsoleName(std::move(c_name))
{
// Set input buffer size.
m_Buffer.resize(inputBufferSize);
m_HistoryIndex = std::numeric_limits<size_t>::min();
// Specify custom data to be store/loaded from imgui.ini
InitIniSettings();
// Set Console ImGui default settings
if (!m_LoadedFromIni)
{
DefaultSettings();
}
// Custom functions.
RegisterConsoleCommands();
}
void ImGuiConsole::Draw()
{
///////////////////////////////////////////////////////////////////////////
// Window and Settings ////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Begin Console Window.
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_WindowAlpha);
if (!ImGui::Begin(m_ConsoleName.data(), nullptr, ImGuiWindowFlags_MenuBar))
{
ImGui::PopStyleVar();
ImGui::End();
return;
}
ImGui::PopStyleVar();
///////////////
// Menu bar //
///////////////
MenuBar();
////////////////
// Filter bar //
////////////////
if (m_FilterBar)
{
FilterBar();
}
//////////////////
// Console Logs //
//////////////////
LogWindow();
// Section off.
ImGui::Separator();
///////////////////////////////////////////////////////////////////////////
// Command-line ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
InputBar();
ImGui::End();
}
csys::System& ImGuiConsole::System()
{
return m_ConsoleSystem;
}
void ImGuiConsole::InitIniSettings()
{
ImGuiContext& g = *ImGui::GetCurrentContext();
// Load from .ini
if (g.Initialized && !g.SettingsLoaded && !m_LoadedFromIni)
{
ImGuiSettingsHandler console_ini_handler;
console_ini_handler.TypeName = "imgui-console";
console_ini_handler.TypeHash = ImHashStr("imgui-console");
console_ini_handler.ClearAllFn = SettingsHandler_ClearALl;
console_ini_handler.ApplyAllFn = SettingsHandler_ApplyAll;
console_ini_handler.ReadInitFn = SettingsHandler_ReadInit;
console_ini_handler.ReadOpenFn = SettingsHandler_ReadOpen;
console_ini_handler.ReadLineFn = SettingsHandler_ReadLine;
console_ini_handler.WriteAllFn = SettingsHandler_WriteAll;
console_ini_handler.UserData = this;
g.SettingsHandlers.push_back(console_ini_handler);
}
// else Ini settings already loaded!
}
void ImGuiConsole::DefaultSettings()
{
// Settings
m_AutoScroll = true;
m_ScrollToBottom = false;
m_ColoredOutput = true;
m_FilterBar = true;
m_TimeStamps = true;
// Style
m_WindowAlpha = 1;
m_ColorPalette[COL_COMMAND] = ImVec4(1.f, 1.f, 1.f, 1.f);
m_ColorPalette[COL_LOG] = ImVec4(1.f, 1.f, 1.f, 0.5f);
m_ColorPalette[COL_WARNING] = ImVec4(1.0f, 0.87f, 0.37f, 1.f);
m_ColorPalette[COL_ERROR] = ImVec4(1.f, 0.365f, 0.365f, 1.f);
m_ColorPalette[COL_INFO] = ImVec4(0.46f, 0.96f, 0.46f, 1.f);
m_ColorPalette[COL_TIMESTAMP] = ImVec4(1.f, 1.f, 1.f, 0.5f);
}
void ImGuiConsole::RegisterConsoleCommands()
{
m_ConsoleSystem.RegisterCommand("clear", "Clear console log", [this]()
{
m_ConsoleSystem.Items().clear();
});
m_ConsoleSystem.RegisterCommand("filter", "Set screen filter", [this](const csys::String& filter)
{
// Reset filter buffer.
std::memset(m_TextFilter.InputBuf, '\0', 256);
// Copy filter input buffer from client.
std::copy(filter.m_String.c_str(), filter.m_String.c_str() + std::min(static_cast<int>(filter.m_String.length()), 255), m_TextFilter.InputBuf);
// Build text filter.
m_TextFilter.Build();
}, csys::Arg<csys::String>("filter_str"));
m_ConsoleSystem.RegisterCommand("run", "Run given script", [this](const csys::String& filter)
{
// Logs command.
m_ConsoleSystem.RunScript(filter.m_String);
}, csys::Arg<csys::String>("script_name"));
}
void ImGuiConsole::FilterBar()
{
m_TextFilter.Draw("Filter", ImGui::GetWindowWidth() * 0.25f);
ImGui::Separator();
}
void ImGuiConsole::LogWindow()
{
const float footerHeightToReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginChild("ScrollRegion##", ImVec2(0, -footerHeightToReserve), false, 0))
{
// Display colored command output.
static const float timestamp_width = ImGui::CalcTextSize("00:00:00:0000").x; // Timestamp.
int count = 0; // Item count.
// Wrap items.
ImGui::PushTextWrapPos();
// Display items.
for (const auto& item : m_ConsoleSystem.Items())
{
// Exit if word is filtered.
if (!m_TextFilter.PassFilter(item.Get().c_str()))
continue;
// Spacing between commands.
if (item.m_Type == csys::eCOMMAND)
{
if (m_TimeStamps) ImGui::PushTextWrapPos(ImGui::GetColumnWidth() - timestamp_width); // Wrap before timestamps start.
if (count++ != 0) ImGui::Dummy(ImVec2(-1, ImGui::GetFontSize())); // No space for the first command.
}
// Items.
if (m_ColoredOutput)
{
ImGui::PushStyleColor(ImGuiCol_Text, m_ColorPalette[item.m_Type]);
ImGui::TextUnformatted(item.Get().data());
ImGui::PopStyleColor();
} else
{
ImGui::TextUnformatted(item.Get().data());
}
// Time stamp.
if (item.m_Type == csys::eCOMMAND && m_TimeStamps)
{
// No wrap for timestamps
ImGui::PopTextWrapPos();
// Right align.
ImGui::SameLine(ImGui::GetColumnWidth(-1) - timestamp_width);
// Draw time stamp.
ImGui::PushStyleColor(ImGuiCol_Text, m_ColorPalette[COL_TIMESTAMP]);
ImGui::Text("%02d:%02d:%02d:%04d", ((item.m_TimeStamp / 1000 / 3600) % 24), ((item.m_TimeStamp / 1000 / 60) % 60),
((item.m_TimeStamp / 1000) % 60), item.m_TimeStamp % 1000);
ImGui::PopStyleColor();
}
}
// Stop wrapping since we are done displaying console items.
ImGui::PopTextWrapPos();
// Auto-scroll logs.
if ((m_ScrollToBottom && (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() || m_AutoScroll)))
ImGui::SetScrollHereY(1.0f);
m_ScrollToBottom = false;
// Loop through command string vector.
ImGui::EndChild();
}
}
void ImGuiConsole::InputBar()
{
// Variables.
ImGuiInputTextFlags inputTextFlags =
ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_CallbackCompletion |
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackAlways;
// Only reclaim after enter key is pressed!
bool reclaimFocus = false;
// Input widget. (Width an always fixed width)
ImGui::PushItemWidth(-ImGui::GetStyle().ItemSpacing.x * 7);
if (ImGui::InputText("Input", &m_Buffer, inputTextFlags, InputCallback, this))
{
// Validate.
if (!m_Buffer.empty())
{
// Run command line input.
m_ConsoleSystem.RunCommand(m_Buffer);
// Scroll to bottom after its ran.
m_ScrollToBottom = true;
}
// Keep focus.
reclaimFocus = true;
// Clear command line.
m_Buffer.clear();
}
ImGui::PopItemWidth();
// Reset suggestions when client provides char input.
if (ImGui::IsItemEdited() && !m_WasPrevFrameTabCompletion)
{
m_CmdSuggestions.clear();
}
m_WasPrevFrameTabCompletion = false;
// Auto-focus on window apparition
ImGui::SetItemDefaultFocus();
if (reclaimFocus)
ImGui::SetKeyboardFocusHere(-1); // Focus on command line after clearing.
}
void ImGuiConsole::MenuBar()
{
if (ImGui::BeginMenuBar())
{
// Settings menu.
if (ImGui::BeginMenu("Settings"))
{
// Colored output
ImGui::Checkbox("Colored Output", &m_ColoredOutput);
ImGui::SameLine();
HelpMaker("Enable colored command output");
// Auto Scroll
ImGui::Checkbox("Auto Scroll", &m_AutoScroll);
ImGui::SameLine();
HelpMaker("Automatically scroll to bottom of console log");
// Filter bar
ImGui::Checkbox("Filter Bar", &m_FilterBar);
ImGui::SameLine();
HelpMaker("Enable console filter bar");
// Time stamp
ImGui::Checkbox("Time Stamps", &m_TimeStamps);
ImGui::SameLine();
HelpMaker("Display command execution timestamps");
// Reset to default settings
if (ImGui::Button("Reset settings", ImVec2(ImGui::GetColumnWidth(), 0)))
ImGui::OpenPopup("Reset Settings?");
// Confirmation
if (ImGui::BeginPopupModal("Reset Settings?", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("All settings will be reset to default.\nThis operation cannot be undone!\n\n");
ImGui::Separator();
if (ImGui::Button("Reset", ImVec2(120, 0)))
{
DefaultSettings();
ImGui::CloseCurrentPopup();
}
ImGui::SetItemDefaultFocus();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0)))
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::EndMenu();
}
// View settings.
if (ImGui::BeginMenu("Appearance"))
{
// Logging Colors
ImGuiColorEditFlags flags =
ImGuiColorEditFlags_Float | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar;
ImGui::TextUnformatted("Color Palette");
ImGui::Indent();
ImGui::ColorEdit4("Command##", (float*)&m_ColorPalette[COL_COMMAND], flags);
ImGui::ColorEdit4("Log##", (float*)&m_ColorPalette[COL_LOG], flags);
ImGui::ColorEdit4("Warning##", (float*)&m_ColorPalette[COL_WARNING], flags);
ImGui::ColorEdit4("Error##", (float*)&m_ColorPalette[COL_ERROR], flags);
ImGui::ColorEdit4("Info##", (float*)&m_ColorPalette[COL_INFO], flags);
ImGui::ColorEdit4("Time Stamp##", (float*)&m_ColorPalette[COL_TIMESTAMP], flags);
ImGui::Unindent();
ImGui::Separator();
// Window transparency.
ImGui::TextUnformatted("Background");
ImGui::SliderFloat("Transparency##", &m_WindowAlpha, 0.1f, 1.f);
ImGui::EndMenu();
}
// All scripts.
if (ImGui::BeginMenu("Scripts"))
{
// Show registered scripts.
for (const auto& scr_pair : m_ConsoleSystem.Scripts())
{
if (ImGui::MenuItem(scr_pair.first.c_str()))
{
m_ConsoleSystem.RunScript(scr_pair.first);
m_ScrollToBottom = true;
}
}
// Reload scripts.
ImGui::Separator();
if (ImGui::Button("Reload Scripts", ImVec2(ImGui::GetColumnWidth(), 0)))
{
for (const auto& scr_pair : m_ConsoleSystem.Scripts())
{
scr_pair.second->Reload();
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
}
// From imgui_demo.cpp
void ImGuiConsole::HelpMaker(const char* desc)
{
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
int ImGuiConsole::InputCallback(ImGuiInputTextCallbackData* data)
{
// Exit if no buffer.
if (data->BufTextLen == 0 && (data->EventFlag != ImGuiInputTextFlags_CallbackHistory))
return 0;
// Get input string and console.
std::string input_str = data->Buf;
std::string trim_str;
auto console = static_cast<ImGuiConsole*>(data->UserData);
// Optimize by only using positions.
// Trim start and end spaces.
size_t startPos = console->m_Buffer.find_first_not_of(' ');
size_t endPos = console->m_Buffer.find_last_not_of(' ');
// Get trimmed string.
if (startPos != std::string::npos && endPos != std::string::npos)
trim_str = console->m_Buffer.substr(startPos, endPos + 1);
else
trim_str = console->m_Buffer;
switch (data->EventFlag)
{
case ImGuiInputTextFlags_CallbackCompletion:
{
// Find last word.
size_t startSubtrPos = trim_str.find_last_of(' ');
csys::AutoComplete* console_autocomplete;
// Command line is an entire word/string (No whitespace)
// Determine which autocomplete tree to use.
if (startSubtrPos == std::string::npos)
{
startSubtrPos = 0;
console_autocomplete = &console->m_ConsoleSystem.CmdAutocomplete();
} else
{
startSubtrPos += 1;
console_autocomplete = &console->m_ConsoleSystem.VarAutocomplete();
}
// Validate str
if (!trim_str.empty())
{
// Display suggestions on console.
if (!console->m_CmdSuggestions.empty())
{
console->m_ConsoleSystem.Log(csys::eCOMMAND) << "Suggestions: " << csys::endl;
for (const auto& suggestion : console->m_CmdSuggestions)
console->m_ConsoleSystem.Log(csys::eLOG) << suggestion << csys::endl;
console->m_CmdSuggestions.clear();
}
// Get partial completion and suggestions.
std::string partial = console_autocomplete->Suggestions(trim_str.substr(startSubtrPos, endPos + 1), console->m_CmdSuggestions);
// Autocomplete only when one work is available.
if (!console->m_CmdSuggestions.empty() && console->m_CmdSuggestions.size() == 1)
{
data->DeleteChars(static_cast<int>(startSubtrPos), static_cast<int>(data->BufTextLen - startSubtrPos));
data->InsertChars(static_cast<int>(startSubtrPos), console->m_CmdSuggestions[0].data());
console->m_CmdSuggestions.clear();
} else
{
// Partially complete word.
if (!partial.empty())
{
data->DeleteChars(static_cast<int>(startSubtrPos), static_cast<int>(data->BufTextLen - startSubtrPos));
data->InsertChars(static_cast<int>(startSubtrPos), partial.data());
}
}
}
// We have performed the completion event.
console->m_WasPrevFrameTabCompletion = true;
}
break;
case ImGuiInputTextFlags_CallbackHistory:
{
// Clear buffer.
data->DeleteChars(0, data->BufTextLen);
// Init history index
if (console->m_HistoryIndex == std::numeric_limits<size_t>::min())
console->m_HistoryIndex = console->m_ConsoleSystem.History().GetNewIndex();
// Traverse history.
if (data->EventKey == ImGuiKey_UpArrow)
{
if (console->m_HistoryIndex) --(console->m_HistoryIndex);
} else
{
if (console->m_HistoryIndex < console->m_ConsoleSystem.History().Size()) ++(console->m_HistoryIndex);
}
// Get history.
std::string prevCommand = console->m_ConsoleSystem.History()[console->m_HistoryIndex];
// Insert commands.
data->InsertChars(data->CursorPos, prevCommand.data());
}
break;
case ImGuiInputTextFlags_CallbackCharFilter:
case ImGuiInputTextFlags_CallbackAlways:
default:
break;
}
return 0;
}
void ImGuiConsole::SettingsHandler_ClearALl(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
{
}
void ImGuiConsole::SettingsHandler_ReadInit(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
{
}
void* ImGuiConsole::SettingsHandler_ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name)
{
if (!handler->UserData)
return nullptr;
auto console = static_cast<ImGuiConsole*>(handler->UserData);
if (strcmp(name, console->m_ConsoleName.c_str()) != 0)
return nullptr;
return (void*)1;
}
void ImGuiConsole::SettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line)
{
if (!handler->UserData)
return;
// Get console.
auto console = static_cast<ImGuiConsole*>(handler->UserData);
// Ensure console doesn't reset variables.
console->m_LoadedFromIni = true;
// Disable warning regarding sscanf when using MVSC
#pragma warning( push )
#pragma warning( disable:4996 )
#define INI_CONSOLE_LOAD_COLOR(type) (std::sscanf(line, #type"=%i,%i,%i,%i", &r, &g, &b, &a) == 4) { console->m_ColorPalette[type] = ImColor(r, g, b, a); }
#define INI_CONSOLE_LOAD_FLOAT(var) (std::sscanf(line, #var"=%f", &f) == 1) { console->var = f; }
#define INI_CONSOLE_LOAD_BOOL(var) (std::sscanf(line, #var"=%i", &b) == 1) {console->var = b == 1;}
float f;
int r, g, b, a;
// Window style/visuals
if INI_CONSOLE_LOAD_COLOR(COL_COMMAND)
else if INI_CONSOLE_LOAD_COLOR(COL_LOG)
else if INI_CONSOLE_LOAD_COLOR(COL_WARNING)
else if INI_CONSOLE_LOAD_COLOR(COL_ERROR)
else if INI_CONSOLE_LOAD_COLOR(COL_INFO)
else if INI_CONSOLE_LOAD_COLOR(COL_TIMESTAMP)
else if INI_CONSOLE_LOAD_FLOAT(m_WindowAlpha)
// Window settings
else if INI_CONSOLE_LOAD_BOOL(m_AutoScroll)
else if INI_CONSOLE_LOAD_BOOL(m_ScrollToBottom)
else if INI_CONSOLE_LOAD_BOOL(m_ColoredOutput)
else if INI_CONSOLE_LOAD_BOOL(m_FilterBar)
else if INI_CONSOLE_LOAD_BOOL(m_TimeStamps)
#pragma warning( pop )
}
void ImGuiConsole::SettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
{
if (!handler->UserData)
return;
}
void ImGuiConsole::SettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
{
if (!handler->UserData)
return;
// Get console.
auto console = static_cast<ImGuiConsole*>(handler->UserData);
#define INI_CONSOLE_SAVE_COLOR(type) buf->appendf(#type"=%i,%i,%i,%i\n", (int)(console->m_ColorPalette[type].x * 255),\
(int)(console->m_ColorPalette[type].y * 255),\
(int)(console->m_ColorPalette[type].z * 255),\
(int)(console->m_ColorPalette[type].w * 255))
#define INI_CONSOLE_SAVE_FLOAT(var) buf->appendf(#var"=%.3f\n", console->var)
#define INI_CONSOLE_SAVE_BOOL(var) buf->appendf(#var"=%i\n", console->var)
// Set header for CONSOLE Console
buf->appendf("[%s][%s]\n", handler->TypeName, console->m_ConsoleName.data());
// Window settings.
INI_CONSOLE_SAVE_BOOL(m_AutoScroll);
INI_CONSOLE_SAVE_BOOL(m_ScrollToBottom);
INI_CONSOLE_SAVE_BOOL(m_ColoredOutput);
INI_CONSOLE_SAVE_BOOL(m_FilterBar);
INI_CONSOLE_SAVE_BOOL(m_TimeStamps);
// Window style/visuals
INI_CONSOLE_SAVE_FLOAT(m_WindowAlpha);
INI_CONSOLE_SAVE_COLOR(COL_COMMAND);
INI_CONSOLE_SAVE_COLOR(COL_LOG);
INI_CONSOLE_SAVE_COLOR(COL_WARNING);
INI_CONSOLE_SAVE_COLOR(COL_ERROR);
INI_CONSOLE_SAVE_COLOR(COL_INFO);
INI_CONSOLE_SAVE_COLOR(COL_TIMESTAMP);
// End saving.
buf->append("\n");
}

View File

@ -0,0 +1,112 @@
// Copyright (c) 2020 - present, Roland Munguia
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#ifndef IMGUI_CONSOLE_H
#define IMGUI_CONSOLE_H
#pragma once
#include "csys/system.h"
#include "imgui.h"
#include <array>
struct ImGuiSettingsHandler;
class ImGuiConsole
{
public:
/*!
* \brief Construct an imgui console
* \param c_name Name of the console
* \param inputBufferSize Maximum input buffer size
*/
explicit ImGuiConsole(std::string c_name = "imgui-console", size_t inputBufferSize = 256);
/*!
* \brief Render the Dear ImGui Console
*/
void Draw();
/*!
* \brief Console system which handles the console functionality (Logging, Commands, History, Scripts, etc).
* \return System Obj
*/
csys::System& System();
protected:
// Console ////////////////////////////////////////////////////////////////
csys::System m_ConsoleSystem; //!< Main console system.
size_t m_HistoryIndex; //!< Command history index.
// Dear ImGui ////////////////////////////////////////////////////////////
// Main
std::string m_Buffer; //!< Input buffer.
std::string m_ConsoleName; //!< Console name string buffer.
ImGuiTextFilter m_TextFilter; //!< Logging filer.
bool m_AutoScroll; //!< Auto scroll flag.
bool m_ColoredOutput; //!< Colored output flag.
bool m_ScrollToBottom; //!< Scroll to bottom after is command is ran
bool m_FilterBar; //!< Filter bar flag.
bool m_TimeStamps; //!< Display time stamps flag
void InitIniSettings(); //!< Initialize Ini Settings handler
void DefaultSettings(); //!< Restore console default settings
void RegisterConsoleCommands(); //!< Register built-in console commands
void MenuBar(); //!< Console menu bar
void FilterBar(); //!< Console filter bar
void InputBar(); //!< Console input bar
void LogWindow(); //!< Console log
static void HelpMaker(const char* desc);
// Window appearance.
float m_WindowAlpha; //!< Window transparency
enum COLOR_PALETTE
{
// This four have to match the csys item type enum.
COL_COMMAND = 0, //!< Color for command logs
COL_LOG, //!< Color for in-command logs
COL_WARNING, //!< Color for warnings logs
COL_ERROR, //!< Color for error logs
COL_INFO, //!< Color for info logs
COL_TIMESTAMP, //!< Color for timestamps
COL_COUNT //!< For bookkeeping purposes
};
std::array<ImVec4, COL_COUNT> m_ColorPalette; //!< Container for all available colors
// ImGui Console Window.
static int InputCallback(ImGuiInputTextCallbackData* data); //!< Console input callback
bool m_WasPrevFrameTabCompletion = false; //!< Flag to determine if previous input was a tab completion
std::vector<std::string> m_CmdSuggestions; //!< Holds command suggestions from partial completion
// Save data inside .ini
bool m_LoadedFromIni = false;
static void SettingsHandler_ClearALl(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
static void SettingsHandler_ReadInit(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
static void* SettingsHandler_ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name);
static void SettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line);
static void SettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
static void SettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
///////////////////////////////////////////////////////////////////////////
};
#endif //IMGUI_CONSOLE_H

View File

@ -21,7 +21,7 @@ int main(int argc, char* argv[]) {
auto in = win.getInput(); auto in = win.getInput();
Renderer ren(win); Renderer ren(win);
while (!in->shouldClose()) { while (!in->shouldClose() && !ren.should_close) {
Timer frame_timer; Timer frame_timer;
in->poll(); in->poll();
in->handleMovementKeys(ren); in->handleMovementKeys(ren);
@ -33,9 +33,8 @@ int main(int argc, char* argv[]) {
switch (event.tag) { switch (event.tag) {
case InputEvent::Tag::eRESIZE: case InputEvent::Tag::eRESIZE:
Log::info("Event Processed: Resized to %dx%d\n", event.resize.width, event.resize.height); Log::info("Event Processed: Resized to %dx%d\n", event.resize.width, event.resize.height);
/* no need to have a resize() function in the renderer, b/c swapchain images will be
* automatically marked out-of-date, and recreation will be triggered in our code ren.swapchain->recreate();
*/
/* but still block while waiting for window to be opened again */ /* but still block while waiting for window to be opened again */
if (event.resize.height == 0 || event.resize.width == 0) { if (event.resize.height == 0 || event.resize.width == 0) {
int h = event.resize.height; int h = event.resize.height;
@ -56,11 +55,14 @@ int main(int argc, char* argv[]) {
case InputEvent::Tag::eBUTTON: case InputEvent::Tag::eBUTTON:
break; break;
case InputEvent::Tag::eKEY: case InputEvent::Tag::eKEY:
if (event.key.key == GLFW_KEY_Q) { if (event.key.key == GLFW_KEY_ESCAPE && event.key.state == GLFW_PRESS) {
return 0; ren.in_menu = !ren.in_menu;
} else if (event.key.key == GLFW_KEY_ESCAPE && event.key.state == GLFW_PRESS) { in->setCursor(ren.in_menu);
ren.capture_mouse = !ren.capture_mouse; } else if (event.key.key == GLFW_KEY_Q && event.key.state == GLFW_PRESS) {
in->setCursor(!ren.capture_mouse); if (!ren.in_menu) {
ren.should_close = true;
goto quit;
}
} else if (event.key.key == GLFW_KEY_R && event.key.state == GLFW_PRESS) { } else if (event.key.key == GLFW_KEY_R && event.key.state == GLFW_PRESS) {
ren.time = 0; ren.time = 0;
} else if (event.key.key == GLFW_KEY_C && event.key.state == GLFW_PRESS) { } else if (event.key.key == GLFW_KEY_C && event.key.state == GLFW_PRESS) {
@ -78,14 +80,19 @@ int main(int argc, char* argv[]) {
ren.draw(); ren.draw();
ren.present(); ren.present();
const auto t = frame_timer.read(); ren.frametime = frame_timer.read();
ren.ui->info.fps = 1000.0f / t; ren.fps = 1000.0f / ren.frametime;
while (frame_timer.read() < 16.60) while (frame_timer.read() < 1000.0 / ren.max_fps)
; ;
ren.frametime = frame_timer.read();
} }
} catch (const std::string& e) { } catch (const std::string& e) {
std::cerr << "Exception: " << e << std::endl; std::cerr << "Exception: " << e << std::endl;
} }
quit:
Log::info("Quitting\n");
} }

11
util/glsl_types.hpp Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <glm/glm.hpp>
namespace glsl {
typedef uint32_t uint;
typedef glm::vec1 vec1;
typedef glm::vec2 vec2;
typedef glm::vec3 vec3;
typedef glm::vec4 vec4;
}

View File

@ -33,10 +33,32 @@ namespace Log {
level_txt = "[DEBUG] "; level_txt = "[DEBUG] ";
break; break;
} }
std::fprintf(stderr, level_txt); std::fputs(level_txt, stderr);
std::fprintf(stderr, fmt.c_str(), args...); std::fprintf(stderr, fmt.c_str(), args...);
} }
static void print(MessageLevelBit level, const std::string& str) {
if (!(log_mask & level))
return;
/* appearently C++ doesn't have designated array indices :( */
const char* level_txt = "[UNKNOWN] ";
switch (level) {
case eERROR:
level_txt = "[ERROR] ";
break;
case eINFO:
level_txt = "[INFO] ";
break;
case eDEBUG:
level_txt = "[DEBUG] ";
break;
}
std::fputs(level_txt, stderr);
std::fprintf(stderr, "%s", str.c_str());
}
template<typename ...Args> template<typename ...Args>
static void error(const std::string& fmt, Args... args) { static void error(const std::string& fmt, Args... args) {
print(MessageLevelBit::eERROR, fmt, args...); print(MessageLevelBit::eERROR, fmt, args...);
@ -54,4 +76,4 @@ namespace Log {
static void debug(const std::string& fmt, Args... args) { static void debug(const std::string& fmt, Args... args) {
print(MessageLevelBit::eDEBUG, fmt, args...); print(MessageLevelBit::eDEBUG, fmt, args...);
} }
} }