From 0236fc662014372bcd3dccb560635b5267fb6d3e Mon Sep 17 00:00:00 2001 From: omigamedev Date: Fri, 5 Jun 2026 11:44:28 +0200 Subject: [PATCH] Split asset reload platform policy --- docs/modernization/build-inventory.md | 24 +++--- docs/modernization/debt.md | 9 +- docs/modernization/roadmap.md | 5 +- src/platform_api/asset_file_load_policy.cpp | 82 +++++++++++++++---- src/platform_api/asset_file_load_policy.h | 19 +++++ src/platform_api/platform_policy.cpp | 5 ++ src/platform_api/platform_policy.h | 1 + .../platform_api/platform_services_tests.cpp | 54 ++++++++++++ 8 files changed, 168 insertions(+), 31 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 262ef7c..78cb858 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -743,10 +743,11 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p persistent-storage flushing, document browse roots, working-directory picker availability, prepared-file target planning, work-directory collection export policy, PPBR data-directory override policy, SonarPen availability, live - asset reload policy, native UI/window state saving, recording cleanup policy, - default canvas resolution, and canvas tip visibility now live in the tested - `platform_policy` catalog and are consumed by both `WindowsPlatformServices` - and the retained non-Windows fallback; + asset reload policy, native UI/window state saving, layout XML file mtime + reload policy, recording cleanup policy, default canvas resolution, and canvas + tip visibility now live in the tested `platform_policy` catalog and are + consumed by both `WindowsPlatformServices` and the retained non-Windows + fallback; Windows live app execution now uses injected `WindowsPlatformServices` from @@ -761,10 +762,12 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p header consumers lighter while `DEBT-0055` tracks removal of those handles from `App`. - `pp_platform_api` also owns `plan_asset_file_load`, the SDK-free file-load - policy consumed by `LayoutManager` for XML layout reload decisions. The - helper preserves desktop mtime-based reload behavior and non-desktop - already-loaded skip behavior while keeping direct platform guards out of the - shared layout parser. + policy consumed by `LayoutManager` for XML layout reload decisions. The pure + probed decision and platform-family policy preserve desktop mtime-based + reload behavior and non-desktop already-loaded skip behavior while keeping + direct platform guards out of the shared layout parser; the live wrapper still + owns the retained `stat` probe until platform storage/file-watch services + replace it. - `pp_renderer_gl` owns the tested `OpenGlInitialState` startup depth/blend policy and dispatch application consumed by `App::init`, tested runtime version/vendor/renderer/GLSL string query dispatch consumed by `App::init` @@ -916,8 +919,9 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p - `pp_platform_api_tests` covers service dispatch for clipboard read/write, empty clipboard writes, cursor visibility, virtual-keyboard visibility, external file display, file sharing, VR lifecycle, layout/asset file load - policy, platform-family export/storage/browse/prepared-file/UI-state/canvas - policies, and picker callbacks without platform SDK headers or a window. + policy including pure probed reload behavior, platform-family + export/storage/browse/prepared-file/UI-state/canvas policies, and picker + callbacks without platform SDK headers or a window. - `pp_app_core_document_cloud_tests` covers cloud upload no-canvas, new-document warning, clean publish prompt, and dirty save-before-upload decisions, plus cloud browse no-canvas/show-browser and selected-download diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 3492ed7..7fc5f24 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -235,6 +235,13 @@ agent or engineer to remove them without reconstructing context from chat. policy, and canvas tip visibility. `WindowsPlatformServices` and the non-Windows legacy fallback consume those helpers while SDK-specific execution remains open in platform shells. +- 2026-06-05: DEBT-0054 was narrowed. Layout XML file reload policy now has a + pure `plan_asset_file_load_with_probe` decision in `pp_platform_api`, with + Windows/macOS mtime reload behavior and mobile/Web already-loaded no-op + behavior selected by the tested `platform_policy` catalog. The live + `plan_asset_file_load` wrapper still performs the retained `stat` probe for + mtime platforms until asset/file watching is owned by injected storage + services. - 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit, thumbnail, and object-draw history paths now query saved blend state through tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect @@ -302,7 +309,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0051 | Open | Modernization | Document browser search roots and Browse dialog working-directory picker visibility/path formatting now dispatch through `PlatformServices`; iOS Inbox roots and working-directory picker availability live in tested `pp_platform_api::platform_policy`, but macOS directory picker/display-path execution still lives in `src/platform_legacy/legacy_platform_services.*` | Preserve current iOS document import/browse and desktop browse picker behavior while Apple platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Apple package smoke once root Apple builds exist | Document browse roots and browse-directory picker/display formatting are owned by injected Apple and desktop `pp_platform_*` services with no legacy adapter branch | | DEBT-0052 | Open | Modernization | Native UI/window state saving now dispatches through `PlatformServices`; Windows/macOS save policy lives in tested `pp_platform_api::platform_policy`, but macOS execution still lives in `src/platform_legacy/legacy_platform_services.*` and forwards to the retained Objective-C app bridge | Preserve current Windows/macOS UI persistence while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple package smoke once root Apple builds exist | UI/window state persistence is owned by injected platform services with no legacy adapter branch | | DEBT-0053 | Open | Modernization | Prepared-file writable target selection and prepared-file export-dialog policy now dispatch through `PlatformServices`; iOS temporary-file and WebGL data-path target planning live in tested `pp_platform_api::platform_policy`, but retained iOS/Web save/download handoff execution still lives in `src/platform_legacy/legacy_platform_services.*` | Preserve mobile/Web export handoff behavior while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple/Web package smoke once root package builds exist | Prepared-file target selection, export-dialog policy, and save/download handoff are owned by injected platform services with no legacy adapter branch | -| DEBT-0054 | Open | Modernization | Layout XML file read/reload decisions now consume `pp_platform_api::plan_asset_file_load`, but that helper still encodes the retained compile-time platform policy: Windows/macOS use `stat` mtime reload checks, while other platforms treat already-loaded layouts as successful no-op loads | Preserve current layout hot-reload and mobile/Web single-load behavior while removing platform guards from the shared `LayoutManager` parser | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build | Layout reload decisions are owned by injected platform storage/file-watch services or an asset manager boundary with platform-specific file watching removed from compile-time helpers | +| DEBT-0054 | Open | Modernization | Layout XML file read/reload decisions now consume `pp_platform_api::plan_asset_file_load`; platform-family reload behavior lives in tested `pp_platform_api::platform_policy` and pure probed planning, but the live wrapper still performs direct `stat` probing for Windows/macOS mtime reload checks until platform storage/file-watch services exist | Preserve current layout hot-reload and mobile/Web single-load behavior while removing platform guards from the shared `LayoutManager` parser | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build | Layout reload decisions are owned by injected platform storage/file-watch services or an asset manager boundary with platform-specific file watching removed from compile-time helpers | | DEBT-0055 | Open | Modernization | `src/app.h` now forward-declares retained iOS/macOS/Android/Linux/Web platform handles instead of including platform SDK headers, and full SDK includes are isolated in `src/platform_legacy/legacy_platform_services.cpp`, but the `App` singleton still stores those platform handles directly | Reduce central header platform coupling incrementally without rewriting non-Windows platform entrypoints before Phase 6 | Windows app build; Apple/Android/Linux/Web package smoke once platform root builds are active | Platform handles are owned by injected `pp_platform_*` shell state or services, and `App` has no platform SDK handle fields or platform conditional members | | DEBT-0056 | Open | Modernization | `src/asset.h` now forward-declares Android asset-manager types and uses `Asset::set_android_asset_manager` instead of public mutable manager state, but retained `Asset` still stores Android asset handles and `src/asset.cpp` still performs Android `AAssetManager` reads directly; the current `android-arm64` root preset is headless and does not expose `pp_legacy_assets_io` | Reduce legacy asset I/O header coupling without rewriting Android asset loading before the asset manager/storage boundary exists | Windows app build; `cmake --build --preset android-arm64 --target pp_assets`; Android package smoke once package builds consume shared targets | Android asset loading is owned by injected asset storage/platform services or `pp_assets` file providers, with no static Android asset manager on `Asset` | | DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`; WebGL's retained 512 default now lives in tested `pp_platform_api::platform_policy`, but the Web shell still reaches it through the legacy platform fallback until injected Web services own the policy | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index eed8d1c..628b0f7 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1405,8 +1405,9 @@ exported-image publishing, persistent-storage flushing, document browse roots, working-directory picker availability, prepared-file target planning, work-directory collection export policy, PPBR data-directory override policy, SonarPen availability, native UI/window state saving, live asset reload policy, -recording cleanup policy, default canvas resolution, and canvas tip visibility. -Platform SDK calls remain in the platform shells while those decisions are +layout XML file mtime reload policy, recording cleanup policy, default canvas +resolution, and canvas tip visibility. Platform SDK calls and filesystem probes +remain in the platform shells or thin runtime wrappers while those decisions are headless-testable. Implementation tasks: diff --git a/src/platform_api/asset_file_load_policy.cpp b/src/platform_api/asset_file_load_policy.cpp index befa059..66c4d16 100644 --- a/src/platform_api/asset_file_load_policy.cpp +++ b/src/platform_api/asset_file_load_policy.cpp @@ -5,29 +5,75 @@ namespace pp::platform { +AssetFileLoadDecision plan_asset_file_load_with_probe( + PlatformFamily family, + bool already_loaded, + std::int64_t previous_last_write_time, + AssetFileTimestampProbe probe) noexcept +{ + if (platform_uses_asset_file_mtime_reload(family)) + { + if (!probe.file_exists) + return { false, false, previous_last_write_time }; + + if (probe.last_write_time <= previous_last_write_time) + return { false, false, previous_last_write_time }; + + return { true, true, probe.last_write_time }; + } + + if (already_loaded) + return { false, true, previous_last_write_time }; + + return { true, true, previous_last_write_time }; +} + +AssetFileLoadDecision plan_asset_file_load_for_platform( + PlatformFamily family, + std::string_view absolute_path, + bool already_loaded, + std::int64_t previous_last_write_time) +{ + if (!platform_uses_asset_file_mtime_reload(family)) + { + return plan_asset_file_load_with_probe( + family, + already_loaded, + previous_last_write_time, + {}); + } + + struct stat file_info {}; + const std::string path(absolute_path); + if (stat(path.c_str(), &file_info) != 0) + { + return plan_asset_file_load_with_probe( + family, + already_loaded, + previous_last_write_time, + {}); + } + + return plan_asset_file_load_with_probe( + family, + already_loaded, + previous_last_write_time, + { + true, + static_cast(file_info.st_mtime), + }); +} + AssetFileLoadDecision plan_asset_file_load( std::string_view absolute_path, bool already_loaded, std::int64_t previous_last_write_time) { -#if defined(_WIN32) || defined(__OSX__) - struct stat file_info {}; - const std::string path(absolute_path); - if (stat(path.c_str(), &file_info) != 0) - return { false, false, previous_last_write_time }; - - const auto current_last_write_time = static_cast(file_info.st_mtime); - if (current_last_write_time <= previous_last_write_time) - return { false, false, previous_last_write_time }; - - return { true, true, current_last_write_time }; -#else - if (already_loaded) - return { false, true, previous_last_write_time }; - - (void)absolute_path; - return { true, true, previous_last_write_time }; -#endif + return plan_asset_file_load_for_platform( + current_platform_family(), + absolute_path, + already_loaded, + previous_last_write_time); } } diff --git a/src/platform_api/asset_file_load_policy.h b/src/platform_api/asset_file_load_policy.h index 0ac793e..813c685 100644 --- a/src/platform_api/asset_file_load_policy.h +++ b/src/platform_api/asset_file_load_policy.h @@ -1,5 +1,7 @@ #pragma once +#include "platform_api/platform_policy.h" + #include #include @@ -11,6 +13,23 @@ struct AssetFileLoadDecision { std::int64_t last_write_time = 0; }; +struct AssetFileTimestampProbe { + bool file_exists = false; + std::int64_t last_write_time = 0; +}; + +[[nodiscard]] AssetFileLoadDecision plan_asset_file_load_with_probe( + PlatformFamily family, + bool already_loaded, + std::int64_t previous_last_write_time, + AssetFileTimestampProbe probe) noexcept; + +[[nodiscard]] AssetFileLoadDecision plan_asset_file_load_for_platform( + PlatformFamily family, + std::string_view absolute_path, + bool already_loaded, + std::int64_t previous_last_write_time); + [[nodiscard]] AssetFileLoadDecision plan_asset_file_load( std::string_view absolute_path, bool already_loaded, diff --git a/src/platform_api/platform_policy.cpp b/src/platform_api/platform_policy.cpp index d710777..32a5158 100644 --- a/src/platform_api/platform_policy.cpp +++ b/src/platform_api/platform_policy.cpp @@ -43,6 +43,11 @@ bool platform_enables_live_asset_reloading(PlatformFamily family) noexcept return family == PlatformFamily::windows || family == PlatformFamily::macos; } +bool platform_uses_asset_file_mtime_reload(PlatformFamily family) noexcept +{ + return family == PlatformFamily::windows || family == PlatformFamily::macos; +} + bool platform_saves_native_ui_state(PlatformFamily family) noexcept { return family == PlatformFamily::windows || family == PlatformFamily::macos; diff --git a/src/platform_api/platform_policy.h b/src/platform_api/platform_policy.h index ff98a9e..719ae88 100644 --- a/src/platform_api/platform_policy.h +++ b/src/platform_api/platform_policy.h @@ -23,6 +23,7 @@ enum class PlatformFamily { [[nodiscard]] bool platform_publishes_exported_images(PlatformFamily family) noexcept; [[nodiscard]] bool platform_flushes_persistent_storage(PlatformFamily family) noexcept; [[nodiscard]] bool platform_enables_live_asset_reloading(PlatformFamily family) noexcept; +[[nodiscard]] bool platform_uses_asset_file_mtime_reload(PlatformFamily family) noexcept; [[nodiscard]] bool platform_saves_native_ui_state(PlatformFamily family) noexcept; [[nodiscard]] bool platform_supports_working_directory_picker(PlatformFamily family) noexcept; [[nodiscard]] bool platform_uses_prepared_file_writes(PlatformFamily family) noexcept; diff --git a/tests/platform_api/platform_services_tests.cpp b/tests/platform_api/platform_services_tests.cpp index 227609a..c89c823 100644 --- a/tests/platform_api/platform_services_tests.cpp +++ b/tests/platform_api/platform_services_tests.cpp @@ -799,6 +799,54 @@ void asset_file_load_policy_preserves_platform_reload_behavior(pp::tests::Harnes std::filesystem::remove(temp_path); } +void asset_file_load_policy_preserves_family_reload_behavior(pp::tests::Harness& harness) +{ + const auto missing_desktop = pp::platform::plan_asset_file_load_with_probe( + pp::platform::PlatformFamily::windows, + false, + 7, + {}); + PP_EXPECT(harness, !missing_desktop.should_read_file); + PP_EXPECT(harness, !missing_desktop.skipped_load_result); + PP_EXPECT(harness, missing_desktop.last_write_time == 7); + + const auto stale_desktop = pp::platform::plan_asset_file_load_with_probe( + pp::platform::PlatformFamily::macos, + true, + 7, + { true, 7 }); + PP_EXPECT(harness, !stale_desktop.should_read_file); + PP_EXPECT(harness, !stale_desktop.skipped_load_result); + PP_EXPECT(harness, stale_desktop.last_write_time == 7); + + const auto fresh_desktop = pp::platform::plan_asset_file_load_with_probe( + pp::platform::PlatformFamily::windows, + true, + 7, + { true, 8 }); + PP_EXPECT(harness, fresh_desktop.should_read_file); + PP_EXPECT(harness, fresh_desktop.skipped_load_result); + PP_EXPECT(harness, fresh_desktop.last_write_time == 8); + + const auto first_mobile = pp::platform::plan_asset_file_load_with_probe( + pp::platform::PlatformFamily::ios, + false, + 7, + {}); + PP_EXPECT(harness, first_mobile.should_read_file); + PP_EXPECT(harness, first_mobile.skipped_load_result); + PP_EXPECT(harness, first_mobile.last_write_time == 7); + + const auto repeated_web = pp::platform::plan_asset_file_load_with_probe( + pp::platform::PlatformFamily::webgl, + true, + 7, + { true, 99 }); + PP_EXPECT(harness, !repeated_web.should_read_file); + PP_EXPECT(harness, repeated_web.skipped_load_result); + PP_EXPECT(harness, repeated_web.last_write_time == 7); +} + void platform_services_dispatch_ppbr_export_directory_policy(pp::tests::Harness& harness) { FakePlatformServices fake("unused"); @@ -929,6 +977,9 @@ void platform_policy_preserves_ui_asset_and_input_rules(pp::tests::Harness& harn PP_EXPECT(harness, pp::platform::platform_enables_live_asset_reloading(pp::platform::PlatformFamily::windows)); PP_EXPECT(harness, pp::platform::platform_enables_live_asset_reloading(pp::platform::PlatformFamily::macos)); PP_EXPECT(harness, !pp::platform::platform_enables_live_asset_reloading(pp::platform::PlatformFamily::ios)); + PP_EXPECT(harness, pp::platform::platform_uses_asset_file_mtime_reload(pp::platform::PlatformFamily::windows)); + PP_EXPECT(harness, pp::platform::platform_uses_asset_file_mtime_reload(pp::platform::PlatformFamily::macos)); + PP_EXPECT(harness, !pp::platform::platform_uses_asset_file_mtime_reload(pp::platform::PlatformFamily::android)); PP_EXPECT(harness, pp::platform::platform_saves_native_ui_state(pp::platform::PlatformFamily::windows)); PP_EXPECT(harness, pp::platform::platform_saves_native_ui_state(pp::platform::PlatformFamily::macos)); PP_EXPECT(harness, !pp::platform::platform_saves_native_ui_state(pp::platform::PlatformFamily::webgl)); @@ -990,6 +1041,9 @@ int main() harness.run( "asset file load policy preserves platform reload behavior", asset_file_load_policy_preserves_platform_reload_behavior); + harness.run( + "asset file load policy preserves family reload behavior", + asset_file_load_policy_preserves_family_reload_behavior); harness.run( "platform services dispatch ppbr export directory policy", platform_services_dispatch_ppbr_export_directory_policy);