Plan recording worker wake decisions
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
23
src/app.cpp
23
src/app.cpp
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user