diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt index c4b7cf6d30..9de02d8dd6 100644 --- a/source/base/CMakeLists.txt +++ b/source/base/CMakeLists.txt @@ -29,7 +29,6 @@ target_sources(tactile-base "${PROJECT_SOURCE_DIR}/inc/tactile/base/io/byte_stream.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/base/numeric/saturate_cast.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/base/numeric/sign_cast.hpp" - "${PROJECT_SOURCE_DIR}/inc/tactile/base/plugin/plugin.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/base/render/renderer.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/base/render/texture.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/base/render/window.hpp" diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 460650de61..5309702ea4 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -133,8 +133,6 @@ target_sources(tactile-core "${PROJECT_SOURCE_DIR}/inc/tactile/core/numeric/vec_common.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/numeric/vec_format.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/numeric/vec_stream.hpp" - "${PROJECT_SOURCE_DIR}/inc/tactile/core/persist/protobuf_context.hpp" - "${PROJECT_SOURCE_DIR}/src/tactile/core/persist/protobuf_context.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/platform/bits.hpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/platform/dynamic_library.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/core/platform/dynamic_library.cpp" @@ -144,8 +142,6 @@ target_sources(tactile-core "${PROJECT_SOURCE_DIR}/src/tactile/core/platform/file_dialog.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/platform/filesystem.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/core/platform/filesystem.cpp" - "${PROJECT_SOURCE_DIR}/inc/tactile/core/platform/sdl_context.hpp" - "${PROJECT_SOURCE_DIR}/src/tactile/core/platform/sdl_context.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/platform/win32.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/core/platform/win32.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/tile/animation.hpp" @@ -227,8 +223,6 @@ target_sources(tactile-core "${PROJECT_SOURCE_DIR}/inc/tactile/core/ui/fonts.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/core/ui/fonts.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/ui/imgui_compat.hpp" - "${PROJECT_SOURCE_DIR}/inc/tactile/core/ui/imgui_context.hpp" - "${PROJECT_SOURCE_DIR}/src/tactile/core/ui/imgui_context.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/ui/shortcuts.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/core/ui/shortcuts.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/core/ui/theme.hpp" diff --git a/source/core/inc/tactile/core/ui/imgui_context.hpp b/source/core/inc/tactile/core/ui/imgui_context.hpp deleted file mode 100644 index f1558b4288..0000000000 --- a/source/core/inc/tactile/core/ui/imgui_context.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) - -#pragma once - -#include "tactile/base/container/smart_ptr.hpp" -#include "tactile/base/prelude.hpp" - -struct ImGuiContext; - -namespace tactile::ui { - -struct ImGuiContextDeleter final -{ - void operator()(ImGuiContext* context) noexcept; -}; - -using UniqueImGuiContext = Unique; - -} // namespace tactile::ui diff --git a/source/core/src/tactile/core/ui/imgui_context.cpp b/source/core/src/tactile/core/ui/imgui_context.cpp deleted file mode 100644 index 5af2d4d99b..0000000000 --- a/source/core/src/tactile/core/ui/imgui_context.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) - -#include "tactile/core/ui/imgui_context.hpp" - -#include - -namespace tactile::ui { - -void ImGuiContextDeleter::operator()(ImGuiContext* context) noexcept -{ - ImGui::DestroyContext(context); -} - -} // namespace tactile::ui diff --git a/source/main/CMakeLists.txt b/source/main/CMakeLists.txt index c2a8f626aa..f45d824e92 100644 --- a/source/main/CMakeLists.txt +++ b/source/main/CMakeLists.txt @@ -14,8 +14,6 @@ target_include_directories(tactile PUBLIC "${PROJECT_SOURCE_DIR}/inc") target_link_libraries(tactile PRIVATE tactile::base - tactile::core tactile::runtime - SDL2::SDL2 SDL2::SDL2main ) diff --git a/source/main/src/tactile/main.cpp b/source/main/src/tactile/main.cpp index 80916f67dd..48f6553389 100644 --- a/source/main/src/tactile/main.cpp +++ b/source/main/src/tactile/main.cpp @@ -1,178 +1,10 @@ // Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) -#include // EXIT_SUCCESS, EXIT_FAILURE, malloc, free -#include // exception, set_terminate -#include // make_unique -#include // move - #include -#include - -#include "tactile/base/int.hpp" -#include "tactile/base/prelude.hpp" -#include "tactile/base/render/renderer.hpp" -#include "tactile/base/util/chrono.hpp" -#include "tactile/core/debug/exception.hpp" -#include "tactile/core/debug/terminate.hpp" -#include "tactile/core/engine/engine.hpp" -#include "tactile/core/log/logger.hpp" -#include "tactile/core/log/set_log_scope.hpp" -#include "tactile/core/log/terminal_log_sink.hpp" -#include "tactile/core/numeric/random.hpp" -#include "tactile/core/persist/protobuf_context.hpp" -#include "tactile/core/platform/dynamic_library.hpp" -#include "tactile/core/platform/sdl_context.hpp" -#include "tactile/core/platform/win32.hpp" -#include "tactile/core/tactile_editor.hpp" -#include "tactile/core/ui/common/style.hpp" -#include "tactile/core/ui/imgui_context.hpp" -#include "tactile/core/util/scope_guard.hpp" -#include "tactile/runtime/window.hpp" - -namespace tactile { - -struct RendererFunctions final -{ - using prepare_renderer_t = void(uint32*); - using make_renderer_t = IRenderer*(IWindow*, ImGuiContext*); - using free_renderer_t = void(IRenderer*); - - Unique lib; - prepare_renderer_t* prepare_renderer; - make_renderer_t* make_renderer; - free_renderer_t* free_renderer; -}; - -using UniqueRenderer = Unique; - -[[nodiscard]] -auto _load_renderer_functions(const Path& libpath) -> Maybe -{ - RendererFunctions functions {}; - - functions.lib = load_library(libpath); - if (!functions.lib) { - TACTILE_LOG_ERROR("Could not load renderer library"); - return kNone; - } - - functions.prepare_renderer = - find_symbol( - *functions.lib, - "tactile_prepare_renderer"); - functions.make_renderer = - find_symbol(*functions.lib, - "tactile_make_renderer"); - functions.free_renderer = - find_symbol(*functions.lib, - "tactile_free_renderer"); - - if (!functions.prepare_renderer || !functions.make_renderer || - !functions.free_renderer) { - TACTILE_LOG_ERROR("Could not load renderer functions"); - return kNone; - } - - return functions; -} - -[[nodiscard]] -auto _make_logger() -> Logger -{ - win32_enable_virtual_terminal_processing(); - - auto terminal_sink = std::make_unique(); - terminal_sink->use_ansi_colors(true); - - Logger logger {}; - - logger.set_reference_instant(SteadyClock::now()); - logger.set_min_level(LogLevel::kTrace); - logger.add_sink(std::move(terminal_sink)); - - return logger; -} - -[[nodiscard]] -auto _run() -> int -{ - std::set_terminate(&on_terminate); - - auto logger = _make_logger(); - set_default_logger(&logger); - const ScopeGuard logger_guard {[] { set_default_logger(nullptr); }}; - const SetLogScope log_scope {"General"}; - - TACTILE_LOG_INFO("Tactile " TACTILE_VERSION_STRING); - - init_random_number_generator(); - - const ProtobufContext protobuf_context {}; - const SDLContext sdl_context {}; - - ui::UniqueImGuiContext imgui_context {ImGui::CreateContext()}; - - // NOLINTBEGIN(*-no-malloc) - ImGui::SetAllocatorFunctions( - [](const usize size, void*) { return std::malloc(size); }, - [](void* ptr, void*) { std::free(ptr); }); - ImGui::SetCurrentContext(imgui_context.get()); - // NOLINTEND(*-no-malloc) - - auto& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - ui::apply_custom_style(ImGui::GetStyle()); +#include "tactile/runtime/launcher.hpp" - const auto renderer_functions = - _load_renderer_functions("tactile-opengl" TACTILE_DLL_EXT); - if (!renderer_functions.has_value()) { - return EXIT_FAILURE; - } - - uint32 window_flags = 0; - renderer_functions->prepare_renderer(&window_flags); - - auto window = Window::create(window_flags); - if (!window.has_value()) { - return EXIT_FAILURE; - } - - UniqueRenderer renderer { - renderer_functions->make_renderer(&window.value(), imgui_context.get()), - renderer_functions->free_renderer}; - if (!renderer) { - TACTILE_LOG_ERROR("Could not create renderer"); - return EXIT_FAILURE; - } - - TactileEditor editor {&window.value(), renderer.get()}; - - Engine engine {&editor, renderer.get()}; - engine.run(); - - return EXIT_SUCCESS; -} - -} // namespace tactile - -auto main(const int, char*[]) -> int +auto main(const int argc, char* argv[]) -> int { - try { - return tactile::_run(); - } - catch (const tactile::Exception& exception) { - TACTILE_LOG_FATAL("Unhandled exception: {}\n{}", - exception.what(), - exception.trace()); - } - catch (const std::exception& exception) { - TACTILE_LOG_FATAL("Unhandled exception: {}", exception.what()); - } - catch (...) { - TACTILE_LOG_FATAL("Unhandled exception"); - } - - return EXIT_FAILURE; + return tactile::launch(argc, argv); } diff --git a/source/opengl/CMakeLists.txt b/source/opengl/CMakeLists.txt index 7572f907b3..fd0c5bdcf3 100644 --- a/source/opengl/CMakeLists.txt +++ b/source/opengl/CMakeLists.txt @@ -12,6 +12,8 @@ target_sources(tactile-opengl "${PROJECT_SOURCE_DIR}/src/tactile/opengl/opengl_imgui.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/opengl/opengl_renderer.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/opengl/opengl_renderer.cpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/opengl/opengl_renderer_plugin.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/opengl/opengl_renderer_plugin.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/opengl/opengl_texture.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/opengl/opengl_texture.cpp" ) @@ -31,6 +33,7 @@ target_include_directories(tactile-opengl target_link_libraries(tactile-opengl PUBLIC tactile::base + tactile::runtime PRIVATE OpenGL::GL diff --git a/source/opengl/inc/tactile/opengl/opengl_renderer.hpp b/source/opengl/inc/tactile/opengl/opengl_renderer.hpp index 6c96a5355a..41e5f5bc27 100644 --- a/source/opengl/inc/tactile/opengl/opengl_renderer.hpp +++ b/source/opengl/inc/tactile/opengl/opengl_renderer.hpp @@ -68,17 +68,4 @@ class TACTILE_OPENGL_API OpenGLRenderer final : public IRenderer OpenGLRenderer(); }; -extern "C" -{ - TACTILE_OPENGL_API - void tactile_prepare_renderer(uint32* window_flags); - - TACTILE_OPENGL_API - auto tactile_make_renderer(IWindow* window, - ImGuiContext* context) -> IRenderer*; - - TACTILE_OPENGL_API - void tactile_free_renderer(IRenderer* renderer); -} - } // namespace tactile diff --git a/source/opengl/inc/tactile/opengl/opengl_renderer_plugin.hpp b/source/opengl/inc/tactile/opengl/opengl_renderer_plugin.hpp new file mode 100644 index 0000000000..6d217aac19 --- /dev/null +++ b/source/opengl/inc/tactile/opengl/opengl_renderer_plugin.hpp @@ -0,0 +1,34 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include "tactile/base/container/maybe.hpp" +#include "tactile/base/prelude.hpp" +#include "tactile/opengl/api.hpp" +#include "tactile/opengl/opengl_renderer.hpp" +#include "tactile/runtime/plugin.hpp" + +namespace tactile { + +/** + * Manages the OpenGL renderer plugin. + */ +class TACTILE_OPENGL_API OpenGLRendererPlugin final : public IPlugin +{ + public: + void load(Runtime& runtime) override; + + void unload(Runtime& runtime) override; + + private: + Optional mRenderer {}; +}; + +extern "C" +{ + TACTILE_OPENGL_API auto tactile_make_plugin() -> IPlugin*; + + TACTILE_OPENGL_API void tactile_free_plugin(IPlugin* plugin); +} + +} // namespace tactile diff --git a/source/opengl/src/tactile/opengl/opengl_renderer.cpp b/source/opengl/src/tactile/opengl/opengl_renderer.cpp index 8f9402d3b3..a9f202e063 100644 --- a/source/opengl/src/tactile/opengl/opengl_renderer.cpp +++ b/source/opengl/src/tactile/opengl/opengl_renderer.cpp @@ -20,6 +20,7 @@ #include "tactile/opengl/opengl_error.hpp" #include "tactile/opengl/opengl_imgui.hpp" #include "tactile/opengl/opengl_texture.hpp" +#include "tactile/runtime/runtime.hpp" namespace tactile { @@ -190,39 +191,4 @@ auto OpenGLRenderer::get_window() const -> const IWindow* return mData->window; } -void tactile_prepare_renderer(uint32* window_flags) -{ - if (window_flags != nullptr) { - *window_flags = SDL_WINDOW_OPENGL; - } - - if constexpr (kOnMacos) { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, - SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); - } - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, SDL_TRUE); -} - -auto tactile_make_renderer(IWindow* window, ImGuiContext* context) -> IRenderer* -{ - if (auto renderer = OpenGLRenderer::make(window, context)) { - return new (std::nothrow) OpenGLRenderer {std::move(*renderer)}; - } - - return nullptr; -} - -void tactile_free_renderer(IRenderer* renderer) -{ - delete renderer; -} - } // namespace tactile diff --git a/source/opengl/src/tactile/opengl/opengl_renderer_plugin.cpp b/source/opengl/src/tactile/opengl/opengl_renderer_plugin.cpp new file mode 100644 index 0000000000..7d38ec14bc --- /dev/null +++ b/source/opengl/src/tactile/opengl/opengl_renderer_plugin.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/opengl/opengl_renderer_plugin.hpp" + +#include // nothrow + +#include + +#include "tactile/runtime/runtime.hpp" + +namespace tactile { + +void OpenGLRendererPlugin::load(Runtime& runtime) +{ + if constexpr (kOnMacos) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, + SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, SDL_TRUE); + + runtime.init_window(SDL_WINDOW_OPENGL); // TODO error handling + + auto* window = runtime.get_window(); + auto* imgui_context = runtime.get_imgui_context(); + + if (auto renderer = OpenGLRenderer::make(window, imgui_context)) { + mRenderer = std::move(*renderer); + runtime.set_renderer(&mRenderer.value()); + } +} + +void OpenGLRendererPlugin::unload(Runtime& runtime) +{ + runtime.set_renderer(nullptr); + mRenderer.reset(); +} + +auto tactile_make_plugin() -> IPlugin* +{ + return new (std::nothrow) OpenGLRendererPlugin {}; +} + +void tactile_free_plugin(IPlugin* plugin) +{ + delete plugin; +} + +} // namespace tactile diff --git a/source/runtime/CMakeLists.txt b/source/runtime/CMakeLists.txt index ab5f99998e..8ce18fc57b 100644 --- a/source/runtime/CMakeLists.txt +++ b/source/runtime/CMakeLists.txt @@ -6,15 +6,33 @@ add_library(tactile::runtime ALIAS tactile-runtime) target_sources(tactile-runtime PRIVATE "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/api.hpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/launcher.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/runtime/launcher.cpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/plugin.hpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/plugin_instance.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/runtime/plugin_instance.cpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/protobuf_context.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/runtime/protobuf_context.cpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/runtime.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/runtime/runtime.cpp" + "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/sdl_context.hpp" + "${PROJECT_SOURCE_DIR}/src/tactile/runtime/sdl_context.cpp" "${PROJECT_SOURCE_DIR}/inc/tactile/runtime/window.hpp" "${PROJECT_SOURCE_DIR}/src/tactile/runtime/window.cpp" ) tactile_prepare_target(tactile-runtime) -target_include_directories(tactile-runtime PUBLIC "${PROJECT_SOURCE_DIR}/inc") +target_include_directories(tactile-runtime + PUBLIC + "${PROJECT_SOURCE_DIR}/inc" + ) -target_compile_definitions(tactile-runtime PRIVATE "TACTILE_BUILDING_RUNTIME") +target_compile_definitions(tactile-runtime + PRIVATE + "TACTILE_BUILDING_RUNTIME" + "TACTILE_ENABLE_OPENGL_RENDERER" + ) target_link_libraries(tactile-runtime PUBLIC @@ -22,5 +40,7 @@ target_link_libraries(tactile-runtime PRIVATE tactile::core + protobuf::libprotobuf SDL2::SDL2 + imgui::imgui ) diff --git a/source/runtime/inc/tactile/runtime/launcher.hpp b/source/runtime/inc/tactile/runtime/launcher.hpp new file mode 100644 index 0000000000..1647f86cfc --- /dev/null +++ b/source/runtime/inc/tactile/runtime/launcher.hpp @@ -0,0 +1,21 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include "tactile/base/prelude.hpp" +#include "tactile/runtime/api.hpp" + +namespace tactile { + +/** + * Launches the Tactile editor application. + * + * \param argc The number of command-line arguments. + * \param argv The command-line arguments. + * + * \return + * \c EXIT_SUCCESS if successful; \c EXIT_FAILURE otherwise. + */ +TACTILE_RUNTIME_API auto launch(int argc, char* argv[]) -> int; + +} // namespace tactile diff --git a/source/base/inc/tactile/base/plugin/plugin.hpp b/source/runtime/inc/tactile/runtime/plugin.hpp similarity index 60% rename from source/base/inc/tactile/base/plugin/plugin.hpp rename to source/runtime/inc/tactile/runtime/plugin.hpp index c0946c9b0a..04b2478636 100644 --- a/source/base/inc/tactile/base/plugin/plugin.hpp +++ b/source/runtime/inc/tactile/runtime/plugin.hpp @@ -6,6 +6,8 @@ namespace tactile { +class Runtime; + /** * Interface for dynamically loaded modules, aka plugins. */ @@ -16,13 +18,20 @@ class IPlugin /** * Loads the resources associated with the plugin. + * + * \param runtime The associated runtime. */ - virtual void load() = 0; + virtual void load(Runtime& runtime) = 0; /** * Releases any resources held by the plugin. + * + * \param runtime The associated runtime. */ - virtual void unload() = 0; + virtual void unload(Runtime& runtime) = 0; }; +using PluginConstructor = IPlugin*(); +using PluginDestructor = void(IPlugin*); + } // namespace tactile diff --git a/source/runtime/inc/tactile/runtime/plugin_instance.hpp b/source/runtime/inc/tactile/runtime/plugin_instance.hpp new file mode 100644 index 0000000000..977d24043d --- /dev/null +++ b/source/runtime/inc/tactile/runtime/plugin_instance.hpp @@ -0,0 +1,72 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include "tactile/base/container/maybe.hpp" +#include "tactile/base/container/smart_ptr.hpp" +#include "tactile/base/container/string.hpp" +#include "tactile/base/prelude.hpp" +#include "tactile/runtime/api.hpp" +#include "tactile/runtime/plugin.hpp" + +namespace tactile { + +class Runtime; +class IDynamicLibrary; + +/** + * Represents a loaded plugin instance. + */ +class TACTILE_RUNTIME_API PluginInstance final +{ + public: + TACTILE_DELETE_COPY(PluginInstance); + + /** + * Unloads the associated plugin. + */ + ~PluginInstance() noexcept; + + PluginInstance(PluginInstance&& other) noexcept; + + auto operator=(PluginInstance&&) -> PluginInstance& = delete; + + /** + * Attempts to load a plugin. + * + * \details + * This function will first try to load a shared library with the specified + * name. Then, the "constructor" and "destructor" functions will be looked up + * in the library (see \c PluginConstructor and \c PluginDestructor), which + * must be called \c tactile_make_plugin and \c tactile_free_plugin, + * respectively. The constructor function is then used to create the actual + * plugin instance, which is then finally initialized via \c IPlugin::load. + * + * \details + * The plugin is unregistered via \c IPlugin::unload and subsequently + * destroyed when the \c PluginInstance goes out of scope. + * + * \param runtime The associated runtime. + * \param plugin_name The file name of the plugin module. + * + * \return + * A plugin instance if successful; an empty optional otherwise. + */ + [[nodiscard]] + static auto load(Runtime* runtime, + StringView plugin_name) -> Optional; + + private: + Runtime* mRuntime; + Unique mDLL; + PluginDestructor* mPluginDestructor; + IPlugin* mPlugin; + bool mPrimed; + + PluginInstance(Runtime* runtime, + Unique dll, + PluginDestructor* plugin_destructor, + IPlugin* plugin); +}; + +} // namespace tactile diff --git a/source/core/inc/tactile/core/persist/protobuf_context.hpp b/source/runtime/inc/tactile/runtime/protobuf_context.hpp similarity index 69% rename from source/core/inc/tactile/core/persist/protobuf_context.hpp rename to source/runtime/inc/tactile/runtime/protobuf_context.hpp index cbdfc765b4..6821a22ce5 100644 --- a/source/core/inc/tactile/core/persist/protobuf_context.hpp +++ b/source/runtime/inc/tactile/runtime/protobuf_context.hpp @@ -3,22 +3,28 @@ #pragma once #include "tactile/base/prelude.hpp" +#include "tactile/runtime/api.hpp" namespace tactile { /** * RAII type that handles the initialization of the Protobuf library. */ -class ProtobufContext final { +class TACTILE_RUNTIME_API ProtobufContext final +{ public: TACTILE_DELETE_COPY(ProtobufContext); TACTILE_DELETE_MOVE(ProtobufContext); - /** Prepares the Protobuf context. */ + /** + * Prepares the Protobuf context. + */ [[nodiscard]] ProtobufContext(); - /** Cleans up Protobuf resources. */ + /** + * Cleans up Protobuf resources. + */ ~ProtobufContext() noexcept; }; diff --git a/source/runtime/inc/tactile/runtime/runtime.hpp b/source/runtime/inc/tactile/runtime/runtime.hpp new file mode 100644 index 0000000000..a0304f93f9 --- /dev/null +++ b/source/runtime/inc/tactile/runtime/runtime.hpp @@ -0,0 +1,52 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include "tactile/base/container/smart_ptr.hpp" +#include "tactile/base/int.hpp" +#include "tactile/base/io/compress/compression_type.hpp" +#include "tactile/base/prelude.hpp" +#include "tactile/runtime/api.hpp" + +struct ImGuiContext; + +namespace tactile { + +class IWindow; +class IRenderer; +class ICompressionProvider; + +/** + * Provides the primary API used by dynamic Tactile modules. + */ +class TACTILE_RUNTIME_API Runtime final +{ + public: + TACTILE_DELETE_COPY(Runtime); + TACTILE_DELETE_MOVE(Runtime); + + Runtime(); + + ~Runtime() noexcept; + + void init_window(uint32 flags); + + void set_renderer(IRenderer* renderer); + + void set_compression_provider(CompressionType format, + ICompressionProvider* provider); + + auto start() -> int; + + [[nodiscard]] + auto get_window() -> IWindow*; + + [[nodiscard]] + auto get_imgui_context() -> ImGuiContext*; + + private: + struct Data; + Unique mData {}; +}; + +} // namespace tactile diff --git a/source/core/inc/tactile/core/platform/sdl_context.hpp b/source/runtime/inc/tactile/runtime/sdl_context.hpp similarity index 82% rename from source/core/inc/tactile/core/platform/sdl_context.hpp rename to source/runtime/inc/tactile/runtime/sdl_context.hpp index 905146681a..a187927a3e 100644 --- a/source/core/inc/tactile/core/platform/sdl_context.hpp +++ b/source/runtime/inc/tactile/runtime/sdl_context.hpp @@ -3,13 +3,15 @@ #pragma once #include "tactile/base/prelude.hpp" +#include "tactile/runtime/api.hpp" namespace tactile { /** * RAII type that manages the initialization of the SDL library. */ -class SDLContext final { +class TACTILE_RUNTIME_API SDLContext final +{ public: TACTILE_DELETE_COPY(SDLContext); TACTILE_DELETE_MOVE(SDLContext); diff --git a/source/runtime/src/tactile/runtime/launcher.cpp b/source/runtime/src/tactile/runtime/launcher.cpp new file mode 100644 index 0000000000..0af39ade0f --- /dev/null +++ b/source/runtime/src/tactile/runtime/launcher.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/runtime/launcher.hpp" + +#include // exception +#include // move + +#include "tactile/base/container/string.hpp" +#include "tactile/base/container/vector.hpp" +#include "tactile/core/debug/exception.hpp" +#include "tactile/core/log/logger.hpp" +#include "tactile/core/platform/dynamic_library.hpp" +#include "tactile/runtime/plugin_instance.hpp" +#include "tactile/runtime/runtime.hpp" + +namespace tactile { +namespace launcher_impl { + +[[nodiscard]] +auto get_plugin_names() -> Vector +{ + Vector plugin_names {}; + +#ifdef TACTILE_ENABLE_ZLIB_COMPRESSION + plugin_names.emplace_back("tactile-zlib-compression" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_ZSTD_COMPRESSION + plugin_names.emplace_back("tactile-zstd-compression" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_TACTILE_YAML_FORMAT + plugin_names.emplace_back("tactile-yaml-format" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_TILED_TMJ_FORMAT + plugin_names.emplace_back("tactile-tiled-tmj-format" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_TILED_TMX_FORMAT + plugin_names.emplace_back("tactile-tiled-tmx-format" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_GODOT_TSCN_FORMAT + plugin_names.emplace_back("tactile-godot-tscn-format" TACTILE_DLL_EXT); +#endif + +#ifdef TACTILE_ENABLE_OPENGL_RENDERER + plugin_names.emplace_back("tactile-opengl" TACTILE_DLL_EXT); +#elif defined(TACTILE_ENABLE_VULKAN_RENDERER) + plugin_names.emplace_back("tactile-vulkan-renderer" TACTILE_DLL_EXT); +#else + #error "No renderer has been configured" +#endif + + return plugin_names; +} + +[[nodiscard]] +auto load_plugins(Runtime& runtime) -> Vector +{ + const auto plugin_names = get_plugin_names(); + TACTILE_LOG_DEBUG("Plugins: {}", plugin_names); + + Vector plugins {}; + + for (const auto plugin_name : plugin_names) { + auto plugin = PluginInstance::load(&runtime, plugin_name); + + if (!plugin) { + TACTILE_LOG_ERROR("Could not load plugin '{}'", plugin_name); + continue; + } + + plugins.push_back(std::move(*plugin)); + } + + return plugins; +} + +} // namespace launcher_impl + +auto launch(const int, char*[]) -> int +{ + try { + tactile::Runtime runtime {}; + + const auto plugins [[maybe_unused]] = launcher_impl::load_plugins(runtime); + + return runtime.start(); + } + catch (const tactile::Exception& exception) { + TACTILE_LOG_FATAL("Unhandled exception: {}\n{}", + exception.what(), + exception.trace()); + } + catch (const std::exception& exception) { + TACTILE_LOG_FATAL("Unhandled exception: {}", exception.what()); + } + catch (...) { + TACTILE_LOG_FATAL("Unhandled exception"); + } + + return EXIT_FAILURE; +} + +} // namespace tactile diff --git a/source/runtime/src/tactile/runtime/plugin_instance.cpp b/source/runtime/src/tactile/runtime/plugin_instance.cpp new file mode 100644 index 0000000000..40918dfb12 --- /dev/null +++ b/source/runtime/src/tactile/runtime/plugin_instance.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/runtime/plugin_instance.hpp" + +#include // move, exchange + +#include "tactile/core/log/logger.hpp" +#include "tactile/core/platform/dynamic_library.hpp" + +namespace tactile { + +PluginInstance::PluginInstance(Runtime* runtime, + Unique dll, + PluginDestructor* plugin_destructor, + IPlugin* plugin) + : mRuntime {runtime}, + mDLL {std::move(dll)}, + mPluginDestructor {plugin_destructor}, + mPlugin {plugin}, + mPrimed {true} +{} + +PluginInstance::~PluginInstance() noexcept +{ + if (mPrimed) { + mPlugin->unload(*mRuntime); + mPluginDestructor(mPlugin); + mPrimed = false; + } +} + +PluginInstance::PluginInstance(PluginInstance&& other) noexcept + : mRuntime {std::exchange(other.mRuntime, nullptr)}, + mDLL {std::exchange(other.mDLL, nullptr)}, + mPluginDestructor {std::exchange(other.mPluginDestructor, nullptr)}, + mPlugin {std::exchange(other.mPlugin, nullptr)}, + mPrimed {std::exchange(other.mPrimed, false)} +{} + +auto PluginInstance::load(Runtime* runtime, const StringView plugin_name) + -> Optional +{ + auto dll = load_library(plugin_name); + + if (!dll) { + TACTILE_LOG_ERROR("Could not load plugin '{}'", plugin_name); + return kNone; + } + + auto* plugin_ctor = + find_symbol(*dll, "tactile_make_plugin"); + auto* plugin_dtor = + find_symbol(*dll, "tactile_free_plugin"); + + if (!plugin_ctor || !plugin_dtor) { + TACTILE_LOG_ERROR("Plugin '{}' has incompatible API", plugin_name); + return kNone; + } + + auto* plugin = plugin_ctor(); + + if (!plugin) { + TACTILE_LOG_ERROR("Could not initialize plugin '{}'", plugin_name); + return kNone; + } + + TACTILE_LOG_DEBUG("Loading plugin '{}'", plugin_name); + plugin->load(*runtime); + + return PluginInstance {runtime, std::move(dll), plugin_dtor, plugin}; +} + +} // namespace tactile diff --git a/source/core/src/tactile/core/persist/protobuf_context.cpp b/source/runtime/src/tactile/runtime/protobuf_context.cpp similarity index 90% rename from source/core/src/tactile/core/persist/protobuf_context.cpp rename to source/runtime/src/tactile/runtime/protobuf_context.cpp index 6aaa6beef5..b065f824a8 100644 --- a/source/core/src/tactile/core/persist/protobuf_context.cpp +++ b/source/runtime/src/tactile/runtime/protobuf_context.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) -#include "tactile/core/persist/protobuf_context.hpp" +#include "tactile/runtime/protobuf_context.hpp" #include diff --git a/source/runtime/src/tactile/runtime/runtime.cpp b/source/runtime/src/tactile/runtime/runtime.cpp new file mode 100644 index 0000000000..c8c2dfa7e7 --- /dev/null +++ b/source/runtime/src/tactile/runtime/runtime.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/runtime/runtime.hpp" + +#include // malloc, free, EXIT_SUCCESS, EXIT_FAILURE +#include // exception, set_terminate +#include // move + +#include + +#include "tactile/base/container/hash_map.hpp" +#include "tactile/base/container/maybe.hpp" +#include "tactile/core/debug/terminate.hpp" +#include "tactile/core/engine/engine.hpp" +#include "tactile/core/log/logger.hpp" +#include "tactile/core/log/set_log_scope.hpp" +#include "tactile/core/log/terminal_log_sink.hpp" +#include "tactile/core/numeric/random.hpp" +#include "tactile/core/platform/win32.hpp" +#include "tactile/core/tactile_editor.hpp" +#include "tactile/core/ui/common/style.hpp" +#include "tactile/runtime/protobuf_context.hpp" +#include "tactile/runtime/sdl_context.hpp" +#include "tactile/runtime/window.hpp" + +namespace tactile { +namespace runtime_impl { + +struct ImGuiContextDeleter final +{ + void operator()(ImGuiContext* context) noexcept + { + ImGui::DestroyContext(context); + } +}; + +using UniqueImGuiContext = Unique; + +[[nodiscard]] +auto make_logger() -> Logger +{ + win32_enable_virtual_terminal_processing(); + + auto terminal_sink = std::make_unique(); + terminal_sink->use_ansi_colors(true); + + Logger logger {}; + + logger.set_reference_instant(SteadyClock::now()); + logger.set_min_level(LogLevel::kTrace); + logger.add_sink(std::move(terminal_sink)); + + return logger; +} + +[[nodiscard]] +auto make_imgui_context() -> UniqueImGuiContext +{ + UniqueImGuiContext imgui_context {ImGui::CreateContext()}; + + // NOLINTBEGIN(*-no-malloc) + ImGui::SetAllocatorFunctions( + [](const usize size, void*) { return std::malloc(size); }, + [](void* ptr, void*) noexcept { std::free(ptr); }); + ImGui::SetCurrentContext(imgui_context.get()); + // NOLINTEND(*-no-malloc) + + auto& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + ui::apply_custom_style(ImGui::GetStyle()); + + return imgui_context; +} + +} // namespace runtime_impl + +struct Runtime::Data final +{ + ProtobufContext protobuf_context; + SDLContext sdl_context; + Logger logger; + runtime_impl::UniqueImGuiContext imgui_context {}; + Optional window {}; + IRenderer* renderer {}; + HashMap compression_providers {}; + Optional editor {}; + + Data() + : protobuf_context {}, + sdl_context {}, + logger {runtime_impl::make_logger()}, + imgui_context {runtime_impl::make_imgui_context()} + { + set_default_logger(&logger); + } + + ~Data() noexcept + { + set_default_logger(nullptr); + } + + TACTILE_DELETE_COPY(Data); + TACTILE_DELETE_MOVE(Data); +}; + +Runtime::Runtime() +{ + std::set_terminate(&on_terminate); + mData = std::make_unique(); + + TACTILE_LOG_INFO("Tactile " TACTILE_VERSION_STRING); + init_random_number_generator(); +} + +Runtime::~Runtime() noexcept = default; + +void Runtime::init_window(const uint32 flags) +{ + if (auto window = Window::create(flags)) { + mData->window = std::move(*window); + } +} + +void Runtime::set_renderer(IRenderer* renderer) +{ + mData->renderer = renderer; +} + +void Runtime::set_compression_provider(const CompressionType format, + ICompressionProvider* provider) +{ + if (provider != nullptr) { + mData->compression_providers.insert_or_assign(format, provider); + } + else { + mData->compression_providers.erase(format); + } +} + +auto Runtime::start() -> int +{ + auto& data = *mData; + + if (!data.window) { + TACTILE_LOG_ERROR("Window has not been initialized"); + return EXIT_FAILURE; + } + + if (!data.renderer) { + TACTILE_LOG_ERROR("A renderer has not been installed"); + return EXIT_FAILURE; + } + + auto* window = &data.window.value(); + auto* renderer = data.renderer; + + auto& app = data.editor.emplace(window, renderer); + + Engine engine {&app, renderer}; + engine.run(); + + return EXIT_SUCCESS; +} + +auto Runtime::get_window() -> IWindow* +{ + return mData->window.has_value() ? &mData->window.value() : nullptr; +} + +auto Runtime::get_imgui_context() -> ImGuiContext* +{ + return mData->imgui_context.get(); +} + +} // namespace tactile diff --git a/source/core/src/tactile/core/platform/sdl_context.cpp b/source/runtime/src/tactile/runtime/sdl_context.cpp similarity index 93% rename from source/core/src/tactile/core/platform/sdl_context.cpp rename to source/runtime/src/tactile/runtime/sdl_context.cpp index 72099e7703..14f2fdaf47 100644 --- a/source/core/src/tactile/core/platform/sdl_context.cpp +++ b/source/runtime/src/tactile/runtime/sdl_context.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) -#include "tactile/core/platform/sdl_context.hpp" +#include "tactile/runtime/sdl_context.hpp" #include