diff --git a/CHANGELOG.md b/CHANGELOG.md index ba4761e..67ab6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.2.0] - 2023-04-24 +- Add plugin API support to allow users to extend the application with their own code. + +## [1.1.2] - 2023-04-23 +- Add `system_save_file_dialog` on OSX +- Fix `build_setting` bash function on OSX +- Fix `MEM_DELETE` when pointer is null +- Fix OSX compilation issues +- Improve `./run` script +- Remove `FOUNDATION_CONSTCALL` usage which is causing issues on OSX + ## [1.1.1] - 2023-04-22 - Add `./run package` (through `build-package.sh`) script to build and package the library for distribution. - Add `BUILD_ENABLE_BACKEND` build switch diff --git a/CMakeLists.txt b/CMakeLists.txt index 7278bb8..32fcf72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,12 @@ set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Root directory path") # Set build output dir set(BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build CACHE PATH "Build directory path") +# Set plugins dir path +set(PLUGINS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/plugins CACHE PATH "Plugins directory path") + +# Set plugin build output dir +set(PLUGIN_BUILD_DIR ${BUILD_DIR}/plugins CACHE PATH "Build plugins path") + # Cache the shared module path # Then each nested CMakeLists.txt can include the shared.cmake file using the following command: # include(${SHARED_MODULE_PATH}) @@ -248,7 +254,17 @@ if(MSVC) # Set /OPT:REF for static libraries as well set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") -elseif(XCODE) + # Set DLL output build directory + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${BUILD_DIR}) + + # Set PDB output build directory + set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR}) + set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR}) + set(CMAKE_PDB_OUTPUT_DIRECTORY_DEPLOY ${BUILD_DIR}) + +elseif(APPLE) # Use fast floating point math add_compile_options(-ffast-math) @@ -286,6 +302,9 @@ elseif(XCODE) add_compile_options(-Wno-tautological-constant-compare -Wno-unguarded-availability-new) endif() + # Define -Wno-varargs + add_compile_options(-Wno-varargs) + endif() set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${CMAKE_FLAGS_RELEASE}") @@ -298,6 +317,12 @@ set(CMAKE_CXX_FLAGS_DEPLOY "${CMAKE_CXX_FLAGS_DEPLOY} ${CMAKE_CXX_FLAGS_RELEASE} # Copy release linker flags to deploy set(CMAKE_EXE_LINKER_FLAGS_DEPLOY "${CMAKE_EXE_LINKER_FLAGS_DEPLOY} ${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +if (MSVC) + # Generate PDB files for debug and release builds + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") +endif() + set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Load BX library @@ -330,9 +355,20 @@ endif() # Load main exectuable project under sources/ add_subdirectory(${ROOT_DIR}/sources) -# Add subdirectories for all cmakelist under tools/ -file(GLOB_RECURSE CMAKE_LISTS ${ROOT_DIR}/tools/*CMakeLists.txt) -foreach(CMAKE_LIST ${CMAKE_LISTS}) - get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY) - add_subdirectory(${CMAKE_LIST_DIR}) -endforeach() +if (BUILD_ENABLE_TOOLS) + # Add subdirectories for all cmakelists under tools/ + file(GLOB_RECURSE TOOLS_LISTS ${ROOT_DIR}/tools/*CMakeLists.txt) + foreach(CMAKE_LIST ${TOOLS_LISTS}) + get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY) + add_subdirectory(${CMAKE_LIST_DIR}) + endforeach() +endif() + +if (BUILD_ENABLE_PLUGINS) + # Add subdirectories for all cmakelists under plugins/ + file(GLOB_RECURSE PLUGINS_LISTS ${ROOT_DIR}/plugins/*CMakeLists.txt) + foreach(CMAKE_LIST ${PLUGINS_LISTS}) + get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY) + add_subdirectory(${CMAKE_LIST_DIR}) + endforeach() +endif() diff --git a/README.md b/README.md index 0d23ce2..2cb06bc 100644 --- a/README.md +++ b/README.md @@ -69,13 +69,11 @@ Then you usually want to start editing sources under `sources/`. As a starting p Basically, you need to have a `cpp` file that implements minimally the following functions: - `app_title` - Returns the application title. -- `app_exception_handler` - Defines how the application handles crashes. - `app_configure` - Defines how the application and foundation is configured. - `app_initialize` - Defines how the application is initialized (i.e. initialize services and modules once, etc). - `app_shutdown` - Defines how the application is finalized (i.e. shutdown services and modules once, etc). - `app_update` - Defines how the application is updated prior to render each frames. - `app_render` - Defines how the application is rendered each frame. -- `app_render_3rdparty_libs` - Entry point to render additional 3rd party libraries used for your specific application. ### Build and run diff --git a/config/build.settings b/config/build.settings index 58eaa1c..ba127b1 100644 --- a/config/build.settings +++ b/config/build.settings @@ -13,7 +13,7 @@ PRODUCT_URL=https://equals-forty-two.com # Version info VERSION_MAJOR=1 VERSION_MINOR=1 -VERSION_PATCH=1 +VERSION_PATCH=2 # Build settings (Usually passed to Cmake) BUILD_SERVICE_EXE=OFF @@ -22,3 +22,5 @@ BUILD_ENABLE_LOCALIZATION=ON BUILD_MAX_JOB_THREADS=2 BUILD_MAX_QUERY_THREADS=2 BUILD_ENABLE_BACKEND=OFF +BUILD_ENABLE_PLUGINS=ON +BUILD_ENABLE_TOOLS=OFF diff --git a/config/options.cmake b/config/options.cmake index 38cfd6b..3eee286 100644 --- a/config/options.cmake +++ b/config/options.cmake @@ -24,3 +24,9 @@ option(BUILD_ENABLE_TESTS "Build tests" ON) # Set the build backend option to OFF by default. option(BUILD_ENABLE_BACKEND "Build backend" OFF) + +# Set options to build or not tools +option(BUILD_ENABLE_TOOLS "Build tools" ON) + +# Set option to build or not plugins +option(BUILD_ENABLE_PLUGINS "Build plugins" ON) diff --git a/config/shared.cmake b/config/shared.cmake index 104d871..dede0d3 100644 --- a/config/shared.cmake +++ b/config/shared.cmake @@ -82,7 +82,7 @@ function(set_executable_framework_linker_flags CMAKE_EXE_LINKER_FLAGS CMAKE_EXE_ # Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC set(${CMAKE_EXE_LINKER_FLAGS_DEBUG} "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib") - elseif(XCODE) + elseif(APPLE) # Link with core libraries set(${CMAKE_EXE_LINKER_FLAGS} "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices") diff --git a/external/bgfx/CMakeLists.txt b/external/bgfx/CMakeLists.txt index 50e3b17..2e57333 100644 --- a/external/bgfx/CMakeLists.txt +++ b/external/bgfx/CMakeLists.txt @@ -44,16 +44,25 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") add_compile_definitions(BGFX_CONFIG_USE_TINYSTL=0) # Disable vulkan backend renderer for all platforms +add_compile_definitions(BGFX_CONFIG_RENDERER_AGC=0) add_compile_definitions(BGFX_CONFIG_RENDERER_VULKAN=0) add_compile_definitions(BGFX_CONFIG_RENDERER_OPENGLES=0) -add_compile_definitions(BGFX_CONFIG_RENDERER_AGC=0) add_compile_definitions(BGFX_CONFIG_RENDERER_OPENGL=0) -add_compile_definitions(BGFX_CONFIG_MAX_DRAW_CALLS=1024) -add_compile_definitions(BGFX_CONFIG_MAX_OCCLUSION_QUERIES=32) -add_compile_definitions(BGFX_CONFIG_MAX_VIEW_NAME=64) +# BGFX tweaks for our needs +add_compile_definitions(BGFX_CONFIG_MAX_SHADERS=16) +add_compile_definitions(BGFX_CONFIG_MAX_VIEW_NAME=16) +add_compile_definitions(BGFX_CONFIG_MAX_DRAW_CALLS=2047) add_compile_definitions(BGFX_CONFIG_MAX_VERTEX_DECLS=32) -add_compile_definitions(BGFX_CONFIG_MAX_INDEX_BUFFERS=512) +add_compile_definitions(BGFX_CONFIG_MAX_INDEX_BUFFERS=256) +add_compile_definitions(BGFX_CONFIG_MAX_OCCLUSION_QUERIES=8) +add_compile_definitions(BGFX_CONFIG_MAX_DYNAMIC_INDEX_BUFFERS=8192) +add_compile_definitions(BGFX_CONFIG_MAX_DYNAMIC_VERTEX_BUFFERS=8192) + +# Disable debug features +add_compile_definitions(BGFX_CONFIG_DEBUG_UNIFORM=0) +add_compile_definitions(BGFX_CONFIG_DEBUG_ANNOTATION=0) +add_compile_definitions(BGFX_CONFIG_DEBUG_OCCLUSION=0) if (WIN32) @@ -63,9 +72,7 @@ if (WIN32) # Enable directx backend renderers add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D11=1) - add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D12=1) - - add_compile_definitions(BGFX_CONFIG_MAX_INSTANCE_DATA_COUNT=4) + add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D12=0) elseif (APPLE) @@ -73,7 +80,7 @@ elseif (APPLE) add_compile_options(-fno-rtti) add_compile_options(-fno-exceptions) - # Force METAL renderer with BGFX_CONFIG_RENDERER_METAL=1 + # Force METAL renderer add_compile_definitions(BGFX_CONFIG_RENDERER_METAL=1) endif() diff --git a/external/foundation/CMakeLists.txt b/external/foundation/CMakeLists.txt index 2323bc4..e0f1779 100644 --- a/external/foundation/CMakeLists.txt +++ b/external/foundation/CMakeLists.txt @@ -29,10 +29,14 @@ add_compile_definitions(_LIB) # Use compiler flags from parent project set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - -# Use compiler flags from parent project set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +# Export foundation functions +if (BUILD_ENABLE_PLUGINS) + add_compile_definitions(FOUNDATION_COMPILE=1) + add_compile_definitions(BUILD_DYNAMIC_LINK=1) +endif() + # Project library add_library(foundation STATIC ${foundation_src} ${foundation_includes}) @@ -45,9 +49,7 @@ target_include_directories(foundation PUBLIC ${ROOT_DIR}/external/) # Export a few defines to the project target_compile_definitions(foundation PUBLIC -DFOUNDATION_SIZE_REAL=8) -if (APPLE) +if (XCODE) # Set Xcode project properties - set_target_properties(foundation - PROPERTIES - XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES) + set_target_properties(foundation PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES) endif() diff --git a/external/foundation/exception.c b/external/foundation/exception.c index c9a5d53..8645663 100644 --- a/external/foundation/exception.c +++ b/external/foundation/exception.c @@ -54,7 +54,7 @@ typedef BOOL(STDCALL* MiniDumpWriteDumpFn)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_CALLBACK_INFORMATION); static void -create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen, char* dump_file, size_t* capacity) { +create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen, char* dump_file, size_t* capacity, bool print_dump_written_log) { MINIDUMP_EXCEPTION_INFORMATION info; HANDLE file; SYSTEMTIME local_time; @@ -109,8 +109,9 @@ create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen, STRING_FORMAT(errmsg)); } if (success) { - log_errorf(0, ERROR_EXCEPTION, STRING_CONST("Exception occurred! Minidump written to: %.*s"), - STRING_FORMAT(filename)); + if (print_dump_written_log) { + log_errorf(0, ERROR_EXCEPTION, STRING_CONST("Exception occurred! Minidump written to: %.*s"), STRING_FORMAT(filename)); + } FlushFileBuffers(file); } CloseHandle(file); @@ -156,9 +157,9 @@ exception_filter(LPEXCEPTION_POINTERS pointers) { char dump_file_buffer[MAX_PATH]; size_t dump_file_len = sizeof(dump_file_buffer); exception_closure.triggered = true; - create_mini_dump(pointers, STRING_ARGS(exception_closure.name), dump_file_buffer, &dump_file_len); + create_mini_dump(pointers, STRING_ARGS(exception_closure.name), dump_file_buffer, &dump_file_len, true); if (exception_closure.handler) - exception_closure.handler(dump_file_buffer, dump_file_len); + exception_closure.handler(nullptr, dump_file_buffer, dump_file_len); if (_RtlRestoreContext) _RtlRestoreContext(&exception_closure.context, 0); else @@ -229,10 +230,10 @@ FOUNDATION_ATTRIBUTE(noreturn) exception_sigaction(int sig, siginfo_t* info, voi char file_name_buffer[BUILD_MAX_PATHLEN]; const char* name = get_thread_dump_name(); string_t dump_file = (string_t){file_name_buffer, sizeof(file_name_buffer)}; - dump_file = create_mini_dump(arg, (string_const_t){name, string_length(name)}, dump_file); + dump_file = create_mini_dump(arg, (string_const_t){name, string_length(name)}, dump_file, true); exception_handler_fn handler = get_thread_exception_handler(); if (handler) - handler(dump_file.str, dump_file.length); + handler(arg, dump_file.str, dump_file.length); // Log dump file name log_warnf(0, WARNING_SUSPICIOUS, STRING_CONST("Minidump written to: %.*s"), STRING_FORMAT(dump_file)); @@ -274,10 +275,10 @@ exception_try(exception_try_fn fn, void* data, exception_handler_fn handler, con __try { ret = fn(data); } /*lint -e534*/ - __except (create_mini_dump(GetExceptionInformation(), name, length, buffer, &capacity), EXCEPTION_EXECUTE_HANDLER) { + __except (create_mini_dump(GetExceptionInformation(), name, length, buffer, &capacity, false), EXCEPTION_EXECUTE_HANDLER) { ret = FOUNDATION_EXCEPTION_CAUGHT; if (handler) - handler(buffer, capacity); + handler(data, buffer, capacity); error_context_clear(); } diff --git a/external/foundation/platform.h b/external/foundation/platform.h index cba9b9e..ec633da 100644 --- a/external/foundation/platform.h +++ b/external/foundation/platform.h @@ -109,7 +109,7 @@ thread local storage to ensure maximum portability across supported platforms */ #undef FOUNDATION_PLATFORM_ANDROID #define FOUNDATION_PLATFORM_ANDROID 1 -// Compatibile platforms +// Compatible platforms #undef FOUNDATION_PLATFORM_POSIX #define FOUNDATION_PLATFORM_POSIX 1 @@ -200,7 +200,7 @@ thread local storage to ensure maximum portability across supported platforms */ #undef FOUNDATION_PLATFORM_TIZEN #define FOUNDATION_PLATFORM_TIZEN 1 -// Compatibile platforms +// Compatible platforms #undef FOUNDATION_PLATFORM_POSIX #define FOUNDATION_PLATFORM_POSIX 1 @@ -1796,7 +1796,7 @@ Expand to three arguments, string pointer, length and capacity, as in s.str, s.length, s.length+1 \def STRING_FORMAT -Expand to two arguments, legnth and string pointer, as in (int)s.length, s.str. +Expand to two arguments, length and string pointer, as in (int)s.length, s.str. Useful when passing a string_t to a string format argument, for example string_t mystr = ...; log_infof(0, STRING_CONST("Mystring: %.*s"), STRING_FORMAT(mystr)); diff --git a/external/foundation/types.h b/external/foundation/types.h index f6f0dc8..575ad19 100644 --- a/external/foundation/types.h +++ b/external/foundation/types.h @@ -603,7 +603,7 @@ an implementation specific code which is then returned from the call to error_re \return Implementation specific code which is passed back as return from error_report */ typedef int (*error_handler_fn)(error_level_t level, error_t error); -/*! Assert handler which is passed assert data and should do impementation specific +/*! Assert handler which is passed assert data and should do implementation specific processing and return a code indicating if execution can continue or need to be aborted. \param context Error context \param condition String expressing the condition that failed @@ -743,9 +743,10 @@ typedef int (*exception_try_fn)(void* arg); /*! Exception handler function prototype, used to notify that an exception occurred and the process state was saved to a dump file +\param arg Implementation specific argument passed to exception_try \param file Dump file path \param length Length of file path */ -typedef void (*exception_handler_fn)(const char* file, size_t length); +typedef void (*exception_handler_fn)(void* arg, const char* file, size_t length); /*! Object deallocation function prototype, used to deallocate an object of a specific type \param object Object pointer */ diff --git a/external/imgui/CMakeLists.txt b/external/imgui/CMakeLists.txt index 35381b2..2112f3b 100644 --- a/external/imgui/CMakeLists.txt +++ b/external/imgui/CMakeLists.txt @@ -18,6 +18,13 @@ source_group("includes" FILES ${imgui_includes}) # Add _LIB only for static library add_compile_definitions(_LIB) +if (BUILD_ENABLE_PLUGINS) + if (MSVC) + # Define IMGUI_API to export symbols + add_compile_definitions("IMGUI_API=__declspec(dllexport)") + endif() +endif() + # Use compiler flags from parent project set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") diff --git a/external/imgui/imconfig.h b/external/imgui/imconfig.h index 8c10190..4a9fd70 100644 --- a/external/imgui/imconfig.h +++ b/external/imgui/imconfig.h @@ -14,10 +14,22 @@ #pragma once +#include + +// ############################################################################### +#include "IconsMaterialDesign.h" +#define THIN_SPACE "\xe2\x80\x89" // U+2009 +// ############################################################################### + //---- Define assertion handler. Defaults to calling assert(). // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + +#ifdef NDEBUG +#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts +#else +#define IM_ASSERT(_EXPR) FOUNDATION_ASSERT(_EXPR) +#endif //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. diff --git a/external/imgui/imgui.h b/external/imgui/imgui.h index 529c4f5..224cab9 100644 --- a/external/imgui/imgui.h +++ b/external/imgui/imgui.h @@ -54,10 +54,6 @@ Index of this file: #include IMGUI_USER_CONFIG #endif #include "imconfig.h" -// ############################################################################### -#include "IconsMaterialDesign.h" -#define THIN_SPACE "\xe2\x80\x89" // U+2009 -// ############################################################################### #ifndef IMGUI_DISABLE diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt index 0534ceb..d47271c 100644 --- a/framework/CMakeLists.txt +++ b/framework/CMakeLists.txt @@ -21,11 +21,19 @@ file(GLOB FRAMEWORK_SOURCES ) source_group("framework" FILES ${FRAMEWORK_SOURCES}) +if (BUILD_ENABLE_PLUGINS) + # Load API source filess + file(GLOB API_FILES ${PLUGINS_DIR}/api/*.h) + source_group("api" FILES ${API_FILES}) + + # Add main.c explicitly to the framework library if plugins are enabled + # This is because the foundation_lib excludes it if built with BUILD_DYNAMIC_LINK=1 to export symbols + list(APPEND FRAMEWORK_SOURCES ${ROOT_DIR}/external/foundation/main.c) +endif() + # Add *.mm files for OSX if (APPLE) - file(GLOB FRAMEWORK_SOURCES_OSX - ${FRAMEWORK_DIR}/*.mm - ) + file(GLOB FRAMEWORK_SOURCES_OSX ${FRAMEWORK_DIR}/*.mm) list(APPEND FRAMEWORK_SOURCES ${FRAMEWORK_SOURCES_OSX}) endif() @@ -105,8 +113,6 @@ endif() # Use compiler flags from parent project set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - -# Use compiler flags from parent project set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Add sources include directories @@ -115,6 +121,7 @@ include_directories(${ROOT_DIR}/sources) # Create the application library add_library(framework STATIC + ${API_FILES} ${FRAMEWORK_SOURCES} ${TEST_SOURCES} ${EXTERNAL_SOURCES} @@ -167,6 +174,16 @@ elseif(APPLE) # Enable openmp support target_compile_definitions(framework PUBLIC _OPENMP) + # Ignore -Wshorten-64-to-32 + target_compile_options(framework PUBLIC -Wno-shorten-64-to-32) + + # Ignore -Wunused-value + target_compile_options(framework PUBLIC -Wno-unused-value) + + # Ignore -Wformat-security + target_compile_options(framework PUBLIC -Wno-format) + target_compile_options(framework PUBLIC -Wno-format-security) + endif() # diff --git a/framework/app.cpp b/framework/app.cpp index ede632e..1d8334d 100644 --- a/framework/app.cpp +++ b/framework/app.cpp @@ -5,7 +5,6 @@ * This module contains application framework specific code. * It is expected that the project sources also includes an app.cpp and defines the following functions: * extern const char* app_title() - * extern void app_exception_handler(const char* dump_file, size_t length) * extern void app_initialize() * extern void app_shutdown() * extern void app_update() @@ -15,6 +14,7 @@ #include "app.h" #include "version.h" +#include #include #include #include @@ -22,11 +22,13 @@ #include #include #include +#include #include #include #include #include +#include #define HASH_APP static_hash_string("app", 3, 0x6ced59ff7a1fae4bULL) @@ -64,6 +66,8 @@ struct app_menu_t static app_menu_t* _menus = nullptr; static app_dialog_t* _dialogs = nullptr; +static app_callback_t* _render_lib_callbacks = nullptr; + // // # PRIVATE // @@ -295,13 +299,90 @@ FOUNDATION_STATIC void app_dialogs_render() } ImGui::End(); } + + plugin_render(); +} + +/*! Render leading menus. + * + * @param window The GLFW window. + */ +FOUNDATION_STATIC void app_main_menu_begin(GLFWwindow* window) +{ + if (!ImGui::BeginMenuBar()) + return; + + if (ImGui::TrBeginMenu("File")) + { + if (ImGui::TrBeginMenu("Create")) + { + ImGui::EndMenu(); + } + + if (ImGui::TrBeginMenu("Open")) + { + ImGui::EndMenu(); + } + + ImGui::Separator(); + if (ImGui::TrMenuItem(ICON_MD_EXIT_TO_APP " Exit", "Alt+F4")) + glfw_request_close_window(window); + + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); + + // Let the framework inject some menus. + app_menu_begin(window); +} + +/*! Render trailing menus. + * + * In between leading and trailing menus the framework usually + * injects additional menus through registered modules. + * + * @param window The GLFW window. + */ +FOUNDATION_STATIC void app_main_menu_end(GLFWwindow* window) +{ + // Let registered module inject some menus. + module_foreach_menu(); + + if (ImGui::BeginMenuBar()) + { + if (ImGui::TrBeginMenu("Windows")) + ImGui::EndMenu(); + + app_menu_help(window); + + // Update special application menu status. + // Usually controls are displayed at the far right of the menu. + profiler_menu_timer(); + module_foreach_menu_status(); + + ImGui::EndMenuBar(); + } + + app_menu_end(window); } // // # PUPLIC API // -void app_open_dialog(const char* title, const app_dialog_handler_t& handler, uint32_t width, uint32_t height, bool can_resize, void* user_data, const app_dialog_close_handler_t& close_handler) +void app_exception_handler(void* context, const char* dump_file, size_t length) +{ + FOUNDATION_UNUSED(context); + FOUNDATION_UNUSED(dump_file); + FOUNDATION_UNUSED(length); + log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception")); + process_exit(-1); +} + +void app_open_dialog(const char* title, const app_dialog_handler_t& handler, + uint32_t width, uint32_t height, bool can_resize, + void* user_data, const app_dialog_close_handler_t& close_handler) { FOUNDATION_ASSERT(handler); @@ -545,14 +626,100 @@ void app_menu_help(GLFWwindow* window) ImGui::EndMenu(); } +void app_render_default(GLFWwindow* window, int frame_width, int frame_height, + int& current_tab, + void(*default_tab)(), + void(*settings_draw)()) +{ + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2((float)frame_width, (float)frame_height)); + + if (ImGui::Begin(app_title(), nullptr, + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_MenuBar)) + { + // Render main menus + app_main_menu_begin(window); + + // Render document tabs + static ImGuiTabBarFlags tabs_init_flags = ImGuiTabBarFlags_Reorderable; + if (tabs_begin("Tabs", current_tab, tabs_init_flags, nullptr)) + { + // Render the settings tab + tab_set_color(TAB_COLOR_APP_3D); + tab_draw(tr(ICON_MD_HEXAGON " Example "), nullptr, 0, default_tab); + + // Render module registered tabs + module_foreach_tabs(); + + if (settings_draw) + { + // Render the settings tab + tab_set_color(TAB_COLOR_SETTINGS); + tab_draw(tr(ICON_MD_SETTINGS " Settings ##Settings"), nullptr, + ImGuiTabItemFlags_NoPushId | ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoReorder, settings_draw); + } + + // We differ setting ImGuiTabBarFlags_AutoSelectNewTabs until after the first frame, + // since we manually select the first tab in the list using the user session data. + if ((tabs_init_flags & ImGuiTabBarFlags_AutoSelectNewTabs) == 0) + tabs_init_flags |= ImGuiTabBarFlags_AutoSelectNewTabs; + + tabs_end(); + } + + // Render trailing menus + app_main_menu_end(window); + + // Render main dialog and floating windows + module_foreach_window(); + + } ImGui::End(); +} + +void app_update_default(GLFWwindow* window) +{ + module_update(); + plugin_update(); +} + +void app_add_render_lib_callback(void(*handler)(), void* user_data) +{ + app_callback_t callback; + callback.handler = handler; + callback.user_data = user_data; + + array_push(_render_lib_callbacks, callback); +} + +void app_remove_render_lib_callback(void(*handler)()) +{ + for (unsigned i = 0, end = array_size(_render_lib_callbacks); i < end; ++i) + { + if (_render_lib_callbacks[i].handler == handler) + { + array_erase(_render_lib_callbacks, i); + break; + } + } +} + +void app_render_3rdparty_libs() +{ + for (unsigned i = 0, end = array_size(_render_lib_callbacks); i < end; ++i) + _render_lib_callbacks[i].handler(); +} + // // # SERVICE // FOUNDATION_STATIC void app_framework_initialize() { - //system_add_menu_item("test"); - module_register_window(HASH_APP, app_dialogs_render); } diff --git a/framework/app.h b/framework/app.h index c9f92c6..37447a1 100644 --- a/framework/app.h +++ b/framework/app.h @@ -29,8 +29,18 @@ typedef function app_dialog_close_handler_t; typedef function app_update_handler_t; typedef function app_render_handler_t; -/*! Set of flags used to customize the registration of a new menu item. */ -typedef enum class AppMenuFlags +template +struct app_callback_t +{ + Handler* handler; + void* user_data; + void* context; +}; + +/*! Set of flags used to customize the registration of a new menu item. + * @api enum app_menu_flags_t + */ +typedef enum class AppMenuFlags : uint32_t { None = 0, @@ -39,9 +49,17 @@ typedef enum class AppMenuFlags /*! Menu item defines a shortcut */ Shortcut = 1 << 1, -} app_menu_flags_t; + +} app_menu_flags_t; DEFINE_ENUM_FLAGS(AppMenuFlags); +/*! Handles exception at the application level. + * + * @param dump_file The path to the dump file. + * @param length The length of the dump file. + */ +void app_exception_handler(void* context, const char* dump_file, size_t length); + #if defined(FRAMEWORK_APP_IMPLEMENTATION) #include @@ -51,16 +69,6 @@ DEFINE_ENUM_FLAGS(AppMenuFlags); /*! Returns the application title. */ extern const char* app_title(); -/*! Renders application 3rdparty libs using ImGui. */ -extern void app_render_3rdparty_libs(); - -/*! Handles exception at the application level. - * - * @param dump_file The path to the dump file. - * @param length The length of the dump file. - */ -extern void app_exception_handler(const char* dump_file, size_t length); - /*! Configure the application features and framework core services. * * @param config The configuration to be modified. @@ -164,3 +172,46 @@ void app_register_menu( * @param window The window to render the menu items for. */ void app_menu_help(GLFWwindow* window); + +/*! Default application render handler. + * + * The default application render draws a set of tabs at the top of the window, and + * renders the current tab. The tab set is rendered using ImGui. + * + * @param window The main window used to render the application (can be null) + * @param frame_width The width of the frame to be rendered. + * @param frame_height The height of the frame to be rendered. + * @param current_tab The current tab to be rendered. + * @param default_tab The default tab to be rendered. + * @param settings_draw The settings draw function to be called when the settings tab is selected. + * + * @remark This handler must be invoked explicitly from #app_render. + */ +void app_render_default(GLFWwindow* window, int frame_width, int frame_height, + int& current_tab, + void(*default_tab)(), + void(*settings_draw)() = nullptr); + +/*! Default application update handler. + * * + * @param window The main window used to render the application (can be null) + * + * @remark This handler must be invoked explicitly from #app_update. + */ +void app_update_default(GLFWwindow* window); + +/*! Register a callback to render 3rdparty libs information in the about window. + * + * @param handler The handler to be called when the application is rendering 3rdparty libs. + * @param user_data The user data to be passed to the handler. + */ +void app_add_render_lib_callback(void(*handler)(), void* user_data); + +/*! Unregister a callback to render 3rdparty libs information in the about window. + * + * @param handler The handler to be called when the application is rendering 3rdparty libs. + */ +void app_remove_render_lib_callback(app_event_handler_t handler); + +/*! Renders application 3rdparty libs using ImGui in the about window. */ +void app_render_3rdparty_libs(); diff --git a/framework/app.impl.inl b/framework/app.impl.inl index 983a165..1ec4712 100644 --- a/framework/app.impl.inl +++ b/framework/app.impl.inl @@ -26,14 +26,6 @@ extern const char* app_title() return PRODUCT_NAME; } -extern void app_exception_handler(const char* dump_file, size_t length) -{ - FOUNDATION_UNUSED(dump_file); - FOUNDATION_UNUSED(length); - log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception")); - process_exit(-1); -} - extern void app_configure(foundation_config_t& config, application_t& application) { application.flags = APPLICATION_GUI; @@ -113,11 +105,6 @@ extern void app_render(GLFWwindow* window, int frame_width, int frame_height) } #endif -extern void app_render_3rdparty_libs() -{ - -} - #if BUILD_TESTS extern int main_tests(void* _context, GLFWwindow* window) { diff --git a/framework/common.h b/framework/common.h index 05bb204..21b3d6c 100644 --- a/framework/common.h +++ b/framework/common.h @@ -40,21 +40,21 @@ constexpr double DNAN = __builtin_nan("0"); * @brief Defines bitwise operators for an enum class. */ #define DEFINE_ENUM_FLAGS(T) \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator~ (T a) { return static_cast(~(std::underlying_type_t)a); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator!= (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a != b; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator== (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a == b; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator&& (const T a, const T b) { return (std::underlying_type_t)a != 0 && (std::underlying_type_t)b != 0; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator&& (const T a, const bool b) { return (std::underlying_type_t)a != 0 && b; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL constexpr T operator| (const T a, const T b) { return (T)((std::underlying_type_t)a | (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator& (const T a, const T b) { return (T)((std::underlying_type_t)a & (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator^ (const T a, const T b) { return (T)((std::underlying_type_t)a ^ (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator|= (T& a, const T b) { return (T&)((std::underlying_type_t&)a |= (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator&= (T& a, const T b) { return (T&)((std::underlying_type_t&)a &= (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator^= (T& a, const T b) { return (T&)((std::underlying_type_t&)a ^= (std::underlying_type_t)b); } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool test(const T a, const T b) { return (a & b) == b; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool any(const T a, const T b) { return (a & b) != 0; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool none(const T a, const T b) { return (a & b) == 0; } \ - FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool one(const T a, const T b) { const auto bits = ((std::underlying_type_t)a & (std::underlying_type_t)b); return bits && !(bits & (bits-1)); } + FOUNDATION_FORCEINLINE T operator~ (T a) { return static_cast(~(std::underlying_type_t)a); } \ + FOUNDATION_FORCEINLINE bool operator!= (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a != b; } \ + FOUNDATION_FORCEINLINE bool operator== (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a == b; } \ + FOUNDATION_FORCEINLINE bool operator&& (const T a, const T b) { return (std::underlying_type_t)a != 0 && (std::underlying_type_t)b != 0; } \ + FOUNDATION_FORCEINLINE bool operator&& (const T a, const bool b) { return (std::underlying_type_t)a != 0 && b; } \ + FOUNDATION_FORCEINLINE constexpr T operator| (const T a, const T b) { return (T)((std::underlying_type_t)a | (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE T operator& (const T a, const T b) { return (T)((std::underlying_type_t)a & (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE T operator^ (const T a, const T b) { return (T)((std::underlying_type_t)a ^ (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE T& operator|= (T& a, const T b) { return (T&)((std::underlying_type_t&)a |= (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE T& operator&= (T& a, const T b) { return (T&)((std::underlying_type_t&)a &= (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE T& operator^= (T& a, const T b) { return (T&)((std::underlying_type_t&)a ^= (std::underlying_type_t)b); } \ + FOUNDATION_FORCEINLINE bool test(const T a, const T b) { return (a & b) == b; } \ + FOUNDATION_FORCEINLINE bool any(const T a, const T b) { return (a & b) != 0; } \ + FOUNDATION_FORCEINLINE bool none(const T a, const T b) { return (a & b) == 0; } \ + FOUNDATION_FORCEINLINE bool one(const T a, const T b) { const auto bits = ((std::underlying_type_t)a & (std::underlying_type_t)b); return bits && !(bits & (bits-1)); } //////////////////////////////////////////////////////////////////////////// // ## Generics @@ -66,7 +66,7 @@ constexpr double DNAN = __builtin_nan("0"); * * @return The minimal value. */ -template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T min(T a, T b) { return (((a) < (b)) ? (a) : (b)); } +template FOUNDATION_FORCEINLINE T min(T a, T b) { return (((a) < (b)) ? (a) : (b)); } /*! Returns the maximal value of two values. * @@ -75,7 +75,7 @@ template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T min(T a, T b) * * @return The maximal value. */ -template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T max(T a, T b) { return (((a) > (b)) ? (a) : (b)); } +template FOUNDATION_FORCEINLINE T max(T a, T b) { return (((a) > (b)) ? (a) : (b)); } //////////////////////////////////////////////////////////////////////////// // ## URLs diff --git a/framework/dispatcher.cpp b/framework/dispatcher.cpp index 6e0dfd3..deb0aec 100644 --- a/framework/dispatcher.cpp +++ b/framework/dispatcher.cpp @@ -70,7 +70,7 @@ static objectmap_t* _dispatcher_threads = nullptr; // # PRIVATE // -FOUNDATION_EXTERN bool dispatcher_process_events() +extern bool dispatcher_process_events() { PERFORMANCE_TRACKER("dispatcher_process_events"); diff --git a/framework/dispatcher.h b/framework/dispatcher.h index 152c849..e493a0d 100644 --- a/framework/dispatcher.h +++ b/framework/dispatcher.h @@ -217,7 +217,7 @@ dispatcher_event_listener_id_t dispatcher_register_event_listener( * * @param event_name Name of the event, must be a static string literal, i.e. "RENDER_FRAME" * @param event_name_length Length of the event name - * @param callback Callback to be invoked when the event is triggered. + * @param callback Callback to be invoked when the event is triggered (#bool(const dispatcher_event_args_t& args)) * @param options Registration and event execution options * @param user_data User data to be passed to the callback when invoked. * diff --git a/framework/localization.cpp b/framework/localization.cpp index c57e5da..550b2c0 100644 --- a/framework/localization.cpp +++ b/framework/localization.cpp @@ -85,7 +85,7 @@ static struct LOCALIZATION_MODULE } *_localization_module = nullptr; -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL int localization_string_locale_key_compare(const string_locale_t& lc, const hash_t& key) +FOUNDATION_FORCEINLINE int localization_string_locale_key_compare(const string_locale_t& lc, const hash_t& key) { if (lc.key < key) return -1; diff --git a/framework/localization.h b/framework/localization.h index b874c6e..6fee4ed 100644 --- a/framework/localization.h +++ b/framework/localization.h @@ -61,7 +61,7 @@ const char* tr_cstr(const char* str, size_t length = SIZE_MAX); #define RTEXT(str) CTEXT(str) -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL const char* tr(const char* str, size_t length, bool literal = false) +FOUNDATION_FORCEINLINE const char* tr(const char* str, size_t length, bool literal = false) { return string_const(str, length); } diff --git a/framework/memory.h b/framework/memory.h index 111ae43..b1c300b 100644 --- a/framework/memory.h +++ b/framework/memory.h @@ -34,10 +34,12 @@ template using alias = T; * @brief Deallocate memory for an object and invoking the destructor. * @param ptr The pointer to the object to deallocate. */ -#define MEM_DELETE(ptr) { \ - ptr->~alias::type>(); \ - memory_deallocate(ptr); \ - ptr = nullptr; \ +#define MEM_DELETE(ptr) { \ + if (ptr) { \ + ptr->~alias::type>(); \ + memory_deallocate(ptr); \ + ptr = nullptr; \ + } \ } /*! @def MEM_DELETE_ARRAY diff --git a/framework/plugin.cpp b/framework/plugin.cpp new file mode 100644 index 0000000..1afa76b --- /dev/null +++ b/framework/plugin.cpp @@ -0,0 +1,310 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + * + * Plugin Implementation + */ + +#include "plugin.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#define HASH_PLUGIN static_hash_string("plugin", 6, 0xcbbfaea91dab646ULL) + +struct plugin_t +{ + object_t handle{ 0 }; + api_interface_t* api{ nullptr }; + + load_plugin_api_fn load{ nullptr }; + unload_plugin_api_fn unload{ nullptr }; + + api_plugin_context_t context; +}; + +static plugin_t* _plugins = nullptr; + +FOUNDATION_FORCEINLINE plugin_t* plugin_get(plugin_handle_t handle) +{ + FOUNDATION_ASSERT(handle.index < array_size(_plugins)); + return &_plugins[handle.index]; +} + +FOUNDATION_FORCEINLINE plugin_t* plugin_resolve(plugin_handle_t handle) +{ + if (handle.index >= array_size(_plugins)) + return nullptr; + return &_plugins[handle.index]; +} + +template +FOUNDATION_FORCEINLINE void plugin_invoke_callback(plugin_t* plugin, const char* name, size_t length, Handler* handler, void* user_data = nullptr) +{ + app_callback_t cb; + cb.handler = handler; + cb.user_data = user_data; + cb.context = plugin; + exception_try([](void* context) + { + auto* cb = (app_callback_t*)context; + + if constexpr (std::is_same_vhandler), void (*)()>) + { + cb->handler(); + } + else + { + cb->handler(cb->user_data); + } + return 0; + }, &cb, [](void* context, const char* file, size_t length) + { + auto* cb = (app_callback_t*)context; + plugin_t* plugin = (plugin_t*)cb->context; + if (plugin) + log_errorf(plugin->context.context, ERROR_EXCEPTION, STRING_CONST("Exception in %s plugin (%.*s)"), plugin->context.name, (int)length, file); + else + log_errorf(HASH_PLUGIN, ERROR_EXCEPTION, STRING_CONST("Exception in plugin (%.*s)"), (int)length, file); + }, name, length); +} + +FOUNDATION_STATIC void app_initialize_apis(const api_plugin_context_t* context, api_interface_t** api) +{ + FOUNDATION_ASSERT(api); + + plugin_t* plugin = plugin_get({context->reserved}); + plugin->api = *api = (api_interface_t*)memory_allocate(HASH_PLUGIN, sizeof(api_interface_t), 8, MEMORY_PERSISTENT | MEMORY_ZERO_INITIALIZED); + + // + // App API + // + static api_app_t app_api + { + // register_menu + [](const char* path, size_t path_length, const char* shortcut, size_t shortcut_length, api_app_menu_flags_t flags, void(*callback)(void*), void* user_data) + { + app_register_menu(HASH_PLUGIN, path, path_length, shortcut, shortcut_length, (app_menu_flags_t)flags, [callback](void* user_data) + { + plugin_invoke_callback(nullptr, STRING_CONST("plugin_register_menu"), callback, user_data); + }, user_data); + } + }; + FOUNDATION_ASSERT(AppMenuFlags::None == APP_MENU_NONE); + FOUNDATION_ASSERT(AppMenuFlags::Append == APP_MENU_APPEND); + FOUNDATION_ASSERT(AppMenuFlags::Shortcut == APP_MENU_SHORTCUT); + + // + // Log API + // + static api_log_t log_api + { + // info + [](hash_t context, const char* fmt, size_t length, ...) + { + va_list args; + va_start(args, length); + + string_t str = string_allocate_vformat(fmt, length, args); + log_info(context, STRING_ARGS(str)); + string_deallocate(str.str); + va_end(args); + }, + + // warning + [](hash_t context, const char* fmt, size_t length, ...) + { + va_list args; + va_start(args, length); + string_t str = string_allocate_vformat(fmt, length, args); + log_warn(context, WARNING_SCRIPT, STRING_ARGS(str)); + string_deallocate(str.str); + va_end(args); + }, + + // error + [](hash_t context, uint32_t err, const char* fmt, size_t length, ...) + { + va_list args; + va_start(args, length); + string_t str = string_allocate_vformat(fmt, length, args); + log_error(context, (error_t)err, STRING_ARGS(str)); + string_deallocate(str.str); + va_end(args); + }, + }; + + // + // Dispatcher API + // + static api_dispatcher_t dispatcher_api + { + // register_event + [](const char* event_name, size_t event_name_length, bool(*callback)(void* payload, size_t payload_size, void* user_data), void* user_data) + { + return dispatcher_register_event_listener(event_name, event_name_length, [callback](const dispatcher_event_args_t& args) + { + return callback(args.data, args.size, args.user_data); + }, DISPATCHER_EVENT_OPTION_NONE, user_data); + }, + + // unregister_event + [](uint32_t register_event_id) + { + return dispatcher_unregister_event_listener(register_event_id); + }, + + // post_event + [](const char* event_name, size_t event_name_length, void* payload, size_t payload_size) + { + return dispatcher_post_event(event_name, event_name_length, payload, + payload_size, payload_size > 0 ? DISPATCHER_EVENT_OPTION_COPY_DATA : DISPATCHER_EVENT_OPTION_NONE); + }, + + // call_delayed + [](void(*callback)(void* user_data), void* user_data) + { + if (user_data) + dispatch([callback, user_data]() { callback(user_data); }); + else + dispatch([callback]() { callback(nullptr); }); + }, + }; + + (*api)->app = &app_api; + (*api)->log = &log_api; + (*api)->dispatcher = &dispatcher_api; +} + +// +// PUBLIC +// + +plugin_handle_t plugin_load(const char* path, size_t length) +{ + plugin_t plugin{}; + string_const_t plugin_file_name = path_file_name(path, length); + + plugin.handle = library_load(path, length); + if (plugin.handle == 0) + { + log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to load plugin: %.*s"), STRING_FORMAT(plugin_file_name)); + return { PLUGIN_HANDLE_INVALID }; + } + + // Make sure the dll has a valid plugin interface (i.e. `load_plugin(...)`) + plugin.load = (load_plugin_api_fn)library_symbol(plugin.handle, STRING_CONST("load_plugin")); + plugin.unload = (unload_plugin_api_fn)library_symbol(plugin.handle, STRING_CONST("unload_plugin")); + if (plugin.load == nullptr) + { + log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to initialize plugin: %.*s"), STRING_FORMAT(plugin_file_name)); + library_release(plugin.handle); + return { PLUGIN_HANDLE_INVALID }; + } + + plugin_handle_t handle = { array_size(_plugins) }; + plugin.context.reserved = handle.index; + plugin.context.initialize_apis = app_initialize_apis; + + string_const_t plugin_base_name = path_base_file_name(STRING_ARGS(plugin_file_name)); + string_copy(STRING_BUFFER(plugin.context.name), STRING_ARGS(plugin_base_name)); + + array_push(_plugins, plugin); + int exit_code = plugin.load(&plugin.context); + if (exit_code != 0) + { + log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to initialize plugin: %.*s (%d)"), STRING_FORMAT(plugin_file_name), exit_code); + library_release(plugin.handle); + array_pop_safe(_plugins); + return { PLUGIN_HANDLE_INVALID }; + } + + _plugins[handle.index].context = plugin.context; + log_infof(plugin.context.context, STRING_CONST("Loaded plugin: %s v.%d.%d"), plugin.context.name, plugin.context.version_major, plugin.context.version_minor); + return handle; +} + +unsigned plugin_count() +{ + return array_size(_plugins); +} + +void plugin_render() +{ + for (unsigned i = 0, end = array_size(_plugins); i < end; ++i) + { + plugin_t* plugin = _plugins + i; + if (plugin->context.callbacks.render) + plugin_invoke_callback(plugin, STRING_CONST("plugin_render"), plugin->context.callbacks.render); + } +} + +void plugin_update() +{ + for (unsigned i = 0, end = array_size(_plugins); i < end; ++i) + { + plugin_t* plugin = _plugins + i; + if (plugin->context.callbacks.update) + plugin_invoke_callback(plugin, STRING_CONST("plugin_update"), plugin->context.callbacks.update); + } +} + +void plugin_unload(plugin_handle_t handle) +{ + plugin_t* plugin = plugin_get(handle); + if (plugin->unload) + plugin->unload(); + + if (plugin->api) + memory_deallocate(plugin->api); + + library_release(plugin->handle); +} + +// +// SYSTEM +// + +FOUNDATION_STATIC void plugins_initialize() +{ + // Find plugin directory + char plugin_dir_buffer[BUILD_MAX_PATHLEN]; + string_const_t resources_dir = environment_get_resources_path(); + string_t plugin_dir = string_copy(plugin_dir_buffer, sizeof(plugin_dir_buffer), resources_dir.str, resources_dir.length); + plugin_dir = string_append(STRING_ARGS(plugin_dir), sizeof(plugin_dir_buffer), STRING_CONST("/plugins")); + if (!fs_is_directory(STRING_ARGS(plugin_dir))) + return; + + // Load plugins (i.e. all DLLs in plugin directory) + string_t* plugin_file_names = fs_matching_files(STRING_ARGS(plugin_dir), STRING_CONST("^.*\\.dll$"), false); + for (unsigned i = 0, end = array_size(plugin_file_names); i < end; ++i) + { + const string_t& plugin_file_name = plugin_file_names[i]; + + char plugin_file_path_buffer[BUILD_MAX_PATHLEN]; + string_t plugin_path = path_concat(STRING_BUFFER(plugin_file_path_buffer), STRING_ARGS(plugin_dir), STRING_ARGS(plugin_file_name)); + + plugin_load(STRING_ARGS(plugin_path)); + } + + string_array_deallocate(plugin_file_names); +} + +FOUNDATION_STATIC void plugins_shutdown() +{ + for (unsigned i = 0, end = array_size(_plugins); i < end; ++i) + { + plugin_t* plugin = _plugins + i; + plugin_unload({i}); + } + array_deallocate(_plugins); +} + +DEFINE_MODULE(PLUGIN, plugins_initialize, plugins_shutdown, MODULE_PRIORITY_BASE); diff --git a/framework/plugin.h b/framework/plugin.h new file mode 100644 index 0000000..0e5d5cf --- /dev/null +++ b/framework/plugin.h @@ -0,0 +1,45 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + * + * Plugin interface + */ + +#pragma once + +#include + +/*! Define an invalid plugin handle value. */ +constexpr const uint32_t PLUGIN_HANDLE_INVALID = UINT32_MAX; + +/*! Plugin handle. */ +struct plugin_handle_t { uint32_t index{ PLUGIN_HANDLE_INVALID }; }; + +/*! Load a plugin shared library. + * + * @param path Path to plugin. + * @param length Length of path. + * + * @return Plugin handle if loaded successfully, otherwise PLUGIN_HANDLE_INVALID. + */ +plugin_handle_t plugin_load(const char* path, size_t length); + +/*! Unload plugin shared library. + * + * @param plugin Plugin handle. + */ +void plugin_unload(plugin_handle_t plugin); + +/*! Returns how many plugins are loaded. + * + * @return Number of plugins. + */ +unsigned plugin_count(); + +/*! Application entry point to lets plugin render any ImGui windows. + */ +void plugin_render(); + +/*! Application entry point to let plugins update their internal state. + */ +void plugin_update(); diff --git a/framework/string.cpp b/framework/string.cpp index 27eb042..3176ae8 100644 --- a/framework/string.cpp +++ b/framework/string.cpp @@ -2149,7 +2149,7 @@ bool string_try_convert_number(const char* str, size_t length, double& out_value bool string_try_convert_number(const char* str, size_t length, int& out_value, const int radix /*= 10*/) { // Make sure the string starts with a digit or a sign - if (length == 0 || !isdigit(str[0]) && str[0] != '-') + if (length == 0 || (!isdigit(str[0]) && str[0] != '-')) return false; char* end = (char*)str + length; diff --git a/framework/string_template.inl.h b/framework/string_template.inl.h index a0c17a4..941e89c 100644 --- a/framework/string_template.inl.h +++ b/framework/string_template.inl.h @@ -169,41 +169,43 @@ struct string_template_arg_value_t string_template_stream_handler_t stream; }; -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(bool t) { return StringArgumentType::BOOL; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int t, std::true_type) { return StringArgumentType::INT32; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(unsigned int t, std::true_type) { return StringArgumentType::UINT32; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int64_t t, std::true_type) { return StringArgumentType::INT64; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(uint64_t t, std::true_type) { return StringArgumentType::UINT64; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(float t, std::true_type) { return StringArgumentType::FLOAT; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(double t, std::false_type) { return StringArgumentType::DOUBLE; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const char* t, std::false_type) { return StringArgumentType::CSTRING; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(std::nullptr_t t, std::false_type) { return StringArgumentType::POINTER; } - -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const string_t& t) +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(bool t) { return StringArgumentType::BOOL; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int t, std::true_type) { return StringArgumentType::INT32; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(long t, std::true_type) { return StringArgumentType::INT32; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(unsigned int t, std::true_type) { return StringArgumentType::UINT32; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(unsigned long t, std::true_type) { return StringArgumentType::UINT32; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int64_t t, std::true_type) { return StringArgumentType::INT64; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(uint64_t t, std::true_type) { return StringArgumentType::UINT64; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(float t, std::true_type) { return StringArgumentType::FLOAT; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(double t, std::false_type) { return StringArgumentType::DOUBLE; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const char* t, std::false_type) { return StringArgumentType::CSTRING; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(std::nullptr_t t, std::false_type) { return StringArgumentType::POINTER; } + +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const string_t& t) { return StringArgumentType::STRING; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const string_const_t& t) +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const string_const_t& t) { return StringArgumentType::STRING; } template -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(T* arr, std::false_type) +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(T* arr, std::false_type) { return StringArgumentType::POINTER; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(string_template_stream_handler_t func) +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(string_template_stream_handler_t func) { return StringArgumentType::STREAM; } -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int* arr) { return StringArgumentType::ARRAY_INT; } +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int* arr) { return StringArgumentType::ARRAY_INT; } template -FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const T& t) +FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const T& t) { return string_template_type(t, typename std::is_integral::type()); } @@ -234,7 +236,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1) { - string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32))); + string_t buffer = string_static_buffer((size_t)max(SIZE_C(64), SIZE_C(N + 32)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1)); } @@ -260,7 +262,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2) { - string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 2))); + string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 2)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2)); } @@ -286,7 +288,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3) { - string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 3))); + string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 3)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3)); } @@ -315,7 +317,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4) { - string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 4))); + string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 4)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4)); } @@ -345,7 +347,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5) { - string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 5))); + string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 5)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5)); } @@ -377,7 +379,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6) { - string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 6))); + string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 6)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6)); @@ -411,7 +413,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7) { - string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 7))); + string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 7)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7)); @@ -445,7 +447,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8) { - string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 8))); + string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 8)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8)); @@ -479,7 +481,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9) { - string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 9))); + string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 9)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8, string_template_type(p9), p9)); @@ -515,7 +517,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9, const P10& p10) { - string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 10))); + string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 10)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8, @@ -552,7 +554,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N], template FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9, const P10& p10, const P11& p11) { - string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 11))); + string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 11)), false); return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8, string_template_type(p9), p9, string_template_type(p10), p10, diff --git a/framework/system.mm b/framework/system.mm index be7156a..6646ce2 100644 --- a/framework/system.mm +++ b/framework/system.mm @@ -24,14 +24,17 @@ bool system_open_file_dialog(const char* dialog_title, if (!app_window) return false; + string_t file_path = {}; string_t file_path_buffer = string_static_buffer(1024, true); if (current_file_path != nullptr) { - string_t file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path); + file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path); file_path = path_clean(STRING_ARGS(file_path), file_path_buffer.length); file_path = string_replace(STRING_ARGS(file_path), file_path_buffer.length, STRING_CONST("/"), STRING_CONST("\\"), true); } + + string_const_t filename = path_file_name(STRING_ARGS(file_path)); @autoreleasepool { @@ -45,7 +48,51 @@ bool system_open_file_dialog(const char* dialog_title, openPanel.canCreateDirectories = YES; openPanel.allowsMultipleSelection = NO; - openPanel.allowedFileTypes = @[@"dcm"]; + if (extension) + { + // Split extension string into array + string_t* exts = string_split(string_to_const(extension), CTEXT("|")); + + // Add extension to savePanel.allowedFileTypes + for (unsigned int i = 0; i < array_size(exts); ++i) + { + // Skip description + if (i % 2 == 0) + continue; + string_t ext = exts[i]; + // Remove *. from extension + ext.str += 2; + ext.length -= 2; + + NSString* ns_ext = [NSString stringWithUTF8String:ext.str]; + [openPanel setAllowedFileTypes:[openPanel.allowedFileTypes arrayByAddingObject:ns_ext]]; + + // Add default extension to savePanel.nameFieldStringValue + if (filename.length != 0 && string_find(STRING_ARGS(filename), '.', 0)) + { + NSString* ns_filename = [NSString stringWithUTF8String:filename.str]; + openPanel.nameFieldStringValue = ns_filename; + + // Append . to filename + ns_filename = [ns_filename stringByAppendingString:@"."]; + + // Add .extension to filename + NSString* ns_ext = [NSString stringWithUTF8String:ext.str]; + openPanel.nameFieldStringValue = [ns_filename stringByAppendingString:ns_ext]; + } + } + + string_array_deallocate(exts); + + openPanel.allowsOtherFileTypes = NO; + } + else + { + openPanel.allowsOtherFileTypes = YES; + } + + // Create a local copy of the callback for the async call to the save panel + function selected_file_callback_copy = selected_file_callback; [openPanel beginSheetModalForWindow: app_window completionHandler:^(NSInteger result) { @@ -60,7 +107,113 @@ bool system_open_file_dialog(const char* dialog_title, static char selected_file_path_buffer[BUILD_MAX_PATHLEN]; string_t selected_file_path = string_copy(STRING_BUFFER(selected_file_path_buffer), path.UTF8String, path.length); - selected_file_callback(string_to_const(selected_file_path)); + selected_file_callback_copy(string_to_const(selected_file_path)); + } + }]; + } + + return true; +} + +bool system_save_file_dialog( + const char* dialog_title, + const char* extension, + const char* current_file_path, + const function& selected_file_callback) +{ + NSWindow* app_window = (NSWindow*)_window_handle; + if (!app_window) + return false; + + string_t file_path = {}; + string_t file_path_buffer = string_static_buffer(1024, true); + if (current_file_path != nullptr) + { + file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path); + file_path = path_clean(STRING_ARGS(file_path), file_path_buffer.length); + file_path = string_replace(STRING_ARGS(file_path), file_path_buffer.length, + STRING_CONST("/"), STRING_CONST("\\"), true); + } + + string_const_t filename = path_file_name(STRING_ARGS(file_path)); + + @autoreleasepool + { + // Open save dialog + NSSavePanel* savePanel = [NSSavePanel savePanel]; + + savePanel.title = [NSString stringWithUTF8String:dialog_title]; + + savePanel.showsHiddenFiles = NO; + savePanel.showsResizeIndicator = YES; + savePanel.canCreateDirectories = YES; + + if (filename.length > 0) + { + NSString* ns_filename = [NSString stringWithUTF8String:filename.str]; + savePanel.nameFieldStringValue = ns_filename; + } + + if (extension) + { + // Split extension string into array + string_t* exts = string_split(string_to_const(extension), CTEXT("|")); + + // Add extension to savePanel.allowedFileTypes + for (unsigned int i = 0; i < array_size(exts); ++i) + { + // Skip description + if (i % 2 == 0) + continue; + string_t ext = exts[i]; + // Remove *. from extension + ext.str += 2; + ext.length -= 2; + + NSString* ns_ext = [NSString stringWithUTF8String:ext.str]; + [savePanel setAllowedFileTypes:[savePanel.allowedFileTypes arrayByAddingObject:ns_ext]]; + + // Add default extension to savePanel.nameFieldStringValue + if (filename.length != 0 && string_find(STRING_ARGS(filename), '.', 0)) + { + NSString* ns_filename = [NSString stringWithUTF8String:filename.str]; + savePanel.nameFieldStringValue = ns_filename; + + // Append . to filename + ns_filename = [ns_filename stringByAppendingString:@"."]; + + // Add .extension to filename + NSString* ns_ext = [NSString stringWithUTF8String:ext.str]; + savePanel.nameFieldStringValue = [ns_filename stringByAppendingString:ns_ext]; + } + } + + string_array_deallocate(exts); + + savePanel.allowsOtherFileTypes = NO; + } + else + { + savePanel.allowsOtherFileTypes = YES; + } + + // Create a local copy of the callback for the async call to the save panel + function selected_file_callback_copy = selected_file_callback; + + [savePanel beginSheetModalForWindow: app_window completionHandler:^(NSInteger result) + { + // If the result is NSOKButton the user selected a file + if (result == NSModalResponseOK) + { + // Get the save path file + NSURL *selection = savePanel.URL; + + //finally store the selected file path as a string + NSString* path = [[selection path] stringByResolvingSymlinksInPath]; + + static char selected_file_path_buffer[BUILD_MAX_PATHLEN]; + string_t selected_file_path = string_copy(STRING_BUFFER(selected_file_path_buffer), path.UTF8String, path.length); + selected_file_callback_copy(string_to_const(selected_file_path)); } }]; } diff --git a/framework/tests/dispatcher_tests.cpp b/framework/tests/dispatcher_tests.cpp index d9c1dc3..9edf4be 100644 --- a/framework/tests/dispatcher_tests.cpp +++ b/framework/tests/dispatcher_tests.cpp @@ -13,7 +13,7 @@ #include -FOUNDATION_EXTERN bool dispatcher_process_events(); +extern bool dispatcher_process_events(); TEST_SUITE("Dispatcher") { diff --git a/framework/tests/expr_tests.cpp b/framework/tests/expr_tests.cpp index 099612d..36218c6 100644 --- a/framework/tests/expr_tests.cpp +++ b/framework/tests/expr_tests.cpp @@ -53,7 +53,7 @@ template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&ex return result; } -template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&expr)[N], int expected) +template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&expr)[N], int32_t expected) { expr_result_t result = eval({expr, N - 1}); @@ -150,9 +150,9 @@ TEST_SUITE("Expressions") test_expr("3/2", 3.0 / 2.0); test_expr("(3/2)|0", 3 / 2); test_expr("(3/0)", INFINITY); - test_expr("(3/0)|0", -2147483648i32); + test_expr("(3/0)|0", (int32_t)INT32_C(-2147483648)); test_expr("(3%0)", NAN); - test_expr("(3%0)|0", -2147483648i32); + test_expr("(3%0)|0", (int32_t)INT32_C(-2147483648)); test_expr("2**3", 8); test_expr("9**(1/2)", 3); test_expr("1+2<<3", (1 + 2) << 3); @@ -795,7 +795,7 @@ TEST_SUITE("Expressions") expr_register_function("ptr_i64", [](const expr_func_t* f, vec_expr_t* args, void* c) -> expr_result_t { - static constexpr int64_t n[] = {INT64_MIN, INT64_MAX, 0i64}; + static constexpr int64_t n[] = {INT64_MIN, INT64_MAX, (int64_t)0}; return expr_result_t((void*)&n, sizeof(n[0]), ARRAY_COUNT(n), EXPR_POINTER_ARRAY | EXPR_POINTER_ARRAY_INTEGER); }); diff --git a/framework/window.cpp b/framework/window.cpp index f1e6c03..00fa640 100644 --- a/framework/window.cpp +++ b/framework/window.cpp @@ -959,9 +959,9 @@ void window_update() window_t* win = (window_t*)args; window_render(win); return 0; - }, win, [](const char* file, size_t length) + }, win, [](void* context, const char* file, size_t length) { - log_errorf(HASH_WINDOW, ERROR_EXCEPTION, "Exception in window render: %.*s", (int)length, file); + log_errorf(HASH_WINDOW, ERROR_EXCEPTION, STRING_CONST("Exception in window render: %.*s"), (int)length, file); }, STRING_CONST("window_dump")); // Check if the window should be closed diff --git a/plugins/api/api.h b/plugins/api/api.h new file mode 100644 index 0000000..ea93c91 --- /dev/null +++ b/plugins/api/api.h @@ -0,0 +1,224 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + * + * Framework API interface + */ + +#pragma once + +#include + +#ifndef FOUNDATION_FORCEINLINE +# define FOUNDATION_FORCEINLINE inline +#endif + +typedef uint8_t byte_t; +typedef uint64_t hash_t; +typedef int64_t tick_t; +typedef double deltatime_t; +typedef uint32_t object_t; +typedef uint128_t uuid_t; +typedef void* user_data_t; + +constexpr object_t INVALID_HANDLE = UINT32_MAX; + +/*! Default API callback with user data context. */ +typedef void(*api_callback_t)(void* user_data); + +/*! API callback with payload and user data context. */ +typedef void(*api_callback_payload_t)(void* payload, size_t payload_size, void* user_data); + +/*! Set of flags used to customize the registration of a new menu item. + * + * Sync with #AppMenuFlags in app.h + */ +typedef enum : uint32_t +{ + APP_MENU_NONE = 0, + + /*! Insert the menu items after all other menu items, this helps preserve the system menu order. */ + APP_MENU_APPEND = 1 << 0, + + /*! Menu item defines a shortcut */ + APP_MENU_SHORTCUT = 1 << 1, + +} api_app_menu_flags_t; + +#ifndef BUILD_DYNAMIC_LINK +# define BUILD_DYNAMIC_LINK 0 +#endif + +#ifndef FOUNDATION_PLATFORM_WINDOWS + #ifdef _WIN32 + #define FOUNDATION_PLATFORM_WINDOWS 1 + #else + #define FOUNDATION_PLATFORM_WINDOWS 0 + #endif +#endif + +#if BUILD_DYNAMIC_LINK && FOUNDATION_PLATFORM_WINDOWS +#ifdef __cplusplus +# define FRAMEWORK_API_EXPORT extern "C" __declspec(dllexport) +#else +# define FRAMEWORK_API_EXPORT __declspec(dllexport) +#endif + +#else +# define FRAMEWORK_API_EXPORT +#endif + +#ifndef STRING_PARAM +# define STRING_PARAM(__name) const char* __name, size_t __name##_length +#endif + +#ifndef STRING_CONST +# define STRING_CONST(__name) __name, sizeof(__name) - 1 +#endif + +struct api_app_t +{ + /*! Register a new menu item. + * + * @param path Path to the menu item, separated by slashes. + * @param shortcut Shortcut to the menu item, can be null. + * @param flags Flags to customize the registration of the menu item. + * @param callback Callback to invoke when the menu item is selected. + * @param user_data User data to pass to the callback. + */ + void (*register_menu)(STRING_PARAM(path), STRING_PARAM(shortcut), api_app_menu_flags_t flags, api_callback_t callback, user_data_t user_data); +}; + +struct api_log_t +{ + /*! Log a message with the given context. + * + * @param context Context of the message. + * @param fmt Format string. + * @param length Length of the format string. + * @param ... Arguments to the format string. + */ + void (*info)(hash_t context, const char* fmt, size_t length, ...); + + /*! Log a warning message with the given context. + * + * @param context Context of the message. + * @param fmt Format string. + * @param length Length of the format string. + * @param ... Arguments to the format string. + */ + void (*warning)(hash_t context, const char* fmt, size_t length, ...); + + /*! Log an error message with the given context. + * + * @param context Context of the message. + * @param err Error code. + * @param fmt Format string. + * @param length Length of the format string. + * @param ... Arguments to the format string. + */ + void (*error)(hash_t context, uint32_t err, const char* fmt, size_t length, ...); +}; + +struct api_dispatcher_t +{ + /*! Register a new event listener. + * + * @param event_name Name of the event. + * @param callback Callback to invoke when the event is posted. + * @param user_data User data to pass to the callback. + * @return Handle to the registered event. + */ + object_t (*register_event)(STRING_PARAM(event_name), bool(*callback)(void* payload, size_t payload_size, user_data_t user_data), user_data_t user_data); + + /*! Unregister an event listener. + * + * @param register_event_id Handle to the registered event. + * @return True if the event was unregistered, false otherwise. + */ + bool (*unregister_event)(object_t register_event_id); + + /*! Post an message event. + * + * @param event_name Name of the event. + * @param payload Payload to pass to the event listeners. + * @param payload_size Size of the payload. + * @return True if the event was posted, false otherwise. + */ + bool (*post_event)(STRING_PARAM(event_name), void* payload, size_t payload_size); + + /*! Dispatch a callback that will be invoked on the main thread. + * + * @param callback Callback to invoke. + * @param user_data User data to pass to the callback. + */ + void (*call_delayed)(api_callback_t callback, user_data_t user_data); +}; + +struct api_system_t {}; +struct api_expression_t {}; + +/*! Structure used to access the plugin API. */ +struct api_interface_t +{ + api_app_t* app{ nullptr }; + api_log_t* log{ nullptr }; + api_dispatcher_t* dispatcher{ nullptr }; + api_expression_t* expression{ nullptr }; + api_system_t* system{ nullptr }; +}; + +/*! Structure used to register callbacks for the plugin. */ +struct api_plugin_callbacks_t +{ + void (*update)() { nullptr }; + void (*render)() { nullptr }; +}; + +struct api_plugin_context_t; + +/*! Plugin API initialization structure */ +typedef void (*initialize_apis_fn)(const api_plugin_context_t* context, api_interface_t** api); + +/*! Structure used to load and initialize a plugin. + The plugin must implement the load_plugin and unload_plugin functions. +*/ +struct api_plugin_context_t +{ + /*! Set plugin context. This is used for logging and memory allocation. */ + hash_t context; + + /*! Display name of the plugin */ + char name[32]; + + /*! Version major of the plugin */ + int version_major; + + /*! Version minor of the plugin */ + int version_minor; + + /*! The plugin can invoke this function to initialize the APIs it needs. + * The framework will fill all the requested APIs and the plugin can use them. + */ + initialize_apis_fn initialize_apis; + + /*! Plugin callbacks that the system invokes at certain times. + * + * The callbacks will mainly be used for realtime events. Otherwise you can use the dispatcher + * to post events and register listeners. + */ + api_plugin_callbacks_t callbacks; + + /*! Reserved for internal usage */ + uint32_t reserved{ UINT32_MAX }; +}; + +/*! Load plugin function + @param plugin_context Plugin API context + @return 0 if plugin loaded successfully, <0 if error occurred +*/ +typedef int (*load_plugin_api_fn)(api_plugin_context_t* plugin_context); + +/*! Unload plugin function +*/ +typedef int (*unload_plugin_api_fn)(); diff --git a/plugins/test1/CMakeLists.txt b/plugins/test1/CMakeLists.txt new file mode 100644 index 0000000..3448ff3 --- /dev/null +++ b/plugins/test1/CMakeLists.txt @@ -0,0 +1,56 @@ +# +# Copyright 2023 Wiimag Inc. All rights reserved. +# License: https://equals-forty-two.com/LICENSE +# +# Framework plugin example CMakeLists.txt +# + +set(PluginId "test1") + +# Include shared module +include(${SHARED_MODULE_PATH}) + +# Define project +project(${PluginId}) + +# Load api headers +file(GLOB API_FILES ${PLUGINS_DIR}/api/*.h) +source_group("api" FILES ${API_FILES}) + +# Load plugin source files +file(GLOB_RECURSE SOURCES_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +source_group("src" FILES ${SOURCES_FILES}) + +if (MSVC) + add_compile_options("$<$:/Os>") + + # Import IMGUI API if needed (Symbols are exported from the app.exe) + add_compile_definitions("IMGUI_API=__declspec(dllimport)") +endif() + +# Set output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PLUGIN_BUILD_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PLUGIN_BUILD_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${PLUGIN_BUILD_DIR}) +set(CMAKE_PDB_OUTPUT_DIRECTORY ${PLUGIN_BUILD_DIR}) +set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG ${PLUGIN_BUILD_DIR}) +set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE ${PLUGIN_BUILD_DIR}) + +# Create shared library (dll) +add_library(${PluginId} SHARED ${API_FILES} ${SOURCES_FILES}) +set_target_properties(${PluginId} PROPERTIES FOLDER "plugins") + +# Define foundation_lib properties +target_include_directories(${PluginId} PUBLIC ${PLUGINS_DIR}/api) +target_include_directories(${PluginId} PUBLIC ${ROOT_DIR}/external/) +target_compile_definitions(${PluginId} PUBLIC -DBUILD_DYNAMIC_LINK=1) +target_compile_definitions(${PluginId} PUBLIC -DFOUNDATION_SIZE_REAL=8) + +if (MSVC) + set_target_properties(${PluginId} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") +endif() + +# Link with C:/work/framework/artifacts/lib/Debug/app.lib +target_link_libraries(${PluginId} PRIVATE ${ProjectId}) diff --git a/plugins/test1/test1.cpp b/plugins/test1/test1.cpp new file mode 100644 index 0000000..f37b689 --- /dev/null +++ b/plugins/test1/test1.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2023 - All rights reserved. + * License: https://equals-forty-two.com/LICENSE + * + * Test1 plugin example implementation. + * + * Plugins can use the foundation_lib and/or IMGUI exported functions, but that is not mandatory. + */ + +#include + +#include + +#include + +static int _update_counter = 0; +static bool _window_opened = true; +static api_interface_t* api = nullptr; + +#define HASH_PLUGIN_TEST1 static_hash_string("plugin_test1", 12, 0xc2f1f9448a0499bdULL) + +FOUNDATION_STATIC void test1_plugin_menu(void* context) +{ + FOUNDATION_UNUSED(context); + api->log->info(HASH_PLUGIN_TEST1, STRING_CONST("test1_plugin_menu")); + api->log->warning(HASH_PLUGIN_TEST1, STRING_CONST("test1_plugin_menu")); + api->log->error(HASH_PLUGIN_TEST1, ERROR_NONE, STRING_CONST("test1_plugin_menu")); +} + +FOUNDATION_STATIC void test1_plugin_crash(void* context) +{ + int* p = (int*)context; + *p = 0; +} + +FOUNDATION_STATIC void test1_plugin_update() +{ + ++_update_counter; +} + +FOUNDATION_STATIC void test1_plugin_render() +{ + if (!_window_opened) + return; + + if (!ImGui::Begin("Plugin test1", &_window_opened)) + return ImGui::End(); + + ImGui::Text("Hello App, I am a plugin! (%d)", _update_counter); + + if (ImGui::Button("Crash")) + test1_plugin_crash(nullptr); + ImGui::End(); +} + +FRAMEWORK_API_EXPORT int load_plugin(api_plugin_context_t* plugin) +{ + // Initialize plugin info + plugin->context = HASH_PLUGIN_TEST1; plugin->version_major = 1; plugin->version_minor = 0; + string_copy(STRING_BUFFER(plugin->name), STRING_CONST("test1")); + + // Load API interface + plugin->initialize_apis(plugin, &api); + + // Set some callbacks that the application will invoke on the plugin + plugin->callbacks.render = test1_plugin_render; + plugin->callbacks.update = test1_plugin_update; + + // Use the API to do things + api->app->register_menu(STRING_CONST("Plugins/test1"), nullptr, 0, APP_MENU_NONE, test1_plugin_menu, nullptr); + api->app->register_menu(STRING_CONST("Plugins/crash"), nullptr, 0, APP_MENU_NONE, test1_plugin_crash, nullptr); + + return 0; +} + +FRAMEWORK_API_EXPORT void unload_plugin() +{ + api = nullptr; +} diff --git a/run b/run index 70084fb..be78a12 100644 --- a/run +++ b/run @@ -28,33 +28,35 @@ FULL_SOLUTION_DIR=$(convert_path_to_platform $(pwd)/$SOLUTION_DIR) # # Parse common command line arguments # -POSITIONAL_ARGS=() +unset POSITIONAL_ARGS RUN=1 # Indicate if the default build should be run at the end -BUILD=0 +BUILD=() BUILD_DEBUG=0 BUILD_RELEASE=1 BUILD_DEPLOY=0 -TESTS=0 +TESTS=() OPEN=0 OPEN_WORKSPACE=0 -GENERATE=0 -GENERATED=0 -PACKAGE=0 -START=0 -PRINT=0 +GENERATE=() +PACKAGE=() +START=() +DIFF=() +PRINT=() PRINT_LARGEFILES=0 + HELP=0 VERBOSE=0 -DIFF=0 NO_COLOR=0 +NO=0 +YES=1 COMMAND_COUNTER=0 # Check if project/.build is present, if not, then we need to generate the project # Also check if the project/.build/CMakeCache.txt is present, if not, then we need to generate the project if [ ! -d "$SOLUTION_DIR" ] || [ ! -f "$SOLUTION_DIR/CMakeCache.txt" ]; then - BUILD=1 - GENERATE=1 + BUILD=($YES) + GENERATE=($YES) fi while [[ $# -gt 0 ]]; do @@ -66,7 +68,6 @@ while [[ $# -gt 0 ]]; do ;; --verbose) VERBOSE=1 - #POSITIONAL_ARGS+=("$1") # save positional arg shift # past argument ;; --no-color) @@ -76,7 +77,8 @@ while [[ $# -gt 0 ]]; do b|build) # Update the command counter COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - BUILD=$COMMAND_COUNTER + BUILD=($YES) + declare -g -n POSITIONAL_ARGS=BUILD shift # past argument # The release argument is the default, but remove it if it was specified @@ -98,7 +100,8 @@ while [[ $# -gt 0 ]]; do ;; p|print) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - PRINT=$COMMAND_COUNTER + PRINT=($YES) + declare -g -n POSITIONAL_ARGS=PRINT shift # past argument # The release argument is the default, but remove it if it was specified @@ -109,12 +112,14 @@ while [[ $# -gt 0 ]]; do ;; g|generate) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - GENERATE=$COMMAND_COUNTER + GENERATE=($YES) + declare -g -n POSITIONAL_ARGS=GENERATE shift # past argument ;; p|package) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - PACKAGE=$COMMAND_COUNTER + PACKAGE=($YES) + declare -g -n POSITIONAL_ARGS=PACKAGE shift # past argument ;; o|open) @@ -126,7 +131,7 @@ while [[ $# -gt 0 ]]; do if [ $# -ne 0 ] && ([ $1 == "workspace" ] || [ $1 == "w" ]); then # Always generate the project if we are opening the workspace - GENERATE=1 + GENERATE=($GENERATE) OPEN_WORKSPACE=1 shift fi @@ -134,23 +139,33 @@ while [[ $# -gt 0 ]]; do ;; t|tests) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - TESTS=$COMMAND_COUNTER + TESTS=($YES) + declare -g -n POSITIONAL_ARGS=TESTS shift # past argument ;; d|diff) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - DIFF=$COMMAND_COUNTER + DIFF=($YES) + declare -g -n POSITIONAL_ARGS=DIFF shift # past argument ;; s|start) COMMAND_COUNTER=$((COMMAND_COUNTER+1)) - START=$COMMAND_COUNTER + START=($YES) + declare -g -n POSITIONAL_ARGS=START RUN=1 shift # past argument ;; *) - POSITIONAL_ARGS+=("$1") # save positional arg + + if [ $POSITIONAL_ARGS ]; then + POSITIONAL_ARGS+=("$1") # Append to current command positional arguments + elif [ ${#START[@]} -eq 0 ]; then + START=($YES $1) + else + START+=("$1") # Assume this is a positional argument for the start command + fi shift # past argument ;; esac @@ -158,14 +173,16 @@ done # If there is not arguments to ./run, then lets force start to be the default command if [ $COMMAND_COUNTER -eq 0 ]; then - START=1 + if [ ${#START[@]} -eq 0 ]; then + START=($YES) + fi fi # Define a set of colors if NO_COLOR is not set if [ $NO_COLOR -eq 0 ]; then bold=$(tput bold) normal=$(tput sgr0) - green='\033[0;32m' + green=$(tput setaf 2) dark_gray='\033[1;30m' light_gray='\033[0;37m' red='\033[0;31m' @@ -253,18 +270,18 @@ if [ $VERBOSE -ge 1 ]; then # Print all set variables echo "APP_NAME: $APP_NAME" echo "SHORT_NAME: $SHORT_NAME" - echo "BUILD: $BUILD" + echo "BUILD: ${BUILD[@]}" echo " BUILD_DEBUG: $BUILD_DEBUG" echo " BUILD_RELEASE: $BUILD_RELEASE" echo " BUILD_DEPLOY: $BUILD_DEPLOY" echo "OPEN: $OPEN" echo " OPEN_WORKSPACE: $OPEN_WORKSPACE" - echo "GENERATE: $GENERATE" - echo "PRINT: $PRINT" + echo "GENERATE: ${GENERATE[@]}" + echo "PRINT: ${PRINT[@]}" echo " PRINT_LARGEFILES: $PRINT_LARGEFILES" - echo "TESTS: $TESTS" - echo "DIFF: $DIFF" - echo "START: $START" + echo "TESTS: ${TESTS[@]}" + echo "DIFF: ${DIFF[@]}" + echo "START: ${START[@]}" echo "HELP: $HELP" echo "VERBOSE: $VERBOSE" @@ -275,7 +292,7 @@ fi # # Generate the CMAKE solution # -if [ $GENERATE -ge 1 ]; then +if [ ${#GENERATE[@]} -ge 1 ] && [ ${GENERATE[0]} -ge 1 ]; then echo echo -e "${green}Generating ${APP_NAME} solution...${normal}" @@ -285,11 +302,9 @@ if [ $GENERATE -ge 1 ]; then mkdir -p $SOLUTION_DIR 2>/dev/null cd $SOLUTION_DIR - CMAKE_ARGS=() - if [ $GENERATE -eq $COMMAND_COUNTER ]; then - CMAKE_ARGS=(${POSITIONAL_ARGS[@]}) - fi - + # Copy second to last parameters to CMAKE_ARGS + CMAKE_ARGS=("${GENERATE[@]:1}") + # Read build.settings and add all the name=value pairs to the CMAKE_ARGS array as -Dname=value while IFS='=' read -r name value; do # Skip comments @@ -302,7 +317,7 @@ if [ $GENERATE -ge 1 ]; then [[ $name != BUILD_* ]] && continue # Skip if -D$name* is already POSTIONAL_ARGS - [[ " ${POSITIONAL_ARGS[@]} " =~ " -D$name" ]] && continue + [[ " ${GENERATE[@]} " =~ " -D$name" ]] && continue CMAKE_ARGS+=("-D$name=$value") done < $CONFIG_DIR/build.settings @@ -336,7 +351,6 @@ if [ $GENERATE -ge 1 ]; then # Keep track of it the solution was generated or not. # We will use this to determine if we need to build the solution or not. - GENERATED=1 cd ../../ # Print the path of the generated solution @@ -350,7 +364,7 @@ fi # # Build solution # -if [ $BUILD -ge 1 ]; then +if [ ${#BUILD[@]} -ge 1 ] && [ ${BUILD[0]} -ge 1 ]; then # Print building message in green echo @@ -368,19 +382,22 @@ if [ $BUILD -ge 1 ]; then fi CMAKE_ARGS+=("-fileLogger") CMAKE_ARGS+=("-fileLoggerParameters:logfile=cmake-build.log;verbosity=Normal") + elif [ ${machine} == "Mac" ]; then + # Use -quiet if verbose is not enabled + if [ $VERBOSE -eq 0 ]; then + CMAKE_ARGS+=("-quiet") + fi fi - ADDITIONAL_ARGS=() - if [ $BUILD -eq $COMMAND_COUNTER ]; then - ADDITIONAL_ARGS=(${POSITIONAL_ARGS[@]}) - fi - + # Copy second to last parameters to CMAKE_ARGS + ADDITIONAL_ARGS=("${BUILD[@]:1}") + if [ $BUILD_DEBUG -eq 1 ]; then - cmake --build . --parallel 7 --config Debug ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} + cmake --build . --parallel 7 --config Debug --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} elif [ $BUILD_DEPLOY -eq 1 ]; then - cmake --build . --parallel 7 --config Deploy ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} + cmake --build . --parallel 7 --config Deploy --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} else - cmake --build . --parallel 7 --config Release ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} + cmake --build . --parallel 7 --config Release --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]} fi retval=$? @@ -402,14 +419,18 @@ fi # # Build package # -if [ $PACKAGE -ge 1 ]; then +if [ ${#PACKAGE[@]} -ge 1 ] && [ ${PACKAGE[0]} -ge 1 ]; then # Run script to build the package echo echo -e "${green}Building ${APP_NAME} package...${normal}" echo - ./scripts/build-package.sh --build ${POSITIONAL_ARGS[@]} + # Capture all additional arguments + ADDITIONAL_ARGS=("${PACKAGE[@]:1}") + + # Run the packaging script + ./scripts/build-package.sh --build ${ADDITIONAL_ARGS[@]} retval=$? if [ $retval -ne 0 ]; then @@ -425,16 +446,19 @@ fi # # GIT DIFF # -if [ $DIFF -ge 1 ]; then +if [ ${#DIFF[@]} -ge 1 ] && [ ${DIFF[0]} -ge 1 ]; then + + # Capture all additional arguments + ADDITIONAL_ARGS=("${DIFF[@]:1}") # Check if we have a remote branch to diff against and make sure the argument doesn't start with -- or - - if [ ${#POSITIONAL_ARGS[@]} -eq 0 ] || [[ ${POSITIONAL_ARGS[0]} == --* ]] || [[ ${POSITIONAL_ARGS[0]} == -* ]]; then + if [ ${#ADDITIONAL_ARGS[@]} -eq 0 ] || [[ ${ADDITIONAL_ARGS[0]} == --* ]] || [[ ${ADDITIONAL_ARGS[0]} == -* ]]; then # Insert main at position 0 and push other arguments - POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:0}" "main" "${POSITIONAL_ARGS[@]:0}") + ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "main" "${ADDITIONAL_ARGS[@]:0}") fi # Diff the current branch with the specified remote branch - git difftool -d "origin/${POSITIONAL_ARGS[0]}" ${POSITIONAL_ARGS[@]:1} + git difftool -d "origin/${ADDITIONAL_ARGS[0]}" ${ADDITIONAL_ARGS[@]:1} # Check return value retval=$? @@ -454,7 +478,7 @@ fi if [ $OPEN -ge 1 ]; then echo - echo -e "${green}Opening IDE...${normal}" + echo -e "${green}Opening ${SHORT_NAME} in IDE...${normal}" echo if [ $OPEN_WORKSPACE -eq 1 ]; then @@ -480,13 +504,16 @@ fi # # Print various solution information # -if [ $PRINT -ge 1 ]; then +if [ ${#PRINT[@]} -ge 1 ] && [ ${PRINT[0]} -ge 1 ]; then + + # Capture all additional arguments + ADDITIONAL_ARGS=("${PRINT[@]:1}") if [ $PRINT_LARGEFILES -eq 1 ]; then TOP_COUNT=20 - if is_number ${POSITIONAL_ARGS[0]}; then - TOP_COUNT=${POSITIONAL_ARGS[0]} + if is_number ${ADDITIONAL_ARGS[0]}; then + TOP_COUNT=${ADDITIONAL_ARGS[0]} fi while read -r largefile; do @@ -508,16 +535,14 @@ fi # # Run tests # -if [ $TESTS -ge 1 ]; then - - ADDITIONAL_ARGS=() - if [ $TESTS -eq $COMMAND_COUNTER ]; then - ADDITIONAL_ARGS=(${POSITIONAL_ARGS[@]}) +if [ ${#TESTS[@]} -ge 1 ] && [ ${TESTS[0]} -ge 1 ]; then - # If VERBOSE is defined, add it to POSITIONAL_ARGS - if [ $VERBOSE -eq 1 ]; then - ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}") - fi + # Capture all additional arguments + ADDITIONAL_ARGS=("${TESTS[@]:1}") + + # If VERBOSE is defined, add it to the arguments + if [ $VERBOSE -eq 1 ]; then + ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}") fi echo @@ -554,26 +579,28 @@ if [ $TESTS -ge 1 ]; then fi # Check if we have an explicit command to run/start the application -if [ $START -ge 1 ]; then +if [ ${#START[@]} -ge 1 ] && [ ${START[0]} -ge 1 ]; then RUN=1 fi # Finally run the application if requested. if [ $RUN -eq 1 ]; then + ADDITIONAL_ARGS=("${START[@]:1}") + # If VERBOSE is defined, add it to POSITIONAL_ARGS if [ $VERBOSE -eq 1 ]; then - POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:0}" "--verbose" "${POSITIONAL_ARGS[@]:0}") + ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}") fi echo - echo -e "${green}${italic}Running ${APP_NAME} with arguments: ${POSITIONAL_ARGS[@]}${normal}" + echo -e "${green}${italic}Running ${APP_NAME} with arguments: ${ADDITIONAL_ARGS[@]}${normal}" echo if [ ${machine} == "MinGw" ]; then - ./build/${SHORT_NAME}.exe ${POSITIONAL_ARGS[@]} + ./build/${SHORT_NAME}.exe ${ADDITIONAL_ARGS[@]} elif [ ${machine} == "Mac" ]; then - ./build/${APP_NAME}.app/Contents/MacOS/${APP_NAME} ${POSITIONAL_ARGS[@]} + ./build/${APP_NAME}.app/Contents/MacOS/${APP_NAME} ${ADDITIONAL_ARGS[@]} fi retval=$? diff --git a/scripts/common.sh b/scripts/common.sh index 3e7c54f..4ce3401 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -93,8 +93,33 @@ function convert_path_to_file_link() { # function build_setting() { local name=$1 - local value=$(awk -F "=" "/$name/ {print \$2}" "$CONFIG_DIR/build.settings") - echo $value + + # Check if the file exists + if [ ! -f "$CONFIG_DIR/build.settings" ]; then + echo "The file $CONFIG_DIR/build.settings does not exist" + exit 1 + fi + + # Read the file line by line + while IFS= read -r line + do + # Check if the line starts with the name + if [[ $line == ${name}* ]]; then + # Split the line into an array + IFS='=' read -ra array <<< "$line" + # Capture the value + local result=${array[1]} + # Remove trailing line return \r and \n + result="${result%$'\r'}" + result="${result%$'\n'}" + echo $result + return + fi + done < "$CONFIG_DIR/build.settings" + + # If we get here, the name was not found + echo "The name $name was not found in $CONFIG_DIR/build.settings" + exit 1 } # diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt index c18d5cd..6fc7516 100644 --- a/sources/CMakeLists.txt +++ b/sources/CMakeLists.txt @@ -152,14 +152,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") if (MSVC) - # Generate PDB files for debug and release builds - set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") - # Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib") -elseif(XCODE) +elseif(APPLE) # Link with core libraries set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices") @@ -201,19 +197,14 @@ if (MSVC) ${EXTERNAL_SOURCES} ) + # Have the application export foundation_lib and IMGUI symbols. + set_property(TARGET ${AppId} PROPERTY ENABLE_EXPORTS ON) + # Copy the config/*.sjson files to the build directory #file(COPY ${CONFIG_FILES} DESTINATION ${ROOT_DIR}/build) - set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.) - - # Make sure we have the pdb in the same folder. set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.) - set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.) + set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.) if (BUILD_SERVICE_EXE) # Create service.exe target to provide a way to run the app from the command line @@ -229,9 +220,6 @@ if (MSVC) set_target_properties(service-exe PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE") set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.) - set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.) - set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.) - set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.) target_link_libraries(service-exe service-framework) @@ -295,4 +283,4 @@ elseif(APPLE) endif() # Add dependencies to other libs -target_link_libraries(${AppId} framework) +target_link_libraries(${AppId} PRIVATE framework) diff --git a/sources/events.h b/sources/events.h deleted file mode 100644 index 346303a..0000000 --- a/sources/events.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023 - All rights reserved. - * License: https://equals-forty-two.com/LICENSE - * - * Declare application message events - */ - -#pragma once - -/*! Posted when the application is fully initialized. */ -constexpr const char EVENT_APPLICATION_INITIALIZED[] = "APPLICATION_INITIALIZED"; - diff --git a/sources/example.cpp b/sources/example.cpp index 53e1794..abaa928 100644 --- a/sources/example.cpp +++ b/sources/example.cpp @@ -13,70 +13,6 @@ #include -/*! Render leading menus. - * - * @param window The GLFW window. - */ -FOUNDATION_STATIC void app_main_menu_begin(GLFWwindow* window) -{ - if (!ImGui::BeginMenuBar()) - return; - - if (ImGui::TrBeginMenu("File")) - { - if (ImGui::TrBeginMenu("Create")) - { - ImGui::EndMenu(); - } - - if (ImGui::TrBeginMenu("Open")) - { - ImGui::EndMenu(); - } - - ImGui::Separator(); - if (ImGui::TrMenuItem(ICON_MD_EXIT_TO_APP " Exit", "Alt+F4")) - glfw_request_close_window(window); - - ImGui::EndMenu(); - } - - ImGui::EndMenuBar(); - - // Let the framework inject some menus. - app_menu_begin(window); -} - -/*! Render trailing menus. - * - * In between leading and trailing menus the framework usually - * injects additional menus through registered modules. - * - * @param window The GLFW window. - */ -FOUNDATION_STATIC void app_main_menu_end(GLFWwindow* window) -{ - // Let registered module inject some menus. - module_foreach_menu(); - - if (ImGui::BeginMenuBar()) - { - if (ImGui::TrBeginMenu("Windows")) - ImGui::EndMenu(); - - app_menu_help(window); - - // Update special application menu status. - // Usually controls are displayed at the far right of the menu. - profiler_menu_timer(); - module_foreach_menu_status(); - - ImGui::EndMenuBar(); - } - - app_menu_end(window); -} - /*! Main entry point used to report the application title. * * @return The application title. @@ -86,23 +22,6 @@ extern const char* app_title() return PRODUCT_NAME; } -/*! Main entry point to setup the application exception handler. - * - * You can use this function to setup a custom exception handler to log and report crashes. - * - * @param dump_file The name of the dump file. - * @param length The length of the dump file name. - * - * @note The dump file is a binary file containing the application state at the time of the crash. - */ -extern void app_exception_handler(const char* dump_file, size_t length) -{ - FOUNDATION_UNUSED(dump_file); - FOUNDATION_UNUSED(length); - log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception")); - process_exit(-1); -} - /*! Main entry point to configure the application. * * This function is called once when the application is starting up. @@ -187,7 +106,7 @@ extern void app_shutdown() */ extern void app_update(GLFWwindow* window) { - module_update(); + app_update_default(window); } /*! Main entry point to render the application state. @@ -204,60 +123,10 @@ extern void app_update(GLFWwindow* window) */ extern void app_render(GLFWwindow* window, int frame_width, int frame_height) { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2((float)frame_width, (float)frame_height)); - - if (ImGui::Begin(app_title(), nullptr, - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_MenuBar)) + constexpr auto default_tab_render = []() { - // Render main menus - app_main_menu_begin(window); - - // Render document tabs - static ImGuiTabBarFlags tabs_init_flags = ImGuiTabBarFlags_Reorderable; - if (tabs_begin("Tabs", SETTINGS.current_tab, tabs_init_flags, nullptr)) - { - // Render the settings tab - tab_set_color(TAB_COLOR_APP_3D); - tab_draw(tr(ICON_MD_HEXAGON " Example "), nullptr, 0, []() - { - ImGui::TrTextUnformatted("Hello World!"); - }); + ImGui::TrTextUnformatted("Hello World!"); + }; - // Render module registered tabs - module_foreach_tabs(); - - // Render the settings tab - tab_set_color(TAB_COLOR_SETTINGS); - tab_draw(tr(ICON_MD_SETTINGS " Settings ##Settings"), nullptr, - ImGuiTabItemFlags_NoPushId | ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoReorder, settings_draw); - - // We differ setting ImGuiTabBarFlags_AutoSelectNewTabs until after the first frame, - // since we manually select the first tab in the list using the user session data. - if ((tabs_init_flags & ImGuiTabBarFlags_AutoSelectNewTabs) == 0) - tabs_init_flags |= ImGuiTabBarFlags_AutoSelectNewTabs; - - tabs_end(); - } - - // Render trailing menus - app_main_menu_end(window); - - // Render main dialog and floating windows - module_foreach_window(); - - } ImGui::End(); -} - -/*! Main entry point to additional 3rdparty library information - * displayed in the default about dialog. - */ -extern void app_render_3rdparty_libs() -{ - // HERE: Invoke here any function that wants to render 3rd party libs information + app_render_default(window, frame_width, frame_height, SETTINGS.current_tab, default_tab_render, settings_draw); } diff --git a/tools/installer/CMakeLists.txt b/tools/installer/CMakeLists.txt index 57468be..3643722 100644 --- a/tools/installer/CMakeLists.txt +++ b/tools/installer/CMakeLists.txt @@ -65,7 +65,7 @@ if (MSVC) # Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib") -elseif(XCODE) +elseif(APPLE) # Link with core libraries set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices")