Centralize legacy app startup
This commit is contained in:
@@ -226,6 +226,7 @@ add_library(pp_app_core STATIC
|
||||
src/app_core/about_menu.h
|
||||
src/app_core/app_preferences.h
|
||||
src/app_core/app_status.h
|
||||
src/app_core/app_startup.h
|
||||
src/app_core/brush_ui.h
|
||||
src/app_core/canvas_hotkey.h
|
||||
src/app_core/canvas_tool_ui.h
|
||||
|
||||
@@ -84,6 +84,8 @@ set(PP_PANOPAINTER_APP_SOURCES
|
||||
src/app_vr.cpp
|
||||
src/legacy_app_preference_services.cpp
|
||||
src/legacy_app_preference_services.h
|
||||
src/legacy_app_startup_services.cpp
|
||||
src/legacy_app_startup_services.h
|
||||
src/legacy_cloud_services.cpp
|
||||
src/legacy_cloud_services.h
|
||||
src/legacy_document_export_services.cpp
|
||||
|
||||
@@ -173,6 +173,12 @@ Known local toolchain state:
|
||||
the `pp_app_core` `AppPreferenceServices` contract while retained settings
|
||||
persistence, VR start/stop, recording lifecycle, and legacy canvas/UI
|
||||
execution remain tracked by `DEBT-0045`.
|
||||
- `src/legacy_app_startup_services.*` is the current app-shell bridge for
|
||||
startup preference/runtime execution. It keeps run-counter persistence,
|
||||
startup preference save, auto-timelapse startup, stored VR-controller state,
|
||||
and license-warning decisions on the `pp_app_core` `AppStartupServices`
|
||||
contract while retained `Settings`, `App::rec_start`, and message-box
|
||||
execution remain tracked by `DEBT-0046`.
|
||||
- `src/legacy_canvas_tool_services.*` is the current app-shell bridge for
|
||||
canvas toolbar tool selection, NodeCanvas stylus/input mode switching, and
|
||||
canvas hotkey/touch execution. It keeps those live paths on the `pp_app_core`
|
||||
@@ -587,6 +593,10 @@ Known local toolchain state:
|
||||
timelapse start/stop/no-op decisions, VR mode success/failure dispatch,
|
||||
simple stored preferences, and `AppPreferenceServices` execution dispatch for
|
||||
options-menu side effects.
|
||||
- `pp_app_core_app_startup_tests` covers startup run-counter increment
|
||||
planning, optional auto-timelapse/license/VR-controller decisions, negative
|
||||
and overflow run-counter rejection, stable full startup dispatch ordering,
|
||||
split persistence/runtime dispatch, and malformed startup-plan rejection.
|
||||
- `pp_platform_api_tests` covers service dispatch for clipboard read/write,
|
||||
empty clipboard writes, cursor visibility, virtual-keyboard visibility,
|
||||
external file display, file sharing, and picker callbacks without platform
|
||||
|
||||
@@ -63,6 +63,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.*`, 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`; `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, 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.*`, but the bridge still calls legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::vr_start`, `App::vr_stop`, `NodeCanvas::set_density`, `NodeCanvas::set_cursor_visibility`, `App::rec_start`, `App::rec_stop`, and `Settings::save` directly | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_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`; `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 |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -183,6 +183,12 @@ contracts. Options-menu preference execution now dispatches through
|
||||
`AppPreferenceServices` and `src/legacy_app_preference_services.*` before
|
||||
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.
|
||||
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
|
||||
@@ -1358,6 +1364,11 @@ Results:
|
||||
- Focused preference CTest coverage passed for
|
||||
`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.
|
||||
- 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`.
|
||||
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
|
||||
clear, platform recorded-file cleanup, frame-count reset, export progress
|
||||
totals, and oversized progress-total clamping.
|
||||
|
||||
35
src/app.cpp
35
src/app.cpp
@@ -6,10 +6,12 @@
|
||||
#include "node_progress_bar.h"
|
||||
#include "mp4enc.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/app_startup.h"
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_recording.h"
|
||||
#include "app_core/document_route.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "legacy_app_startup_services.h"
|
||||
#include "legacy_document_open_services.h"
|
||||
#include "legacy_document_session_services.h"
|
||||
#include "legacy_recording_services.h"
|
||||
@@ -436,12 +438,20 @@ void App::init()
|
||||
LOG("OpenGL startup state failed: %s", startup_state_status.message);
|
||||
});
|
||||
|
||||
int run_counter = Settings::value<Serializer::Integer>("run_counter") + 1;
|
||||
Settings::set("run_counter", Serializer::Integer(run_counter));
|
||||
LOG("run_counter %d", run_counter);
|
||||
|
||||
if (!Settings::save())
|
||||
LOG("save preferences failed");
|
||||
const auto startup_plan = pp::app::plan_app_startup(
|
||||
Settings::value<Serializer::Integer>("run_counter"),
|
||||
Settings::value_or<Serializer::Boolean>("auto-timelapse", true),
|
||||
Settings::value_or<Serializer::Boolean>("vr-controllers-enabled", vr_controllers_enabled),
|
||||
check_license());
|
||||
if (!startup_plan) {
|
||||
LOG("App startup plan failed: %s", startup_plan.status().message);
|
||||
} else {
|
||||
const auto persistence_status = pp::panopainter::execute_legacy_app_startup_persistence_plan(
|
||||
*this,
|
||||
startup_plan.value());
|
||||
if (!persistence_status.ok())
|
||||
LOG("App startup persistence failed: %s", persistence_status.message);
|
||||
}
|
||||
|
||||
initShaders();
|
||||
initAssets();
|
||||
@@ -450,13 +460,12 @@ void App::init()
|
||||
|
||||
uirtt.create(width, height, -1, rgba8_internal_format(), true);
|
||||
|
||||
if (Settings::value_or<Serializer::Boolean>("auto-timelapse", true))
|
||||
rec_start();
|
||||
Settings::value<Serializer::Boolean>("vr-controllers-enabled", vr_controllers_enabled);
|
||||
|
||||
if (!check_license())
|
||||
{
|
||||
message_box("License", "Could not validate this license, running in demo mode.");
|
||||
if (startup_plan) {
|
||||
const auto startup_status = pp::panopainter::execute_legacy_app_startup_runtime_plan(
|
||||
*this,
|
||||
startup_plan.value());
|
||||
if (!startup_status.ok())
|
||||
LOG("App startup runtime execution failed: %s", startup_status.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
112
src/app_core/app_startup.h
Normal file
112
src/app_core/app_startup.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppStartupPlan {
|
||||
int previous_run_counter = 0;
|
||||
int next_run_counter = 1;
|
||||
bool save_preferences = true;
|
||||
bool start_timelapse = false;
|
||||
bool vr_controllers_enabled = true;
|
||||
bool show_license_warning = false;
|
||||
};
|
||||
|
||||
class AppStartupServices {
|
||||
public:
|
||||
virtual ~AppStartupServices() = default;
|
||||
|
||||
virtual void store_run_counter(int value) = 0;
|
||||
virtual void save_preferences() = 0;
|
||||
virtual void start_timelapse_recording() = 0;
|
||||
virtual void apply_vr_controllers_enabled(bool enabled) = 0;
|
||||
virtual void show_license_warning() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppStartupPlan> plan_app_startup(
|
||||
int current_run_counter,
|
||||
bool auto_timelapse_enabled,
|
||||
bool stored_vr_controllers_enabled,
|
||||
bool license_valid)
|
||||
{
|
||||
if (current_run_counter < 0) {
|
||||
return pp::foundation::Result<AppStartupPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("run counter must not be negative"));
|
||||
}
|
||||
|
||||
if (current_run_counter == std::numeric_limits<int>::max()) {
|
||||
return pp::foundation::Result<AppStartupPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("run counter would overflow"));
|
||||
}
|
||||
|
||||
AppStartupPlan plan;
|
||||
plan.previous_run_counter = current_run_counter;
|
||||
plan.next_run_counter = current_run_counter + 1;
|
||||
plan.start_timelapse = auto_timelapse_enabled;
|
||||
plan.vr_controllers_enabled = stored_vr_controllers_enabled;
|
||||
plan.show_license_warning = !license_valid;
|
||||
return pp::foundation::Result<AppStartupPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
services.store_run_counter(plan.next_run_counter);
|
||||
if (plan.save_preferences) {
|
||||
services.save_preferences();
|
||||
}
|
||||
if (plan.start_timelapse) {
|
||||
services.start_timelapse_recording();
|
||||
}
|
||||
services.apply_vr_controllers_enabled(plan.vr_controllers_enabled);
|
||||
if (plan.show_license_warning) {
|
||||
services.show_license_warning();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_persistence_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
services.store_run_counter(plan.next_run_counter);
|
||||
if (plan.save_preferences) {
|
||||
services.save_preferences();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_runtime_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
if (plan.start_timelapse) {
|
||||
services.start_timelapse_recording();
|
||||
}
|
||||
services.apply_vr_controllers_enabled(plan.vr_controllers_enabled);
|
||||
if (plan.show_license_warning) {
|
||||
services.show_license_warning();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
76
src/legacy_app_startup_services.cpp
Normal file
76
src/legacy_app_startup_services.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "legacy_app_startup_services.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "serializer.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace pp::panopainter {
|
||||
namespace {
|
||||
|
||||
class LegacyAppStartupServices final : public pp::app::AppStartupServices {
|
||||
public:
|
||||
explicit LegacyAppStartupServices(App& app) noexcept
|
||||
: app_(app)
|
||||
{
|
||||
}
|
||||
|
||||
void store_run_counter(int value) override
|
||||
{
|
||||
Settings::set("run_counter", Serializer::Integer(value));
|
||||
LOG("run_counter %d", value);
|
||||
}
|
||||
|
||||
void save_preferences() override
|
||||
{
|
||||
if (!Settings::save())
|
||||
LOG("save preferences failed");
|
||||
}
|
||||
|
||||
void start_timelapse_recording() override
|
||||
{
|
||||
app_.rec_start();
|
||||
}
|
||||
|
||||
void apply_vr_controllers_enabled(bool enabled) override
|
||||
{
|
||||
app_.vr_controllers_enabled = enabled;
|
||||
}
|
||||
|
||||
void show_license_warning() override
|
||||
{
|
||||
app_.message_box("License", "Could not validate this license, running in demo mode.");
|
||||
}
|
||||
|
||||
private:
|
||||
App& app_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
pp::foundation::Status execute_legacy_app_startup_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan)
|
||||
{
|
||||
LegacyAppStartupServices services(app);
|
||||
return pp::app::execute_app_startup_plan(plan, services);
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_legacy_app_startup_persistence_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan)
|
||||
{
|
||||
LegacyAppStartupServices services(app);
|
||||
return pp::app::execute_app_startup_persistence_plan(plan, services);
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_legacy_app_startup_runtime_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan)
|
||||
{
|
||||
LegacyAppStartupServices services(app);
|
||||
return pp::app::execute_app_startup_runtime_plan(plan, services);
|
||||
}
|
||||
|
||||
} // namespace pp::panopainter
|
||||
19
src/legacy_app_startup_services.h
Normal file
19
src/legacy_app_startup_services.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_startup.h"
|
||||
|
||||
class App;
|
||||
|
||||
namespace pp::panopainter {
|
||||
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_app_startup_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan);
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_app_startup_persistence_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan);
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_app_startup_runtime_plan(
|
||||
App& app,
|
||||
const pp::app::AppStartupPlan& plan);
|
||||
|
||||
} // namespace pp::panopainter
|
||||
@@ -488,6 +488,16 @@ add_test(NAME pp_app_core_app_status_tests COMMAND pp_app_core_app_status_tests)
|
||||
set_tests_properties(pp_app_core_app_status_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_app_startup_tests
|
||||
app_core/app_startup_tests.cpp)
|
||||
target_link_libraries(pp_app_core_app_startup_tests PRIVATE
|
||||
pp_app_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_app_core_app_startup_tests COMMAND pp_app_core_app_startup_tests)
|
||||
set_tests_properties(pp_app_core_app_startup_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_document_sharing_tests
|
||||
app_core/document_sharing_tests.cpp)
|
||||
target_link_libraries(pp_app_core_document_sharing_tests PRIVATE
|
||||
@@ -851,6 +861,22 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-preferences\".*\"scaleSelection\":\\{\"hasSelection\":false,\"index\":0\\}.*\"direction\":\"left-to-right\".*\"timelapse\":\\{\"enabled\":false,\"recordingAction\":\"stop-recording\"\\}.*\"vrControllers\":\\{\"enabled\":false\\}")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_startup_smoke
|
||||
COMMAND pano_cli plan-app-startup
|
||||
--run-counter 7
|
||||
--vr-controllers-disabled
|
||||
--license-invalid)
|
||||
set_tests_properties(pano_cli_plan_app_startup_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-startup\".*\"runCounter\":7.*\"licenseValid\":false.*\"previousRunCounter\":7.*\"nextRunCounter\":8.*\"savePreferences\":true.*\"startTimelapse\":true.*\"vrControllersEnabled\":false.*\"showLicenseWarning\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_startup_rejects_negative_counter
|
||||
COMMAND pano_cli plan-app-startup --run-counter -1)
|
||||
set_tests_properties(pano_cli_plan_app_startup_rejects_negative_counter PROPERTIES
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-startup\".*\"message\":\"run counter must not be negative\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_tools_menu_shortcuts_smoke
|
||||
COMMAND pano_cli plan-tools-menu --command shortcuts)
|
||||
set_tests_properties(pano_cli_plan_tools_menu_shortcuts_smoke PROPERTIES
|
||||
|
||||
164
tests/app_core/app_startup_tests.cpp
Normal file
164
tests/app_core/app_startup_tests.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "app_core/app_startup.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeAppStartupServices final : public pp::app::AppStartupServices {
|
||||
public:
|
||||
void store_run_counter(int value) override
|
||||
{
|
||||
stored_run_counter = value;
|
||||
call_order += "store-counter;";
|
||||
}
|
||||
|
||||
void save_preferences() override
|
||||
{
|
||||
save_calls += 1;
|
||||
call_order += "save;";
|
||||
}
|
||||
|
||||
void start_timelapse_recording() override
|
||||
{
|
||||
timelapse_starts += 1;
|
||||
call_order += "timelapse;";
|
||||
}
|
||||
|
||||
void apply_vr_controllers_enabled(bool enabled) override
|
||||
{
|
||||
vr_controllers_enabled = enabled;
|
||||
call_order += "vr-controllers;";
|
||||
}
|
||||
|
||||
void show_license_warning() override
|
||||
{
|
||||
license_warnings += 1;
|
||||
call_order += "license;";
|
||||
}
|
||||
|
||||
int stored_run_counter = 0;
|
||||
int save_calls = 0;
|
||||
int timelapse_starts = 0;
|
||||
bool vr_controllers_enabled = false;
|
||||
int license_warnings = 0;
|
||||
std::string call_order;
|
||||
};
|
||||
|
||||
void startup_plan_increments_counter_and_enables_requested_side_effects(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_startup(7, true, false, false);
|
||||
|
||||
PP_EXPECT(harness, plan);
|
||||
PP_EXPECT(harness, plan.value().previous_run_counter == 7);
|
||||
PP_EXPECT(harness, plan.value().next_run_counter == 8);
|
||||
PP_EXPECT(harness, plan.value().save_preferences);
|
||||
PP_EXPECT(harness, plan.value().start_timelapse);
|
||||
PP_EXPECT(harness, !plan.value().vr_controllers_enabled);
|
||||
PP_EXPECT(harness, plan.value().show_license_warning);
|
||||
}
|
||||
|
||||
void startup_plan_preserves_disabled_optional_work(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_startup(0, false, true, true);
|
||||
|
||||
PP_EXPECT(harness, plan);
|
||||
PP_EXPECT(harness, plan.value().next_run_counter == 1);
|
||||
PP_EXPECT(harness, !plan.value().start_timelapse);
|
||||
PP_EXPECT(harness, plan.value().vr_controllers_enabled);
|
||||
PP_EXPECT(harness, !plan.value().show_license_warning);
|
||||
}
|
||||
|
||||
void startup_plan_rejects_invalid_counters(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto negative = pp::app::plan_app_startup(-1, true, true, true);
|
||||
const auto overflow = pp::app::plan_app_startup(std::numeric_limits<int>::max(), true, true, true);
|
||||
|
||||
PP_EXPECT(harness, !negative);
|
||||
PP_EXPECT(harness, negative.status().code == pp::foundation::StatusCode::invalid_argument);
|
||||
PP_EXPECT(harness, !overflow);
|
||||
PP_EXPECT(harness, overflow.status().code == pp::foundation::StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
void startup_executor_dispatches_in_stable_order(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeAppStartupServices services;
|
||||
const auto plan = pp::app::plan_app_startup(2, true, true, false);
|
||||
|
||||
PP_EXPECT(harness, plan);
|
||||
PP_EXPECT(harness, pp::app::execute_app_startup_plan(plan.value(), services).ok());
|
||||
PP_EXPECT(harness, services.stored_run_counter == 3);
|
||||
PP_EXPECT(harness, services.save_calls == 1);
|
||||
PP_EXPECT(harness, services.timelapse_starts == 1);
|
||||
PP_EXPECT(harness, services.vr_controllers_enabled);
|
||||
PP_EXPECT(harness, services.license_warnings == 1);
|
||||
PP_EXPECT(harness, services.call_order == "store-counter;save;timelapse;vr-controllers;license;");
|
||||
}
|
||||
|
||||
void startup_executor_preserves_no_op_side_effects(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeAppStartupServices services;
|
||||
auto plan = pp::app::plan_app_startup(4, false, false, true).value();
|
||||
|
||||
PP_EXPECT(harness, pp::app::execute_app_startup_plan(plan, services).ok());
|
||||
PP_EXPECT(harness, services.stored_run_counter == 5);
|
||||
PP_EXPECT(harness, services.save_calls == 1);
|
||||
PP_EXPECT(harness, services.timelapse_starts == 0);
|
||||
PP_EXPECT(harness, !services.vr_controllers_enabled);
|
||||
PP_EXPECT(harness, services.license_warnings == 0);
|
||||
PP_EXPECT(harness, services.call_order == "store-counter;save;vr-controllers;");
|
||||
}
|
||||
|
||||
void startup_split_executors_keep_persistence_and_runtime_separate(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeAppStartupServices persistence_services;
|
||||
FakeAppStartupServices runtime_services;
|
||||
const auto plan = pp::app::plan_app_startup(5, true, false, false);
|
||||
|
||||
PP_EXPECT(harness, plan);
|
||||
PP_EXPECT(harness, pp::app::execute_app_startup_persistence_plan(plan.value(), persistence_services).ok());
|
||||
PP_EXPECT(harness, persistence_services.stored_run_counter == 6);
|
||||
PP_EXPECT(harness, persistence_services.save_calls == 1);
|
||||
PP_EXPECT(harness, persistence_services.timelapse_starts == 0);
|
||||
PP_EXPECT(harness, persistence_services.license_warnings == 0);
|
||||
PP_EXPECT(harness, persistence_services.call_order == "store-counter;save;");
|
||||
|
||||
PP_EXPECT(harness, pp::app::execute_app_startup_runtime_plan(plan.value(), runtime_services).ok());
|
||||
PP_EXPECT(harness, runtime_services.stored_run_counter == 0);
|
||||
PP_EXPECT(harness, runtime_services.save_calls == 0);
|
||||
PP_EXPECT(harness, runtime_services.timelapse_starts == 1);
|
||||
PP_EXPECT(harness, !runtime_services.vr_controllers_enabled);
|
||||
PP_EXPECT(harness, runtime_services.license_warnings == 1);
|
||||
PP_EXPECT(harness, runtime_services.call_order == "timelapse;vr-controllers;license;");
|
||||
}
|
||||
|
||||
void startup_executor_rejects_malformed_counter_state(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeAppStartupServices services;
|
||||
pp::app::AppStartupPlan plan;
|
||||
plan.previous_run_counter = 3;
|
||||
plan.next_run_counter = 3;
|
||||
|
||||
PP_EXPECT(harness, !pp::app::execute_app_startup_plan(plan, services).ok());
|
||||
PP_EXPECT(harness, services.call_order.empty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run(
|
||||
"startup plan increments counter and enables requested side effects",
|
||||
startup_plan_increments_counter_and_enables_requested_side_effects);
|
||||
harness.run("startup plan preserves disabled optional work", startup_plan_preserves_disabled_optional_work);
|
||||
harness.run("startup plan rejects invalid counters", startup_plan_rejects_invalid_counters);
|
||||
harness.run("startup executor dispatches in stable order", startup_executor_dispatches_in_stable_order);
|
||||
harness.run("startup executor preserves no-op side effects", startup_executor_preserves_no_op_side_effects);
|
||||
harness.run(
|
||||
"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);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "app_core/about_menu.h"
|
||||
#include "app_core/app_preferences.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/app_startup.h"
|
||||
#include "app_core/brush_ui.h"
|
||||
#include "app_core/canvas_hotkey.h"
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
@@ -227,6 +228,13 @@ struct PlanAppPreferencesArgs {
|
||||
int cursor_mode = 0;
|
||||
};
|
||||
|
||||
struct PlanAppStartupArgs {
|
||||
int run_counter = 0;
|
||||
bool auto_timelapse_enabled = true;
|
||||
bool vr_controllers_enabled = true;
|
||||
bool license_valid = true;
|
||||
};
|
||||
|
||||
struct PlanAppStatusArgs {
|
||||
std::string document_name = "no-name";
|
||||
bool unsaved = false;
|
||||
@@ -1852,6 +1860,7 @@ void print_help()
|
||||
<< " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\n"
|
||||
<< " 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-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-tools-menu --command panels|options|clear-grids|reset-camera|shortcuts|sonarpen [--sonarpen-available]\n"
|
||||
<< " plan-tools-panel --panel presets|color|color-advanced|layers|brush|grids|animation [--already-visible]\n"
|
||||
@@ -3416,6 +3425,70 @@ int plan_app_preferences(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_app_startup_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanAppStartupArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--run-counter") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_i32_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
args.run_counter = value.value();
|
||||
} else if (key == "--auto-timelapse-disabled") {
|
||||
args.auto_timelapse_enabled = false;
|
||||
} else if (key == "--vr-controllers-disabled") {
|
||||
args.vr_controllers_enabled = false;
|
||||
} else if (key == "--license-invalid") {
|
||||
args.license_valid = false;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_app_startup(int argc, char** argv)
|
||||
{
|
||||
PlanAppStartupArgs args;
|
||||
const auto status = parse_plan_app_startup_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-app-startup", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_app_startup(
|
||||
args.run_counter,
|
||||
args.auto_timelapse_enabled,
|
||||
args.vr_controllers_enabled,
|
||||
args.license_valid);
|
||||
if (!plan) {
|
||||
print_error("plan-app-startup", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-app-startup\""
|
||||
<< ",\"state\":{\"runCounter\":" << args.run_counter
|
||||
<< ",\"autoTimelapseEnabled\":" << json_bool(args.auto_timelapse_enabled)
|
||||
<< ",\"vrControllersEnabled\":" << json_bool(args.vr_controllers_enabled)
|
||||
<< ",\"licenseValid\":" << json_bool(args.license_valid)
|
||||
<< "},\"plan\":{\"previousRunCounter\":" << plan.value().previous_run_counter
|
||||
<< ",\"nextRunCounter\":" << plan.value().next_run_counter
|
||||
<< ",\"savePreferences\":" << json_bool(plan.value().save_preferences)
|
||||
<< ",\"startTimelapse\":" << json_bool(plan.value().start_timelapse)
|
||||
<< ",\"vrControllersEnabled\":" << json_bool(plan.value().vr_controllers_enabled)
|
||||
<< ",\"showLicenseWarning\":" << json_bool(plan.value().show_license_warning)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_tools_menu_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -8492,6 +8565,10 @@ int main(int argc, char** argv)
|
||||
return plan_app_preferences(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-app-startup") {
|
||||
return plan_app_startup(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-app-status") {
|
||||
return plan_app_status(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user