Plan recording worker wake decisions

This commit is contained in:
2026-06-05 07:07:28 +02:00
parent c50ea14a2a
commit 942c053c19
8 changed files with 94 additions and 13 deletions

View File

@@ -817,7 +817,8 @@ Known local toolchain state:
animation MP4/timelapse paths, and empty video-path rejection.
- `pp_app_core_document_recording_tests` covers recording start/stop, clear,
platform recorded-file cleanup, frame-count reset, export progress totals,
and oversized progress-total clamping.
oversized progress-total clamping, and recording-worker encode-wake
eligibility when recording, encoder, and canvas-document state vary.
- `pp_app_core_document_sharing_tests` covers saved-path gating before platform
share execution.
- `pp_app_core_document_platform_io_tests` covers empty selected-path filtering

File diff suppressed because one or more lines are too long

View File

@@ -1750,11 +1750,14 @@ Results:
tests.
- `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.
totals, oversized progress-total clamping, and recording-worker encode-wake
eligibility.
- `pano_cli_plan_recording_session_stopped_smoke`,
`pano_cli_plan_recording_session_running_smoke`, and
`pano_cli_plan_recording_session_platform_cleanup_smoke` passed and expose
app-core recording lifecycle/export decisions as JSON.
app-core recording lifecycle/export decisions as JSON. On 2026-06-05,
`pano_cli_plan_recording_session_missing_encoder_smoke` was added for the
worker no-encode path.
- `pp_app_core_document_resize_tests` passed, covering resize dialog state,
unknown current-resolution labeling, selected-resolution mapping, square
canvas sizing, history-clearing intent, invalid selection rejection, service

View File

@@ -783,21 +783,30 @@ void App::rec_loop()
{
std::unique_lock<std::mutex> lock(rec_mutex);
rec_cv.wait(lock/*, [this] { return !(rec_frames.empty() && rec_running); }*/);
if (!rec_running)
auto* legacy_canvas = Canvas::I;
auto* canvas_document = canvas ? canvas->m_canvas.get() : nullptr;
auto* encoder = legacy_canvas ? legacy_canvas->m_encoder.get() : nullptr;
const auto plan = pp::app::plan_recording_worker_iteration(
rec_running,
encoder != nullptr,
legacy_canvas != nullptr && canvas_document != nullptr);
if (!plan.continue_running)
break;
if (Canvas::I->m_encoder)
if (plan.encode_frame && legacy_canvas && canvas_document && encoder)
{
canvas->m_canvas->m_dirty_stroke = false;
PBO equirect = Canvas::I->m_layers_merge.gen_equirect_pbo(
Canvas::I->m_encoder->frame_size());
if (plan.clear_dirty_stroke)
canvas_document->m_dirty_stroke = false;
PBO equirect = legacy_canvas->m_layers_merge.gen_equirect_pbo(
encoder->frame_size());
std::this_thread::yield();
ImageRef img;
img.create(equirect.width, equirect.height, equirect.map());
Canvas::I->m_encoder->encode(img);
encoder->encode(img);
equirect.unmap();
LOG("rec frame encoded");
update_rec_frames();
if (plan.update_frame_label)
update_rec_frames();
}
}
}

View File

@@ -29,6 +29,13 @@ struct RecordingExportPlan {
int progress_total = 0;
};
struct RecordingWorkerIterationPlan {
bool continue_running = true;
bool encode_frame = false;
bool clear_dirty_stroke = false;
bool update_frame_label = false;
};
class RecordingServices {
public:
virtual ~RecordingServices() = default;
@@ -77,6 +84,20 @@ public:
};
}
[[nodiscard]] constexpr RecordingWorkerIterationPlan plan_recording_worker_iteration(
bool is_running_after_wake,
bool has_encoder,
bool has_canvas_document) noexcept
{
const bool encode = is_running_after_wake && has_encoder && has_canvas_document;
return {
.continue_running = is_running_after_wake,
.encode_frame = encode,
.clear_dirty_stroke = encode,
.update_frame_label = encode,
};
}
[[nodiscard]] inline pp::foundation::Status execute_recording_start_action(
RecordingStartAction action,
RecordingServices& services)

View File

@@ -934,7 +934,7 @@ if(TARGET pano_cli)
COMMAND pano_cli plan-recording-session --running --frame-count 12)
set_tests_properties(pano_cli_plan_recording_session_running_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-recording-session\".*\"running\":true.*\"startDecision\":\"no-op-already-running\".*\"stopDecision\":\"stop-thread\".*\"stopRunningRecording\":true.*\"progressTotal\":12")
PASS_REGULAR_EXPRESSION "\"command\":\"plan-recording-session\".*\"running\":true.*\"startDecision\":\"no-op-already-running\".*\"stopDecision\":\"stop-thread\".*\"stopRunningRecording\":true.*\"progressTotal\":12.*\"worker\":\\{\"continueRunning\":true,\"encodeFrame\":true,\"clearDirtyStroke\":true,\"updateFrameLabel\":true\\}")
add_test(NAME pano_cli_plan_recording_session_platform_cleanup_smoke
COMMAND pano_cli plan-recording-session --platform-deletes-recorded-files)
@@ -942,6 +942,12 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast;fuzz"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-recording-session\".*\"platformDeletesRecordedFiles\":true.*\"deleteRecordedFiles\":true.*\"frameCountAfterClear\":0")
add_test(NAME pano_cli_plan_recording_session_missing_encoder_smoke
COMMAND pano_cli plan-recording-session --running --no-encoder)
set_tests_properties(pano_cli_plan_recording_session_missing_encoder_smoke PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-recording-session\".*\"running\":true.*\"encoderAvailable\":false.*\"worker\":\\{\"continueRunning\":true,\"encodeFrame\":false,\"clearDirtyStroke\":false,\"updateFrameLabel\":false\\}")
add_test(NAME pano_cli_plan_app_preferences_smoke
COMMAND pano_cli plan-app-preferences
--ui-scale 1.5

View File

@@ -119,6 +119,25 @@ void recording_export_clamps_progress_total(pp::tests::Harness& harness)
PP_EXPECT(harness, plan.progress_total == std::numeric_limits<int>::max());
}
void recording_worker_iteration_encodes_only_when_ready(pp::tests::Harness& harness)
{
const auto encode = pp::app::plan_recording_worker_iteration(true, true, true);
const auto stopped = pp::app::plan_recording_worker_iteration(false, true, true);
const auto missing_encoder = pp::app::plan_recording_worker_iteration(true, false, true);
const auto missing_canvas = pp::app::plan_recording_worker_iteration(true, true, false);
PP_EXPECT(harness, encode.continue_running);
PP_EXPECT(harness, encode.encode_frame);
PP_EXPECT(harness, encode.clear_dirty_stroke);
PP_EXPECT(harness, encode.update_frame_label);
PP_EXPECT(harness, !stopped.continue_running);
PP_EXPECT(harness, !stopped.encode_frame);
PP_EXPECT(harness, missing_encoder.continue_running);
PP_EXPECT(harness, !missing_encoder.encode_frame);
PP_EXPECT(harness, missing_canvas.continue_running);
PP_EXPECT(harness, !missing_canvas.encode_frame);
}
void executor_dispatches_recording_lifecycle(pp::tests::Harness& harness)
{
FakeRecordingServices services;
@@ -180,6 +199,7 @@ int main()
recording_clear_resets_frames_and_preserves_platform_delete_flag);
harness.run("recording export tracks frame count", recording_export_tracks_frame_count);
harness.run("recording export clamps progress total", recording_export_clamps_progress_total);
harness.run("recording worker iteration encodes only when ready", recording_worker_iteration_encodes_only_when_ready);
harness.run("executor dispatches recording lifecycle", executor_dispatches_recording_lifecycle);
harness.run("executor dispatches recording clear and export", executor_dispatches_recording_clear_and_export);
return harness.finish();

View File

@@ -198,6 +198,8 @@ struct PlanRecordingSessionArgs {
bool running = false;
std::uint32_t frame_count = 0;
bool platform_deletes_recorded_files = false;
bool encoder_available = true;
bool has_canvas = true;
};
struct PlanShareFileArgs {
@@ -2090,7 +2092,7 @@ void print_help()
<< " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n"
<< " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n"
<< " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\n"
<< " plan-recording-session [--running] [--frame-count N] [--platform-deletes-recorded-files]\n"
<< " plan-recording-session [--running] [--frame-count N] [--platform-deletes-recorded-files] [--no-encoder] [--no-canvas]\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"
@@ -3539,6 +3541,10 @@ pp::foundation::Status parse_plan_recording_session_args(
args.frame_count = value.value();
} else if (key == "--platform-deletes-recorded-files") {
args.platform_deletes_recorded_files = true;
} else if (key == "--no-encoder") {
args.encoder_available = false;
} else if (key == "--no-canvas") {
args.has_canvas = false;
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
@@ -3562,10 +3568,16 @@ int plan_recording_session(int argc, char** argv)
args.running,
args.platform_deletes_recorded_files);
const auto export_plan = pp::app::plan_recording_export(args.frame_count);
const auto worker = pp::app::plan_recording_worker_iteration(
args.running,
args.encoder_available,
args.has_canvas);
std::cout << "{\"ok\":true,\"command\":\"plan-recording-session\""
<< ",\"state\":{\"running\":" << json_bool(args.running)
<< ",\"frameCount\":" << args.frame_count
<< ",\"platformDeletesRecordedFiles\":" << json_bool(args.platform_deletes_recorded_files)
<< ",\"encoderAvailable\":" << json_bool(args.encoder_available)
<< ",\"hasCanvas\":" << json_bool(args.has_canvas)
<< "},\"startDecision\":\"" << recording_start_action_name(start)
<< "\",\"stopDecision\":\"" << recording_stop_action_name(stop)
<< "\",\"clear\":{\"stopRunningRecording\":" << json_bool(clear.stop_running_recording)
@@ -3573,6 +3585,10 @@ int plan_recording_session(int argc, char** argv)
<< ",\"frameCountAfterClear\":" << clear.frame_count_after_clear
<< "},\"export\":{\"frameCount\":" << export_plan.frame_count
<< ",\"progressTotal\":" << export_plan.progress_total
<< "},\"worker\":{\"continueRunning\":" << json_bool(worker.continue_running)
<< ",\"encodeFrame\":" << json_bool(worker.encode_frame)
<< ",\"clearDirtyStroke\":" << json_bool(worker.clear_dirty_stroke)
<< ",\"updateFrameLabel\":" << json_bool(worker.update_frame_label)
<< "}}\n";
return 0;
}