Route startup resources through app core

This commit is contained in:
2026-06-05 05:55:23 +02:00
parent e42afcc83f
commit 678bf2dcd6
10 changed files with 347 additions and 21 deletions

View File

@@ -234,6 +234,13 @@ Known local toolchain state:
`pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`,
and `pano_cli plan-canvas-view-cursor-mode` before retained canvas mutation
and settings writes.
- `src/legacy_app_startup_services.*` is the current main-app startup bridge for
run-counter persistence, startup runtime side effects, and startup resource
sequencing. `App::init` now consumes `pp_app_core` plans exposed through
`pano_cli plan-app-startup` and `pano_cli plan-app-startup-resources`, while
retained shader loading, asset initialization, layout creation, title updates,
UI render-target creation, recording startup, VR-controller state mutation,
settings writes, and license-warning dialogs remain legacy execution.
- `src/legacy_app_preference_services.*` is the current app-shell bridge for
options-menu preference execution. It keeps UI scale, viewport scale, RTL,
VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on

View File

@@ -85,6 +85,12 @@ agent or engineer to remove them without reconstructing context from chat.
This also narrows DEBT-0045 for viewport-density and cursor-mode preference
execution, though preference persistence remains retained in the legacy
canvas-view bridge.
- 2026-06-05: DEBT-0046 was narrowed. Main startup shader, asset, layout,
title, and UI render-target sequencing now goes through tested `pp_app_core`
resource plans and `src/legacy_app_startup_services.*`; `App::init` keeps
the retained OpenGL startup task in place, then delegates startup resources
and runtime side effects through the startup bridge. `pano_cli
plan-app-startup-resources` exposes the resource path for automation.
- 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
@@ -144,7 +150,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters |
| DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters |
| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and `Settings::save` directly; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters |
| DEBT-0046 | Open | Modernization | Startup preference/runtime execution now consumes pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `AppStartupServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, and message-box license warning execution directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, and startup UI/runtime side effects are owned by injected app/preferences/storage/recording/UI services with `App::init` acting only as orchestration |
| DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration |
| DEBT-0047 | Open | Modernization | PPBR brush package export request validation and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, mobile/Web completion, and success-message behavior directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and success UI are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter |
| DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent |
| DEBT-0049 | Open | Modernization | `pp_assets::validate_ppbr_header` intentionally preserves the legacy PPBR version check from `NodePanelBrushPreset::import_ppbr`, which accepts files when either major is `0` or minor is `1` instead of requiring exactly version `0.1` | Avoid rejecting existing brush packages before compatibility fixtures prove the stricter rule is safe | `pp_assets_brush_package_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add PPBR compatibility fixtures for accepted/rejected historical package versions, then require canonical `0.1` or an explicit supported-version matrix and update live import accordingly |

View File

@@ -184,11 +184,13 @@ contracts. Options-menu preference execution now dispatches through
legacy widgets, settings persistence, recording toggles, and canvas cursor
updates continue.
It also owns tested startup plans for run-counter increments, preference-save
intent, auto-timelapse startup, stored VR-controller state, and license-warning
visibility. `App::init` now plans those decisions before heavy initialization,
executes run-counter persistence through `src/legacy_app_startup_services.*`
before asset/layout setup, and executes runtime startup side effects after the
UI layout and main render target exist.
intent, auto-timelapse startup, stored VR-controller state, license-warning
visibility, and main startup resource sequencing for shader, asset, layout,
title, and UI render-target setup. `App::init` now plans those decisions before
heavy initialization, executes run-counter persistence through
`src/legacy_app_startup_services.*` before resource setup, dispatches the
resource sequence through the same bridge, and executes runtime startup side
effects after the UI layout and main render target exist.
It also owns tested app status/display plans for document title text,
resolution mapping/labels, DPI text, history-memory text, and recording-frame
status text, plus renderer diagnostic indicator labels for framebuffer fetch
@@ -1634,10 +1636,13 @@ Results:
`pp_app_core_app_preferences_tests` and the app-preferences CLI smoke tests
after the live bridge split, including VR mode failed-start status coverage.
- `PanoPainter`, `pp_app_core_app_startup_tests`, and `pano_cli` built after
startup preference/runtime execution moved behind app startup services.
startup preference/runtime execution and startup resource sequencing moved
behind app startup services.
- Focused startup CTest coverage passed for `pp_app_core_app_startup_tests`,
`pano_cli_plan_app_startup_smoke`, and
`pano_cli_plan_app_startup_rejects_negative_counter`.
`pano_cli_plan_app_startup_rejects_negative_counter`, with startup resource
sequencing also covered by `pano_cli_plan_app_startup_resources_smoke` and
`pano_cli_plan_app_startup_resources_rejects_bad_size`.
- `PanoPainter`, `pp_app_core_brush_package_export_tests`, and `pano_cli` built
after PPBR brush package export request validation and dispatch moved behind
app-core brush package services.

View File

@@ -143,11 +143,6 @@ void apply_app_scissor_test(bool enabled)
LOG("OpenGL scissor test failed: %s", status.message);
}
[[nodiscard]] GLint rgba8_internal_format() noexcept
{
return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}
[[nodiscard]] GLenum linear_texture_filter() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::linear_texture_filter());
@@ -450,12 +445,16 @@ void App::init()
LOG("App startup persistence failed: %s", persistence_status.message);
}
initShaders();
initAssets();
initLayout();
title_update();
uirtt.create(width, height, -1, rgba8_internal_format(), true);
const auto startup_resources = pp::app::plan_app_startup_resources(width, height);
if (!startup_resources) {
LOG("App startup resource plan failed: %s", startup_resources.status().message);
} else {
const auto resource_status = pp::panopainter::execute_legacy_app_startup_resources(
*this,
startup_resources.value());
if (!resource_status.ok())
LOG("App startup resources failed: %s", resource_status.message);
}
if (startup_plan) {
const auto startup_status = pp::panopainter::execute_legacy_app_startup_runtime_plan(

View File

@@ -2,6 +2,7 @@
#include "foundation/result.h"
#include <cmath>
#include <limits>
namespace pp::app {
@@ -15,6 +16,16 @@ struct AppStartupPlan {
bool show_license_warning = false;
};
struct AppStartupResourcePlan {
int ui_render_target_width = 0;
int ui_render_target_height = 0;
bool initialize_shaders = true;
bool initialize_assets = true;
bool initialize_layout = true;
bool update_title = true;
bool create_ui_render_target = true;
};
class AppStartupServices {
public:
virtual ~AppStartupServices() = default;
@@ -26,6 +37,17 @@ public:
virtual void show_license_warning() = 0;
};
class AppStartupResourceServices {
public:
virtual ~AppStartupResourceServices() = default;
virtual void initialize_shaders() = 0;
virtual void initialize_assets() = 0;
virtual void initialize_layout() = 0;
virtual void update_title() = 0;
virtual void create_ui_render_target(int width, int height) = 0;
};
[[nodiscard]] inline pp::foundation::Result<AppStartupPlan> plan_app_startup(
int current_run_counter,
bool auto_timelapse_enabled,
@@ -51,6 +73,32 @@ public:
return pp::foundation::Result<AppStartupPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<AppStartupResourcePlan> plan_app_startup_resources(
float ui_width,
float ui_height)
{
if (!std::isfinite(ui_width) || !std::isfinite(ui_height)) {
return pp::foundation::Result<AppStartupResourcePlan>::failure(
pp::foundation::Status::invalid_argument("startup resource dimensions must be finite"));
}
if (ui_width < 1.0F || ui_height < 1.0F) {
return pp::foundation::Result<AppStartupResourcePlan>::failure(
pp::foundation::Status::invalid_argument("startup resource dimensions must be positive"));
}
if (ui_width > static_cast<float>(std::numeric_limits<int>::max())
|| ui_height > static_cast<float>(std::numeric_limits<int>::max())) {
return pp::foundation::Result<AppStartupResourcePlan>::failure(
pp::foundation::Status::out_of_range("startup resource dimensions exceed integer range"));
}
AppStartupResourcePlan plan;
plan.ui_render_target_width = static_cast<int>(ui_width);
plan.ui_render_target_height = static_cast<int>(ui_height);
return pp::foundation::Result<AppStartupResourcePlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Status execute_app_startup_plan(
const AppStartupPlan& plan,
AppStartupServices& services)
@@ -109,4 +157,32 @@ public:
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_app_startup_resources(
const AppStartupResourcePlan& plan,
AppStartupResourceServices& services)
{
if (plan.create_ui_render_target
&& (plan.ui_render_target_width <= 0 || plan.ui_render_target_height <= 0)) {
return pp::foundation::Status::invalid_argument("startup resource plan has invalid UI render target size");
}
if (plan.initialize_shaders) {
services.initialize_shaders();
}
if (plan.initialize_assets) {
services.initialize_assets();
}
if (plan.initialize_layout) {
services.initialize_layout();
}
if (plan.update_title) {
services.update_title();
}
if (plan.create_ui_render_target) {
services.create_ui_render_target(plan.ui_render_target_width, plan.ui_render_target_height);
}
return pp::foundation::Status::success();
}
} // namespace pp::app

View File

@@ -3,13 +3,16 @@
#include "legacy_app_startup_services.h"
#include "app.h"
#include "renderer_gl/opengl_capabilities.h"
#include "serializer.h"
#include "settings.h"
namespace pp::panopainter {
namespace {
class LegacyAppStartupServices final : public pp::app::AppStartupServices {
class LegacyAppStartupServices final
: public pp::app::AppStartupServices
, public pp::app::AppStartupResourceServices {
public:
explicit LegacyAppStartupServices(App& app) noexcept
: app_(app)
@@ -43,6 +46,36 @@ public:
app_.message_box("License", "Could not validate this license, running in demo mode.");
}
void initialize_shaders() override
{
app_.initShaders();
}
void initialize_assets() override
{
app_.initAssets();
}
void initialize_layout() override
{
app_.initLayout();
}
void update_title() override
{
app_.title_update();
}
void create_ui_render_target(int width, int height) override
{
app_.uirtt.create(
width,
height,
-1,
static_cast<GLint>(pp::renderer::gl::rgba8_internal_format()),
true);
}
private:
App& app_;
};
@@ -73,4 +106,12 @@ pp::foundation::Status execute_legacy_app_startup_runtime_plan(
return pp::app::execute_app_startup_runtime_plan(plan, services);
}
pp::foundation::Status execute_legacy_app_startup_resources(
App& app,
const pp::app::AppStartupResourcePlan& plan)
{
LegacyAppStartupServices services(app);
return pp::app::execute_app_startup_resources(plan, services);
}
} // namespace pp::panopainter

View File

@@ -15,5 +15,8 @@ namespace pp::panopainter {
[[nodiscard]] pp::foundation::Status execute_legacy_app_startup_runtime_plan(
App& app,
const pp::app::AppStartupPlan& plan);
[[nodiscard]] pp::foundation::Status execute_legacy_app_startup_resources(
App& app,
const pp::app::AppStartupResourcePlan& plan);
} // namespace pp::panopainter

View File

@@ -934,6 +934,19 @@ if(TARGET pano_cli)
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-startup\".*\"message\":\"run counter must not be negative\"")
add_test(NAME pano_cli_plan_app_startup_resources_smoke
COMMAND pano_cli plan-app-startup-resources --width 1280 --height 720)
set_tests_properties(pano_cli_plan_app_startup_resources_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-startup-resources\".*\"uiRenderTargetWidth\":1280.*\"uiRenderTargetHeight\":720.*\"initializeShaders\":true.*\"initializeAssets\":true.*\"initializeLayout\":true.*\"updateTitle\":true.*\"createUiRenderTarget\":true")
add_test(NAME pano_cli_plan_app_startup_resources_rejects_bad_size
COMMAND pano_cli plan-app-startup-resources --bad-size)
set_tests_properties(pano_cli_plan_app_startup_resources_rejects_bad_size PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-startup-resources\".*\"message\":\"startup resource dimensions")
add_test(NAME pano_cli_plan_brush_package_import_ppbr_smoke
COMMAND pano_cli plan-brush-package-import
--kind ppbr

View File

@@ -1,12 +1,15 @@
#include "app_core/app_startup.h"
#include "test_harness.h"
#include <cmath>
#include <limits>
#include <string>
namespace {
class FakeAppStartupServices final : public pp::app::AppStartupServices {
class FakeAppStartupServices final
: public pp::app::AppStartupServices
, public pp::app::AppStartupResourceServices {
public:
void store_run_counter(int value) override
{
@@ -38,11 +41,50 @@ public:
call_order += "license;";
}
void initialize_shaders() override
{
shader_initializations += 1;
call_order += "shaders;";
}
void initialize_assets() override
{
asset_initializations += 1;
call_order += "assets;";
}
void initialize_layout() override
{
layout_initializations += 1;
call_order += "layout;";
}
void update_title() override
{
title_updates += 1;
call_order += "title;";
}
void create_ui_render_target(int width, int height) override
{
ui_render_target_width = width;
ui_render_target_height = height;
ui_render_target_creations += 1;
call_order += "ui-rtt;";
}
int stored_run_counter = 0;
int save_calls = 0;
int timelapse_starts = 0;
bool vr_controllers_enabled = false;
int license_warnings = 0;
int shader_initializations = 0;
int asset_initializations = 0;
int layout_initializations = 0;
int title_updates = 0;
int ui_render_target_creations = 0;
int ui_render_target_width = 0;
int ui_render_target_height = 0;
std::string call_order;
};
@@ -144,6 +186,57 @@ void startup_executor_rejects_malformed_counter_state(pp::tests::Harness& harnes
PP_EXPECT(harness, services.call_order.empty());
}
void startup_resource_plan_projects_ui_target_size(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_startup_resources(1280.9F, 720.1F);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, plan.value().ui_render_target_width == 1280);
PP_EXPECT(harness, plan.value().ui_render_target_height == 720);
PP_EXPECT(harness, plan.value().initialize_shaders);
PP_EXPECT(harness, plan.value().initialize_assets);
PP_EXPECT(harness, plan.value().initialize_layout);
PP_EXPECT(harness, plan.value().update_title);
PP_EXPECT(harness, plan.value().create_ui_render_target);
}
}
void startup_resource_plan_rejects_invalid_dimensions(pp::tests::Harness& harness)
{
PP_EXPECT(harness, !pp::app::plan_app_startup_resources(0.0F, 720.0F));
PP_EXPECT(harness, !pp::app::plan_app_startup_resources(1280.0F, -1.0F));
PP_EXPECT(harness, !pp::app::plan_app_startup_resources(std::nanf(""), 720.0F));
}
void startup_resource_executor_dispatches_in_stable_order(pp::tests::Harness& harness)
{
FakeAppStartupServices services;
const auto plan = pp::app::plan_app_startup_resources(1024.0F, 512.0F);
PP_EXPECT(harness, plan);
PP_EXPECT(harness, pp::app::execute_app_startup_resources(plan.value(), services).ok());
PP_EXPECT(harness, services.shader_initializations == 1);
PP_EXPECT(harness, services.asset_initializations == 1);
PP_EXPECT(harness, services.layout_initializations == 1);
PP_EXPECT(harness, services.title_updates == 1);
PP_EXPECT(harness, services.ui_render_target_creations == 1);
PP_EXPECT(harness, services.ui_render_target_width == 1024);
PP_EXPECT(harness, services.ui_render_target_height == 512);
PP_EXPECT(harness, services.call_order == "shaders;assets;layout;title;ui-rtt;");
}
void startup_resource_executor_rejects_invalid_render_target_size(pp::tests::Harness& harness)
{
FakeAppStartupServices services;
pp::app::AppStartupResourcePlan plan;
plan.ui_render_target_width = 0;
plan.ui_render_target_height = 720;
PP_EXPECT(harness, !pp::app::execute_app_startup_resources(plan, services).ok());
PP_EXPECT(harness, services.call_order.empty());
}
}
int main()
@@ -160,5 +253,13 @@ int main()
"startup split executors keep persistence and runtime separate",
startup_split_executors_keep_persistence_and_runtime_separate);
harness.run("startup executor rejects malformed counter state", startup_executor_rejects_malformed_counter_state);
harness.run("startup resource plan projects UI target size", startup_resource_plan_projects_ui_target_size);
harness.run("startup resource plan rejects invalid dimensions", startup_resource_plan_rejects_invalid_dimensions);
harness.run(
"startup resource executor dispatches in stable order",
startup_resource_executor_dispatches_in_stable_order);
harness.run(
"startup resource executor rejects invalid render target size",
startup_resource_executor_rejects_invalid_render_target_size);
return harness.finish();
}

View File

@@ -239,6 +239,12 @@ struct PlanAppStartupArgs {
bool license_valid = true;
};
struct PlanAppStartupResourcesArgs {
float width = 1280.0F;
float height = 720.0F;
bool bad_size = false;
};
struct PlanBrushPackageExportArgs {
std::string path;
std::string author;
@@ -2003,6 +2009,7 @@ void print_help()
<< " plan-recording-session [--running] [--frame-count N] [--platform-deletes-recorded-files]\n"
<< " plan-app-preferences [--ui-scale N] [--display-density N] [--current-scale N] [--scale-option N] [--viewport-scale N] [--rtl] [--timelapse-disabled] [--recording-running] [--vr-controllers-disabled] [--cursor-mode N]\n"
<< " plan-app-startup [--run-counter N] [--auto-timelapse-disabled] [--vr-controllers-disabled] [--license-invalid]\n"
<< " plan-app-startup-resources [--width N] [--height N] [--bad-size]\n"
<< " plan-app-status [--doc-name NAME] [--unsaved] [--resolution N] [--resolution-index N] [--zoom N] [--history-bytes N] [--recording-running] [--encoder-available] [--encoded-frames N] [--framebuffer-fetch] [--float32] [--float32-linear] [--float16]\n"
<< " plan-brush-package-import --kind abr|ppbr --path FILE\n"
<< " plan-brush-package-export --path FILE [--author NAME] [--email EMAIL] [--url URL] [--description TEXT] [--dest-path DIR] [--export-data|--no-export-data] [--header-image]\n"
@@ -3644,6 +3651,70 @@ int plan_app_startup(int argc, char** argv)
return 0;
}
pp::foundation::Status parse_plan_app_startup_resources_args(
int argc,
char** argv,
PlanAppStartupResourcesArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--width") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
const auto value = parse_float_arg(argv[++i]);
if (!value) {
return value.status();
}
args.width = value.value();
} else if (key == "--height") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
const auto value = parse_float_arg(argv[++i]);
if (!value) {
return value.status();
}
args.height = value.value();
} else if (key == "--bad-size") {
args.bad_size = true;
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
return pp::foundation::Status::success();
}
int plan_app_startup_resources(int argc, char** argv)
{
PlanAppStartupResourcesArgs args;
const auto status = parse_plan_app_startup_resources_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-app-startup-resources", status.message);
return 2;
}
const auto plan = pp::app::plan_app_startup_resources(
args.bad_size ? 0.0F : args.width,
args.bad_size ? std::nanf("") : args.height);
if (!plan) {
print_error("plan-app-startup-resources", plan.status().message);
return 2;
}
std::cout << "{\"ok\":true,\"command\":\"plan-app-startup-resources\""
<< ",\"plan\":{\"uiRenderTargetWidth\":" << plan.value().ui_render_target_width
<< ",\"uiRenderTargetHeight\":" << plan.value().ui_render_target_height
<< ",\"initializeShaders\":" << json_bool(plan.value().initialize_shaders)
<< ",\"initializeAssets\":" << json_bool(plan.value().initialize_assets)
<< ",\"initializeLayout\":" << json_bool(plan.value().initialize_layout)
<< ",\"updateTitle\":" << json_bool(plan.value().update_title)
<< ",\"createUiRenderTarget\":" << json_bool(plan.value().create_ui_render_target)
<< "}}\n";
return 0;
}
pp::foundation::Status parse_plan_brush_package_import_args(
int argc,
char** argv,
@@ -9887,6 +9958,10 @@ int main(int argc, char** argv)
return plan_app_startup(argc, argv);
}
if (command == "plan-app-startup-resources") {
return plan_app_startup_resources(argc, argv);
}
if (command == "plan-brush-package-import") {
return plan_brush_package_import(argc, argv);
}