diff --git a/CMakeLists.txt b/CMakeLists.txt index 882c43d3..326764c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,8 +242,7 @@ add_library(pp_platform_api STATIC src/platform_api/platform_policy.cpp src/platform_api/platform_policy.h src/platform_api/platform_services.cpp - src/platform_api/platform_services.h - ${PP_PLATFORM_LINUX_SOURCES}) + src/platform_api/platform_services.h) target_include_directories(pp_platform_api PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") @@ -265,6 +264,18 @@ target_link_libraries(pp_platform_apple PRIVATE pp_project_warnings) +add_library(pp_platform_linux STATIC + ${PP_PLATFORM_LINUX_SOURCES}) +target_include_directories(pp_platform_linux + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_link_libraries(pp_platform_linux + PUBLIC + pp_platform_api + pp_project_options + PRIVATE + pp_project_warnings) + add_library(pp_app_core STATIC src/app_core/about_menu.h src/app_core/app_dialog.h @@ -565,6 +576,9 @@ if(PP_BUILD_APP) pp_platform_api pp_renderer_api pp_project_warnings) + target_link_libraries(pp_legacy_app + PUBLIC + pp_platform_linux) if(TARGET pp_renderer_gl) target_link_libraries(pp_legacy_app PRIVATE pp_renderer_gl) endif() diff --git a/PanoPainter-OSX/main.cpp b/PanoPainter-OSX/main.cpp index 5c9b6cae..bd48321f 100644 --- a/PanoPainter-OSX/main.cpp +++ b/PanoPainter-OSX/main.cpp @@ -7,6 +7,7 @@ #include "app.h" #include "keymap.h" #include "main.h" +#include "platform_legacy/legacy_platform_state.h" #include "settings.h" #include #include @@ -533,7 +534,7 @@ NSString* keyCodeToString(NSUInteger keyCode, NSUInteger mods) view = [[View alloc] initWithFrame:r]; controller = [[Controller alloc] initWithWindow:window]; - App::I->osx_view = view; + pp::platform::legacy::set_legacy_apple_state(view, nullptr); float z = (float)window.backingScaleFactor; App::I->zoom = Settings::value_or("ui-scale", (z > 0.f) ? z : 1.f); @@ -625,7 +626,7 @@ int main(int argc, const char * argv[]) return 0; AppOSX* app = [AppOSX sharedApplication]; - App::I->osx_app = app; + pp::platform::legacy::set_legacy_apple_state(nullptr, app); [app run]; return 0; diff --git a/PanoPainter/GameViewController.m b/PanoPainter/GameViewController.m index 503dc221..07baf8bd 100644 --- a/PanoPainter/GameViewController.m +++ b/PanoPainter/GameViewController.m @@ -10,6 +10,7 @@ #import "GameViewController.h" #import #include "app.h" +#include "platform_legacy/legacy_platform_state.h" #include "settings.h" #import "objc_utils.h" #import @@ -569,8 +570,9 @@ bool is_tap = true; [super viewDidLoad]; input_enabled = NO; App::I = new App; - App::I->ios_view = self; - App::I->ios_app = (AppDelegate*)[[UIApplication sharedApplication] delegate]; + pp::platform::legacy::set_legacy_apple_state( + self, + (AppDelegate*)[[UIApplication sharedApplication] delegate]); App::I->initLog(); //self.preferredFramesPerSecond = 60; diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 4ae77c84..41ca4743 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -1,7 +1,7 @@ # Build And Platform Inventory Status: live -Last updated: 2026-06-05 +Last updated: 2026-06-17 This inventory records the known build surfaces during the CMake migration. Keep it updated as platform paths move to shared CMake targets. @@ -17,7 +17,7 @@ Keep it updated as platform paths move to shared CMake targets. | Android standard | `android/android/build.gradle`, `android/android/CMakeLists.txt` | Retained native library target `native-lib`; CMake 3.10/C++23 baseline now links the standard arm64 package path with modern component/service sources and the generated `nanort` overlay helper | | Android Quest | `android/quest/build.gradle`, `android/quest/CMakeLists.txt` | OVR SDK imported libraries; CMake 3.10/C++23 baseline and current Yoga source list configure with the shared Android package compatibility helper | | Android Focus/Wave | `android/focus/build.gradle`, `android/focus/CMakeLists.txt` | Wave SDK imported libraries; CMake 3.10/C++23 baseline and current Yoga source list configure with the shared Android package compatibility helper | -| Linux | `linux/CMakeLists.txt` | Retained app target now uses CMake 3.10 and target-level C++23 while package/root target migration remains open | +| Linux | `linux/CMakeLists.txt` | Retained app target now uses CMake 3.10 and target-level C++23 while package/root target migration remains open; the modern root CMake path now splits Linux helper code into `pp_platform_linux` instead of leaving it inside `pp_platform_api` | | WebGL/Emscripten | `webgl/CMakeLists.txt` | Retained WebGL app target now uses CMake 3.10 and target-level C++23 with retained WebGL2/Emscripten link flags | ## Existing Version Generation diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index e6390e1e..ba77ee27 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -92,6 +92,18 @@ Current hotspot files: Latest slice: +- `pp_platform_api` no longer compiles + `src/platform_linux/linux_platform_services.*`; Linux concrete platform code + now lives in `pp_platform_linux`, which `pp_legacy_app` and + `pp_platform_api_tests` link where needed. +- Win32 main-thread queued task ownership now lives in `AppRuntime` instead of + `src/platform_windows/windows_platform_services.cpp`, which removes another + runtime queue from retained platform-local static state and leaves the + Windows shell as a thin forwarder. +- The `platform_legacy`-mirrored Apple and GLFW handle cluster no longer lives + on `App`; retained Apple/GLFW platform state is now seeded explicitly from + the iOS, macOS, Linux, and WebGL entrypoints through + `src/platform_legacy/legacy_platform_state.*`. - `pp_platform_api` no longer compiles `src/platform_apple/apple_platform_services.*`; Apple concrete platform code now lives in the new `pp_platform_apple` target, and @@ -187,8 +199,8 @@ Latest slice: Current architecture mismatches that must be treated as real blockers: - `pp_platform_api` no longer compiles Apple implementation files, but it - still owns concrete Linux platform sources instead of only platform-neutral - policy and interface code. + still owns too much concrete platform implementation instead of only + platform-neutral policy and interface code. - `src/platform_apple/apple_platform_services.cpp` no longer reaches `App::I` directly, and Linux FPS title reporting now uses an injected callback, but retained Apple bridging in `platform_legacy` and other platform/app coupling @@ -203,7 +215,9 @@ Current architecture mismatches that must be treated as real blockers: the retained GLFW window hooks, Apple handle snapshots, and fallback storage-path return now also using local retained-state helpers instead of direct method-body reads, while Windows VR session snapshot state now also - lives behind platform-owned helpers instead of on `App`. + lives behind platform-owned helpers instead of on `App`, and the + `platform_legacy`-mirrored Apple/GLFW handle cluster is now seeded + explicitly from platform entrypoints instead of being copied out of `App`. - `src/platform_legacy/legacy_platform_services.*` is still part of the live app shell. - `pp_panopainter_ui` still depends on `pp_legacy_app`. diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 97146718..da532356 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -41,8 +41,8 @@ Completed, blocked, and superseded task history moved to `src/node_stroke_preview.cpp`, `src/app.cpp`, `src/app_dialogs.cpp`, and the extracted canvas/platform containment files. - The platform boundary is not finished: - - `pp_platform_api` no longer compiles Apple implementation files, but it - still compiles concrete Linux platform sources + - `pp_platform_api` no longer compiles Apple or Linux implementation files, + but broader concrete platform ownership is still not fully separated - `platform_apple` no longer reaches `App::I` directly, and Linux FPS title reporting now uses an injected callback, but retained Apple bridging and broader platform-to-app singleton reach are still open in @@ -770,6 +770,9 @@ Current slice: - the remaining Windows app shell in `main(...)` now also routes through `run_main_application(...)` in `src/platform_windows/windows_runtime_shell.*`, which reduces `src/main.cpp` to a minimal `main`/`WinMain` wrapper +- the retained Win32 main-thread task queue now also lives in `AppRuntime` + instead of `src/platform_windows/windows_platform_services.cpp`, which + removes another runtime queue from retained platform-local static state - prepared-file background work now runs through an `AppRuntime`-owned worker queue instead of a retained static worker in `src/app_events.cpp` - canvas async import/export/save/open background work now also runs through an @@ -1077,8 +1080,13 @@ Current slice: - Apple concrete platform code now lives in the new `pp_platform_apple` target, and `panopainter_app` plus `pp_platform_api_tests` now link that concrete target where needed. -- The dependency direction is cleaner for Apple, but the same split is still - incomplete for Linux and the broader concrete platform family. +- `pp_platform_api` now also stops compiling + `src/platform_linux/linux_platform_services.*`. +- Linux concrete platform code now lives in the new `pp_platform_linux` + target, and `pp_legacy_app` plus `pp_platform_api_tests` now link that + concrete target where needed. +- The dependency direction is cleaner for Apple and Linux, but the broader + concrete platform family is still being separated. Write scope: - `CMakeLists.txt` @@ -1093,8 +1101,8 @@ Read scope: Done when: - `pp_platform_api` contains only platform-neutral interfaces, policies, and shared helpers. -- Apple implementation files are built by a concrete platform target instead of - the API target. +- Apple and Linux implementation files are built by concrete platform targets + instead of the API target. - The dependency direction is obvious from CMake without reading debt notes. Mini-model packet: @@ -1145,6 +1153,10 @@ Current slice: snapshots without direct `App::I` reads in the touched paths - retained Apple callback injection and broader `platform_legacy` singleton reach are still open +- the `platform_legacy`-mirrored Apple/GLFW handle cluster is now seeded + explicitly from the iOS, macOS, Linux, and WebGL entrypoints through + `src/platform_legacy/legacy_platform_state.*` instead of being copied out + of `App` - The remaining Win32 shell wrappers for close, async lock/swap, stylus/FPS updates, VR start/stop, window-state save, and the window-handle accessor now live in @@ -1188,8 +1200,12 @@ Current slice: `read_platform_vr_session_snapshot()` in `src/platform_windows/windows_platform_services.*`, with app-side reads now routed through `App::vr_session_snapshot()`. -- `App` still owns other platform-facing handles and retained legacy platform - state is not fully removed, so this remains a live ownership task. +- The `platform_legacy`-mirrored Apple/GLFW handle cluster also no longer + lives on `App`; retained Apple/GLFW platform state is now seeded explicitly + from the iOS, macOS, Linux, and WebGL entrypoints through + `src/platform_legacy/legacy_platform_state.*`. +- `App` still owns Android-native handles plus broader retained legacy + platform state, so this remains a live ownership task. Write scope: - `src/platform_legacy/legacy_platform_services.*` diff --git a/linux/src/main.cpp b/linux/src/main.cpp index 643d4796..4bb24c3a 100644 --- a/linux/src/main.cpp +++ b/linux/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +42,9 @@ void linux_update_fps(int frames) { static char title_fps[512]; sprintf(title_fps, "PanoPainter - %d fps", frames); - glfwSetWindowTitle(app.glfw_window, title_fps); + auto* const window = pp::platform::legacy::active_legacy_glfw_window_state().window; + if (window) + glfwSetWindowTitle(window, title_fps); } void error_log(int code, const char * s) @@ -90,9 +93,9 @@ int main(int argc, char** args) }); }); glfwSetWindowCloseCallback(wnd, [](GLFWwindow* wnd){ - app.ui_task([] { + app.ui_task([wnd] { if (!app.request_close()) - glfwSetWindowShouldClose(app.glfw_window, GLFW_FALSE); + glfwSetWindowShouldClose(wnd, GLFW_FALSE); }); }); glfwSetWindowRefreshCallback(wnd, [](GLFWwindow* wnd){ @@ -116,11 +119,11 @@ int main(int argc, char** args) umask(0); App::I = &app; + pp::platform::legacy::set_legacy_glfw_window(wnd); app.initLog(); app.create(); app.width = 800; app.height = 600; - app.glfw_window = wnd; app.render_thread_start(); app.ui_thread_start(); diff --git a/src/app.h b/src/app.h index ef2cd32e..6de58710 100644 --- a/src/app.h +++ b/src/app.h @@ -155,16 +155,6 @@ public: int idle_ms = 100; pp::platform::PlatformServices* platform_services_ = nullptr; -#if defined(__IOS__) && defined(__OBJC__) - GameViewController* ios_view; - AppDelegate* ios_app; -#elif defined(__OSX__) && defined(__OBJC__) - View* osx_view; - AppOSX* osx_app; -#elif __LINUX__ || __WEB__ - GLFWwindow* glfw_window; -#endif - #ifdef __ANDROID__ struct android_app* and_app; struct engine* and_engine; diff --git a/src/app_events.cpp b/src/app_events.cpp index 0390c633..b674b04c 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -15,6 +15,7 @@ #ifdef _WIN32 #include "platform_windows/windows_platform_services.h" #endif +#include "platform_legacy/legacy_platform_state.h" #include "platform_legacy/legacy_platform_services.h" #include "renderer_gl/opengl_capabilities.h" @@ -65,10 +66,11 @@ void App::set_platform_services(pp::platform::PlatformServices* services) noexce if (services) { pp::platform::linux_desktop::set_fps_title_callback([this](std::string title) { - if (!glfw_window) + auto* const window = pp::platform::legacy::active_legacy_glfw_window_state().window; + if (!window) return; - glfwSetWindowTitle(glfw_window, title.c_str()); + glfwSetWindowTitle(window, title.c_str()); }); } else diff --git a/src/app_runtime.cpp b/src/app_runtime.cpp index dec02294..c5043e4d 100644 --- a/src/app_runtime.cpp +++ b/src/app_runtime.cpp @@ -63,6 +63,27 @@ void AppRuntime::canvas_async_task(std::function task) canvas_async_cv_.notify_one(); } +void AppRuntime::main_thread_task(std::packaged_task task) +{ + std::lock_guard lock(main_thread_task_mutex_); + main_thread_tasklist_.push_back(std::move(task)); +} + +void AppRuntime::drain_main_thread_tasks() +{ + std::deque> tasklist; + { + std::lock_guard lock(main_thread_task_mutex_); + tasklist = std::move(main_thread_tasklist_); + } + + while (!tasklist.empty()) + { + tasklist.front()(); + tasklist.pop_front(); + } +} + void AppRuntime::prepared_file_worker_main(std::stop_token stop_token) { for (;;) diff --git a/src/app_runtime.h b/src/app_runtime.h index e6313bb3..2b7ee5a8 100644 --- a/src/app_runtime.h +++ b/src/app_runtime.h @@ -46,6 +46,8 @@ public: void notify_ui_worker() noexcept; void prepared_file_task(std::function task); void canvas_async_task(std::function task); + void main_thread_task(std::packaged_task task); + void drain_main_thread_tasks(); void render_thread_tick(App& app); void render_thread_main(App& app, std::stop_token stop_token); @@ -221,6 +223,9 @@ private: std::jthread canvas_async_worker_; bool canvas_async_running_ = true; + std::deque> main_thread_tasklist_; + std::mutex main_thread_task_mutex_; + std::deque render_tasklist_; std::mutex render_task_mutex_; std::condition_variable render_cv_; diff --git a/src/platform_legacy/legacy_platform_state.cpp b/src/platform_legacy/legacy_platform_state.cpp index 4b4604c6..26f24266 100644 --- a/src/platform_legacy/legacy_platform_state.cpp +++ b/src/platform_legacy/legacy_platform_state.cpp @@ -19,20 +19,7 @@ struct RetainedLegacyStoragePaths final { #if defined(__LINUX__) || defined(__WEB__) [[nodiscard]] RetainedLegacyGlfwWindowState& active_legacy_glfw_window_state() { - static RetainedLegacyGlfwWindowState state = [] { - RetainedLegacyGlfwWindowState retained; - retained.window = App::I->glfw_window; - retained.hooks.acquire_render_context = [window = retained.window] { - glfwMakeContextCurrent(window); - }; - retained.hooks.present_render_context = [window = retained.window] { - glfwSwapBuffers(window); - }; - retained.hooks.request_app_close = [window = retained.window] { - glfwSetWindowShouldClose(window, GLFW_TRUE); - }; - return retained; - }(); + static RetainedLegacyGlfwWindowState state; return state; } @@ -40,24 +27,53 @@ struct RetainedLegacyStoragePaths final { { return active_legacy_glfw_window_state().hooks; } + +void set_legacy_glfw_window(GLFWwindow* window) +{ + auto& retained = active_legacy_glfw_window_state(); + retained.window = window; + retained.hooks.acquire_render_context = [window] { + glfwMakeContextCurrent(window); + }; + retained.hooks.present_render_context = [window] { + glfwSwapBuffers(window); + }; + retained.hooks.request_app_close = [window] { + glfwSetWindowShouldClose(window, GLFW_TRUE); + }; +} #endif #if defined(__IOS__) || defined(__OSX__) [[nodiscard]] RetainedLegacyAppleState& active_legacy_apple_state() { - static RetainedLegacyAppleState state = [] { - RetainedLegacyAppleState retained; -#ifdef __IOS__ - retained.ios_view = App::I->ios_view; - retained.ios_app = App::I->ios_app; -#elif defined(__OSX__) - retained.osx_view = App::I->osx_view; - retained.osx_app = App::I->osx_app; -#endif - return retained; - }(); + static RetainedLegacyAppleState state; return state; } + +void set_legacy_apple_state( +#ifdef __IOS__ + GameViewController* ios_view, + AppDelegate* ios_app +#elif defined(__OSX__) + View* osx_view, + AppOSX* osx_app +#endif +) +{ + auto& retained = active_legacy_apple_state(); +#ifdef __IOS__ + if (ios_view) + retained.ios_view = ios_view; + if (ios_app) + retained.ios_app = ios_app; +#elif defined(__OSX__) + if (osx_view) + retained.osx_view = osx_view; + if (osx_app) + retained.osx_app = osx_app; +#endif +} #endif [[nodiscard]] const pp::platform::PlatformStoragePaths& active_legacy_storage_paths() diff --git a/src/platform_legacy/legacy_platform_state.h b/src/platform_legacy/legacy_platform_state.h index 0b644022..9f5569e3 100644 --- a/src/platform_legacy/legacy_platform_state.h +++ b/src/platform_legacy/legacy_platform_state.h @@ -32,6 +32,7 @@ struct RetainedLegacyGlfwWindowState final { [[nodiscard]] RetainedLegacyGlfwWindowState& active_legacy_glfw_window_state(); [[nodiscard]] RetainedLegacyGlfwWindowHooks& active_legacy_glfw_window_hooks(); +void set_legacy_glfw_window(GLFWwindow* window); #endif #if defined(__IOS__) || defined(__OSX__) @@ -46,6 +47,15 @@ struct RetainedLegacyAppleState final { }; [[nodiscard]] RetainedLegacyAppleState& active_legacy_apple_state(); +void set_legacy_apple_state( +#ifdef __IOS__ + GameViewController* ios_view, + AppDelegate* ios_app +#elif defined(__OSX__) + View* osx_view, + AppOSX* osx_app +#endif +); #endif [[nodiscard]] const pp::platform::PlatformStoragePaths& active_legacy_storage_paths(); diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp index c2bd796b..e87524b0 100644 --- a/src/platform_windows/windows_platform_services.cpp +++ b/src/platform_windows/windows_platform_services.cpp @@ -99,24 +99,6 @@ void win32_save_window_state() namespace pp::platform::windows { namespace { -struct RetainedMainTaskQueue final { - std::deque> tasklist; - std::mutex task_mutex; -}; - -[[nodiscard]] RetainedMainTaskQueue& retained_main_task_queue() -{ - static RetainedMainTaskQueue queue; - return queue; -} - -void enqueue_main_task(std::packaged_task task) -{ - auto& queue = retained_main_task_queue(); - std::lock_guard lock(queue.task_mutex); - queue.tasklist.emplace_back(std::move(task)); -} - struct RetainedWin32AsyncRenderContextState final { HDC hdc{}; HGLRC hrc{}; @@ -189,23 +171,18 @@ void swap_async_render_context() void enqueue_main_thread_task(std::packaged_task task) { - enqueue_main_task(std::move(task)); + if (!App::I) + { + task(); + return; + } + App::I->runtime().main_thread_task(std::move(task)); } void drain_main_thread_tasks() { - std::deque> working_list; - { - auto& queue = retained_main_task_queue(); - std::lock_guard lock(queue.task_mutex); - working_list = std::move(queue.tasklist); - } - - while (!working_list.empty()) - { - working_list.front()(); - working_list.pop_front(); - } + if (App::I) + App::I->runtime().drain_main_thread_tasks(); } } // namespace pp::platform::windows diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d799cdc9..d6f89247 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -355,6 +355,9 @@ target_link_libraries(pp_platform_api_tests PRIVATE if(TARGET pp_platform_apple) target_link_libraries(pp_platform_api_tests PRIVATE pp_platform_apple) endif() +if(TARGET pp_platform_linux) + target_link_libraries(pp_platform_api_tests PRIVATE pp_platform_linux) +endif() add_test(NAME pp_platform_api_tests COMMAND pp_platform_api_tests) set_tests_properties(pp_platform_api_tests PROPERTIES diff --git a/webgl/src/main.cpp b/webgl/src/main.cpp index a300e3a6..fc2aaa4e 100644 --- a/webgl/src/main.cpp +++ b/webgl/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -117,11 +118,11 @@ void CanvasOnWheel(float y) void StartApp() { App::I = &app; + pp::platform::legacy::set_legacy_glfw_window(wnd); app.initLog(); app.create(); app.width = 1024; app.height = 768; - app.glfw_window = wnd; // app.render_thread_tick(); // app.ui_thread_tick(); @@ -240,9 +241,9 @@ int main() }); }); glfwSetWindowCloseCallback(wnd, [](GLFWwindow* wnd){ - app.ui_task([] { + app.ui_task([wnd] { if (!app.request_close()) - glfwSetWindowShouldClose(app.glfw_window, GLFW_FALSE); + glfwSetWindowShouldClose(wnd, GLFW_FALSE); }); }); glfwSetWindowRefreshCallback(wnd, [](GLFWwindow* wnd){