From 08602f926bf97552864e37181482cd428873af4a Mon Sep 17 00:00:00 2001 From: Ben McAvoy Date: Mon, 4 Nov 2024 23:41:22 +0000 Subject: [PATCH] feat: console window in editor --- README.md | 11 ++-- engine/include/jenjin/editor/editor.h | 5 +- engine/include/jenjin/engine.h | 8 ++- engine/include/jenjin/log.h | 82 ++++++++++++++++++++++++++ engine/include/jenjin/targets/editor.h | 6 +- engine/src/editor/editor.cpp | 74 +++++++++++++++++++++-- engine/src/engine.cpp | 2 + engine/src/log.cpp | 47 +++++++++++++++ engine/src/luamanager.cpp | 24 ++++---- engine/src/targets/editor.cpp | 51 ++++++++-------- jenjin/src/main.cpp | 27 ++++++--- 11 files changed, 275 insertions(+), 62 deletions(-) create mode 100644 engine/include/jenjin/log.h create mode 100644 engine/src/log.cpp diff --git a/README.md b/README.md index fd3f168..5c56625 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,21 @@ Come back later, sorry! Jenjin is not ready for real usage. - [x] Icons everywhere - [x] Code editor in the editor - [x] Dynamic data attached to game objects at runtime +- [x] Console window in the editor showing output from the engine - [ ] Improved meshing algorithm that gives same input meshes all one mesh reference instead of making multiple mesh references and data for them. - [ ] Hierarchy and `.Parent` in Lua along with `workspace` (e.g. `Workspace.MyGameObject.Parent` == `Workspace`) -- [ ] Console window in the editor showing output from the engine - [ ] Autocompletion in the editor - [ ] Allow user to create UI from Lua - [ ] Better Explorer, allowing right click and creation of new scripts if on the scripts context menu +- [ ] Serialization/Deserialization of scenes to better formats ## TODO (Future) -- [ ] Serialization/Deserialization of scenes to better formats +- [ ] Physics +- [ ] Installer - [ ] Syntax highlighting in the editor - [ ] Sprite animations -- [ ] Scripting (Python) -- [ ] Physics engine -- [ ] Instancing -- [ ] Installer - [ ] 3D support +- [ ] Scripting (Python) ## License License is in [`LICENSE.md`](LICENSE.md) file and should be read before using this software. (Custom source available license) diff --git a/engine/include/jenjin/editor/editor.h b/engine/include/jenjin/editor/editor.h index 0435866..3b2ec27 100644 --- a/engine/include/jenjin/editor/editor.h +++ b/engine/include/jenjin/editor/editor.h @@ -23,13 +23,14 @@ class Manager { void viewport(Jenjin::Scene *scene); void explorer(Jenjin::Scene *scene); void code(Jenjin::Scene *scene); + void console(Jenjin::Scene *scene); void backup_prompts(Jenjin::Scene *scene); void show_all(Jenjin::Scene *scene); - bool hasProjectOpen = false; - int renderTexture = -1; + bool hasProjectOpen = false; + int renderTexture = -1; private: Jenjin::GameObject *selectedObject = nullptr; diff --git a/engine/include/jenjin/engine.h b/engine/include/jenjin/engine.h index 95892c8..6525a0a 100644 --- a/engine/include/jenjin/engine.h +++ b/engine/include/jenjin/engine.h @@ -1,5 +1,6 @@ #pragma once +#include "jenjin/log.h" #include "jenjin/luamanager.h" #include "jenjin/scene.h" #include "jenjin/target.h" @@ -21,11 +22,16 @@ class Engine { Scene *GetCurrentScene() { return currentScene; } + void SetLogSink(Jenjin::LogSink *sink); + Jenjin::LogSink *GetLogSink() { return logSink; } + private: std::vector> scenes = {}; Scene *currentScene = nullptr; - LuaManager luaManager; + LuaManager luaManager; + + Jenjin::LogSink *logSink = nullptr; }; extern Engine *EngineRef; diff --git a/engine/include/jenjin/log.h b/engine/include/jenjin/log.h new file mode 100644 index 0000000..68ce48e --- /dev/null +++ b/engine/include/jenjin/log.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +namespace Jenjin { +// A logger that writes to stdout and to a std::vector internally for display in +// ImGui +class LogSink : public spdlog::sinks::sink { +public: + LogSink() { + colour_sink = std::make_shared(); + colour_sink->set_level(spdlog::level::trace); + spdlog::set_level(spdlog::level::trace); + spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"); + spdlog::set_pattern("%v"); + } + + void log(const spdlog::details::log_msg &msg) override; + void flush() override {} + void set_pattern(const std::string &pattern) override {} + void + set_formatter(std::unique_ptr sink_formatter) override {} + + std::unordered_map &GetTraceLogs() { + std::lock_guard lock(mutex); + return trace_logs; + } + + std::unordered_map &GetDebugLogs() { + std::lock_guard lock(mutex); + return debug_logs; + } + + std::unordered_map &GetInfoLogs() { + std::lock_guard lock(mutex); + return info_logs; + } + + std::unordered_map &GetWarnLogs() { + std::lock_guard lock(mutex); + return warn_logs; + } + + std::unordered_map &GetErrorLogs() { + std::lock_guard lock(mutex); + return error_logs; + } + + std::unordered_map &GetCriticalLogs() { + std::lock_guard lock(mutex); + return critical_logs; + } + + void ClearLogs() { + std::lock_guard lock(mutex); + trace_logs.clear(); + debug_logs.clear(); + info_logs.clear(); + warn_logs.clear(); + error_logs.clear(); + critical_logs.clear(); + } + + ~LogSink() override {} + +private: + // map: Line number -> details, times printed + + std::unordered_map trace_logs; + std::unordered_map debug_logs; + std::unordered_map info_logs; + std::unordered_map warn_logs; + std::unordered_map error_logs; + std::unordered_map critical_logs; + + std::shared_ptr colour_sink; + + std::mutex mutex; +}; +} // namespace Jenjin diff --git a/engine/include/jenjin/targets/editor.h b/engine/include/jenjin/targets/editor.h index cd3c627..25b5dcd 100644 --- a/engine/include/jenjin/targets/editor.h +++ b/engine/include/jenjin/targets/editor.h @@ -20,14 +20,14 @@ class EditorTarget : public Jenjin::Target { virtual glm::vec2 GetMousePosition() override; virtual bool RespondsToWindowResize() override; - virtual void SetWindowPosition(ImVec2 pos) override; + virtual void SetWindowPosition(ImVec2 pos) override; Jenjin::Framebuffer renderTexture; int width, height; - Jenjin::Editor::Manager editor; + Jenjin::Editor::Manager manager; - ImVec2 pos; + ImVec2 pos; }; } // namespace Targets } // namespace Jenjin diff --git a/engine/src/editor/editor.cpp b/engine/src/editor/editor.cpp index 51a246a..66cb3ff 100644 --- a/engine/src/editor/editor.cpp +++ b/engine/src/editor/editor.cpp @@ -147,17 +147,20 @@ void Manager::dockspace() { auto dock_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f, nullptr, &dockspace_id); - auto dock_left_up = ImGui::DockBuilderSplitNode(dock_left, ImGuiDir_Up, - 0.8f, nullptr, &dock_left); auto dock_right = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.6f, nullptr, &dockspace_id); + auto dock_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, + 0.2f, nullptr, &dockspace_id); auto middle = dockspace_id; static auto hierarchy_title = ICON_FA_SITEMAP " Hierarchy"; - ImGui::DockBuilderDockWindow(hierarchy_title, dock_left_up); + ImGui::DockBuilderDockWindow(hierarchy_title, dock_left); static auto explorer_title = ICON_FA_FOLDER " Explorer"; - ImGui::DockBuilderDockWindow(explorer_title, dock_left); + ImGui::DockBuilderDockWindow(explorer_title, dock_down); + + static auto console_title = ICON_FA_TERMINAL " Console"; + ImGui::DockBuilderDockWindow(console_title, dock_down); static auto inspector_title = ICON_FA_EYE " Inspector"; ImGui::DockBuilderDockWindow(inspector_title, dock_right); @@ -549,6 +552,68 @@ void Manager::code(Jenjin::Scene *scene) { ImGui::End(); } +void Manager::console(Jenjin::Scene *scene) { + static auto sink = Jenjin::EngineRef->GetLogSink(); + + if (sink == nullptr) { + ImGui::Begin("Console", nullptr); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); + ImGui::Text("No sink set"); + ImGui::PopStyleColor(); + ImGui::End(); + return; + } + + static auto title = ICON_FA_TERMINAL " Console"; + ImGui::Begin(title, nullptr, ImGuiWindowFlags_MenuBar); + + if (ImGui::BeginMenuBar()) { + if (ImGui::MenuItem("Clear")) { + spdlog::info("Clearing logs"); + sink->ClearLogs(); + } + + ImGui::EndMenuBar(); + } + + if (ImGui::CollapsingHeader("Trace")) { + auto &trace_logs = sink->GetTraceLogs(); + for (auto &log : sink->GetTraceLogs()) { + ImGui::Text("(%d) %s", log.second, log.first.c_str()); + } + } + + if (ImGui::CollapsingHeader("Info"), ImGuiTreeNodeFlags_DefaultOpen) { + auto &info_logs = sink->GetInfoLogs(); + for (auto &log : sink->GetInfoLogs()) { + ImGui::Text("(%d) %s", log.second, log.first.c_str()); + } + } + + if (ImGui::CollapsingHeader("Warn")) { + auto &warn_logs = sink->GetWarnLogs(); + for (auto &log : sink->GetWarnLogs()) { + ImGui::Text("(%d) %s", log.second, log.first.c_str()); + } + } + + if (ImGui::CollapsingHeader("Error")) { + auto &error_logs = sink->GetErrorLogs(); + for (auto &log : sink->GetErrorLogs()) { + ImGui::Text("(%d) %s", log.second, log.first.c_str()); + } + } + + if (ImGui::CollapsingHeader("Critical")) { + auto &critical_logs = sink->GetCriticalLogs(); + for (auto &log : sink->GetCriticalLogs()) { + ImGui::Text("(%d) %s", log.second, log.first.c_str()); + } + } + + ImGui::End(); +} + void Manager::show_all(Jenjin::Scene *scene) { bool isRunning = this->running; if (isRunning) { @@ -582,6 +647,7 @@ void Manager::show_all(Jenjin::Scene *scene) { explorer(scene); viewport(scene); code(scene); + console(scene); } void Manager::welcome() { diff --git a/engine/src/engine.cpp b/engine/src/engine.cpp index 59c8540..1810a17 100644 --- a/engine/src/engine.cpp +++ b/engine/src/engine.cpp @@ -93,3 +93,5 @@ void Engine::Render(Target *target) { target->PostRender(); } + +void Engine::SetLogSink(Jenjin::LogSink *sink) { this->logSink = sink; } diff --git a/engine/src/log.cpp b/engine/src/log.cpp new file mode 100644 index 0000000..d0bf8bc --- /dev/null +++ b/engine/src/log.cpp @@ -0,0 +1,47 @@ +#include "jenjin/log.h" +#include + +using namespace Jenjin; + +void handle(std::unordered_map &map, + const std::string &data) { + bool exists = map.find(data) != map.end(); + + if (!exists) + map[data] = 1; + + if (exists) + map[data]++; +} + +void LogSink::log(const spdlog::details::log_msg &msg) { + std::lock_guard lock(mutex); + colour_sink->log(msg); + + std::string data = msg.payload.data(); + bool exists = false; + switch (msg.level) { + case spdlog::level::trace: + handle(trace_logs, data); + break; + case spdlog::level::debug: + handle(debug_logs, data); + break; + case spdlog::level::info: + handle(info_logs, data); + break; + case spdlog::level::warn: + handle(warn_logs, data); + break; + case spdlog::level::err: + handle(error_logs, data); + break; + case spdlog::level::critical: + handle(critical_logs, data); + break; + case spdlog::level::off: + break; + default: + break; + } +} diff --git a/engine/src/luamanager.cpp b/engine/src/luamanager.cpp index ff1ea95..311af7d 100644 --- a/engine/src/luamanager.cpp +++ b/engine/src/luamanager.cpp @@ -68,6 +68,18 @@ void Bindings(LuaManager *lm, sol::state &lua) { return ss.str(); }; + lua.set_function("trace", [&](sol::variadic_args va) { + spdlog::trace("[LUA] {}", construct_string(va)); + }); + + lua.set_function("debug", [&](sol::variadic_args va) { + spdlog::debug("[LUA] {}", construct_string(va)); + }); + + lua.set_function("print", [&](sol::variadic_args va) { + spdlog::info("[LUA] {}", construct_string(va)); + }); + lua.set_function("info", [&](sol::variadic_args va) { spdlog::info("[LUA] {}", construct_string(va)); }); @@ -80,22 +92,10 @@ void Bindings(LuaManager *lm, sol::state &lua) { spdlog::error("[LUA] {}", construct_string(va)); }); - lua.set_function("debug", [&](sol::variadic_args va) { - spdlog::debug("[LUA] {}", construct_string(va)); - }); - - lua.set_function("trace", [&](sol::variadic_args va) { - spdlog::trace("[LUA] {}", construct_string(va)); - }); - lua.set_function("critical", [&](sol::variadic_args va) { spdlog::critical("[LUA] {}", construct_string(va)); }); - lua.set_function("print", [&](sol::variadic_args va) { - spdlog::info("[LUA] {}", construct_string(va)); - }); - // ======================================================================== // Userdata // ======================================================================== diff --git a/engine/src/targets/editor.cpp b/engine/src/targets/editor.cpp index fdd05ae..4eebf50 100644 --- a/engine/src/targets/editor.cpp +++ b/engine/src/targets/editor.cpp @@ -16,7 +16,7 @@ using namespace Jenjin::Targets; EditorTarget::EditorTarget() { - editor.renderTexture = this->renderTexture.texture; + manager.renderTexture = this->renderTexture.texture; } void EditorTarget::PreRender() { @@ -26,19 +26,18 @@ void EditorTarget::PreRender() { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - this->editor.show_all(Jenjin::EngineRef->GetCurrentScene()); + manager.show_all(Jenjin::EngineRef->GetCurrentScene()); renderTexture.Bind(); - static auto bg = Jenjin::EngineRef->GetCurrentScene()->GetCamera()->GetBackgroundPointer(); + static auto bg = + Jenjin::EngineRef->GetCurrentScene()->GetCamera()->GetBackgroundPointer(); glClearColor(bg->r, bg->g, bg->b, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, width, height); } -void EditorTarget::Render() { - renderTexture.Unbind(); -} +void EditorTarget::Render() { renderTexture.Unbind(); } void EditorTarget::PostRender() { ImGui::EndFrame(); @@ -56,24 +55,23 @@ void EditorTarget::Resize(glm::vec2 size) { Jenjin::EngineRef->GetCurrentScene()->GetCamera()->Resize(size); } -void EditorTarget::SetWindowPosition(ImVec2 pos) { - this->pos = pos; -} +void EditorTarget::SetWindowPosition(ImVec2 pos) { this->pos = pos; } -glm::vec2 positionToNDC(glm::vec2 pos, Jenjin::Camera* camera) { - // Get camera position - glm::vec2 cameraPos = glm::vec2(camera->GetPosition().x, camera->GetPosition().y); - glm::vec2 newPos = pos + cameraPos; +glm::vec2 positionToNDC(glm::vec2 pos, Jenjin::Camera *camera) { + // Get camera position + glm::vec2 cameraPos = + glm::vec2(camera->GetPosition().x, camera->GetPosition().y); + glm::vec2 newPos = pos + cameraPos; - // Get projection matrix (with viewport - auto viewproj = camera->GetViewProjection(); + // Get projection matrix (with viewport + auto viewproj = camera->GetViewProjection(); - auto aspect = camera->size.x / camera->size.y; + auto aspect = camera->size.x / camera->size.y; - // Get the NDC coordinates - auto ndc = glm::vec2(viewproj * glm::vec4(newPos, 0.0f, 1.0f)); - ndc.x *= aspect; - return ndc; + // Get the NDC coordinates + auto ndc = glm::vec2(viewproj * glm::vec4(newPos, 0.0f, 1.0f)); + ndc.x *= aspect; + return ndc; } glm::vec2 EditorTarget::GetMousePosition() { @@ -81,18 +79,19 @@ glm::vec2 EditorTarget::GetMousePosition() { static double gx, gy; glfwGetCursorPos(ctx, &gx, &gy); - static ImVec2 windowPadding = ImGui::GetStyle().WindowPadding; + static ImVec2 windowPadding = ImGui::GetStyle().WindowPadding; auto lx = gx - (this->pos.x + windowPadding.x); auto ly = this->height - (gy - (this->pos.y + windowPadding.y * 4 - 2)); - auto centered = glm::vec2(lx - (float)this->width / 2, ly - (float)this->height / 2); + auto centered = + glm::vec2(lx - (float)this->width / 2, ly - (float)this->height / 2); - // Convert it to NDC - static auto camera = Jenjin::EngineRef->GetCurrentScene()->GetCamera(); - auto ndc = positionToNDC(centered, camera); + // Convert it to NDC + static auto camera = Jenjin::EngineRef->GetCurrentScene()->GetCamera(); + auto ndc = positionToNDC(centered, camera); - return glm::vec2(ndc); + return glm::vec2(ndc); /*return glm::vec2(lx, ly);*/ } diff --git a/jenjin/src/main.cpp b/jenjin/src/main.cpp index 233a11d..c59a49f 100644 --- a/jenjin/src/main.cpp +++ b/jenjin/src/main.cpp @@ -1,3 +1,4 @@ +#include "jenjin/log.h" #define GLFW_INCLUDE_NONE #include "jenjin/editor/utils.h" @@ -10,8 +11,8 @@ #include -#include #include +#include #include #define LAUNCH_ARGS \ @@ -21,7 +22,14 @@ void launchEditor(LAUNCH_ARGS); void launchRuntime(LAUNCH_ARGS); +Jenjin::LogSink *g_logSink = nullptr; + int main(int argc, char *argv[]) { + auto logSink = std::make_shared(); + auto logger = std::make_shared("default", logSink); + g_logSink = logSink.get(); + spdlog::set_default_logger(logger); + bool editor = true; // We use the editor by default for (int i = 0; i < argc; i++) { @@ -35,10 +43,11 @@ int main(int argc, char *argv[]) { Jenjin::Helpers::InitiateImGui(window); Jenjin::Engine engine(window); + auto scene = std::make_shared(); engine.AddScene(scene, true); - spdlog::info("Launching Jenjin {}", editor ? "Editor" : "Runtime"); + spdlog::info("Launching Jenjin {}", editor ? "Editor" : "Runtime"); (editor ? launchEditor : launchRuntime)(engine, window, scene); } @@ -48,6 +57,8 @@ void launchEditor(LAUNCH_ARGS) { auto editor = Jenjin::Targets::EditorTarget(); scene->SetTarget(&editor); + engine.SetLogSink(g_logSink); + while (!glfwWindowShouldClose(window)) { engine.Render(&editor); @@ -60,12 +71,12 @@ void launchRuntime(LAUNCH_ARGS) { auto runtime = Jenjin::Targets::RuntimeTarget(); scene->SetTarget(&runtime); - if (!std::filesystem::exists("scripts")) { - spdlog::error("Could not find `scripts` directory"); - exit(1); - } + if (!std::filesystem::exists("scripts")) { + spdlog::error("Could not find `scripts` directory"); + exit(1); + } - scene->GetLuaManager()->LoadDirectory("scripts"); + scene->GetLuaManager()->LoadDirectory("scripts"); // Look for `main.jenscene` in the current directory if (!std::filesystem::exists("main.jenscene")) { @@ -74,7 +85,7 @@ void launchRuntime(LAUNCH_ARGS) { } scene->Load("main.jenscene"); - scene->GetLuaManager()->Ready(); + scene->GetLuaManager()->Ready(); while (!glfwWindowShouldClose(window)) { engine.Render(&runtime);