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.
|
animation MP4/timelapse paths, and empty video-path rejection.
|
||||||
- `pp_app_core_document_recording_tests` covers recording start/stop, clear,
|
- `pp_app_core_document_recording_tests` covers recording start/stop, clear,
|
||||||
platform recorded-file cleanup, frame-count reset, export progress totals,
|
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
|
- `pp_app_core_document_sharing_tests` covers saved-path gating before platform
|
||||||
share execution.
|
share execution.
|
||||||
- `pp_app_core_document_platform_io_tests` covers empty selected-path filtering
|
- `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.
|
tests.
|
||||||
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
|
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
|
||||||
clear, platform recorded-file cleanup, frame-count reset, export progress
|
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_stopped_smoke`,
|
||||||
`pano_cli_plan_recording_session_running_smoke`, and
|
`pano_cli_plan_recording_session_running_smoke`, and
|
||||||
`pano_cli_plan_recording_session_platform_cleanup_smoke` passed and expose
|
`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,
|
- `pp_app_core_document_resize_tests` passed, covering resize dialog state,
|
||||||
unknown current-resolution labeling, selected-resolution mapping, square
|
unknown current-resolution labeling, selected-resolution mapping, square
|
||||||
canvas sizing, history-clearing intent, invalid selection rejection, service
|
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);
|
std::unique_lock<std::mutex> lock(rec_mutex);
|
||||||
rec_cv.wait(lock/*, [this] { return !(rec_frames.empty() && rec_running); }*/);
|
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;
|
break;
|
||||||
|
|
||||||
if (Canvas::I->m_encoder)
|
if (plan.encode_frame && legacy_canvas && canvas_document && encoder)
|
||||||
{
|
{
|
||||||
canvas->m_canvas->m_dirty_stroke = false;
|
if (plan.clear_dirty_stroke)
|
||||||
PBO equirect = Canvas::I->m_layers_merge.gen_equirect_pbo(
|
canvas_document->m_dirty_stroke = false;
|
||||||
Canvas::I->m_encoder->frame_size());
|
PBO equirect = legacy_canvas->m_layers_merge.gen_equirect_pbo(
|
||||||
|
encoder->frame_size());
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
ImageRef img;
|
ImageRef img;
|
||||||
img.create(equirect.width, equirect.height, equirect.map());
|
img.create(equirect.width, equirect.height, equirect.map());
|
||||||
Canvas::I->m_encoder->encode(img);
|
encoder->encode(img);
|
||||||
equirect.unmap();
|
equirect.unmap();
|
||||||
LOG("rec frame encoded");
|
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;
|
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 {
|
class RecordingServices {
|
||||||
public:
|
public:
|
||||||
virtual ~RecordingServices() = default;
|
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(
|
[[nodiscard]] inline pp::foundation::Status execute_recording_start_action(
|
||||||
RecordingStartAction action,
|
RecordingStartAction action,
|
||||||
RecordingServices& services)
|
RecordingServices& services)
|
||||||
|
|||||||
@@ -934,7 +934,7 @@ if(TARGET pano_cli)
|
|||||||
COMMAND pano_cli plan-recording-session --running --frame-count 12)
|
COMMAND pano_cli plan-recording-session --running --frame-count 12)
|
||||||
set_tests_properties(pano_cli_plan_recording_session_running_smoke PROPERTIES
|
set_tests_properties(pano_cli_plan_recording_session_running_smoke PROPERTIES
|
||||||
LABELS "app;integration;desktop-fast"
|
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
|
add_test(NAME pano_cli_plan_recording_session_platform_cleanup_smoke
|
||||||
COMMAND pano_cli plan-recording-session --platform-deletes-recorded-files)
|
COMMAND pano_cli plan-recording-session --platform-deletes-recorded-files)
|
||||||
@@ -942,6 +942,12 @@ if(TARGET pano_cli)
|
|||||||
LABELS "app;integration;desktop-fast;fuzz"
|
LABELS "app;integration;desktop-fast;fuzz"
|
||||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-recording-session\".*\"platformDeletesRecordedFiles\":true.*\"deleteRecordedFiles\":true.*\"frameCountAfterClear\":0")
|
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
|
add_test(NAME pano_cli_plan_app_preferences_smoke
|
||||||
COMMAND pano_cli plan-app-preferences
|
COMMAND pano_cli plan-app-preferences
|
||||||
--ui-scale 1.5
|
--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());
|
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)
|
void executor_dispatches_recording_lifecycle(pp::tests::Harness& harness)
|
||||||
{
|
{
|
||||||
FakeRecordingServices services;
|
FakeRecordingServices services;
|
||||||
@@ -180,6 +199,7 @@ int main()
|
|||||||
recording_clear_resets_frames_and_preserves_platform_delete_flag);
|
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 tracks frame count", recording_export_tracks_frame_count);
|
||||||
harness.run("recording export clamps progress total", recording_export_clamps_progress_total);
|
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 lifecycle", executor_dispatches_recording_lifecycle);
|
||||||
harness.run("executor dispatches recording clear and export", executor_dispatches_recording_clear_and_export);
|
harness.run("executor dispatches recording clear and export", executor_dispatches_recording_clear_and_export);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
|
|||||||
@@ -198,6 +198,8 @@ struct PlanRecordingSessionArgs {
|
|||||||
bool running = false;
|
bool running = false;
|
||||||
std::uint32_t frame_count = 0;
|
std::uint32_t frame_count = 0;
|
||||||
bool platform_deletes_recorded_files = false;
|
bool platform_deletes_recorded_files = false;
|
||||||
|
bool encoder_available = true;
|
||||||
|
bool has_canvas = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlanShareFileArgs {
|
struct PlanShareFileArgs {
|
||||||
@@ -2090,7 +2092,7 @@ void print_help()
|
|||||||
<< " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n"
|
<< " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n"
|
||||||
<< " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n"
|
<< " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n"
|
||||||
<< " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\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-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 [--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-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();
|
args.frame_count = value.value();
|
||||||
} else if (key == "--platform-deletes-recorded-files") {
|
} else if (key == "--platform-deletes-recorded-files") {
|
||||||
args.platform_deletes_recorded_files = true;
|
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 {
|
} else {
|
||||||
return pp::foundation::Status::invalid_argument("unknown option");
|
return pp::foundation::Status::invalid_argument("unknown option");
|
||||||
}
|
}
|
||||||
@@ -3562,10 +3568,16 @@ int plan_recording_session(int argc, char** argv)
|
|||||||
args.running,
|
args.running,
|
||||||
args.platform_deletes_recorded_files);
|
args.platform_deletes_recorded_files);
|
||||||
const auto export_plan = pp::app::plan_recording_export(args.frame_count);
|
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\""
|
std::cout << "{\"ok\":true,\"command\":\"plan-recording-session\""
|
||||||
<< ",\"state\":{\"running\":" << json_bool(args.running)
|
<< ",\"state\":{\"running\":" << json_bool(args.running)
|
||||||
<< ",\"frameCount\":" << args.frame_count
|
<< ",\"frameCount\":" << args.frame_count
|
||||||
<< ",\"platformDeletesRecordedFiles\":" << json_bool(args.platform_deletes_recorded_files)
|
<< ",\"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)
|
<< "},\"startDecision\":\"" << recording_start_action_name(start)
|
||||||
<< "\",\"stopDecision\":\"" << recording_stop_action_name(stop)
|
<< "\",\"stopDecision\":\"" << recording_stop_action_name(stop)
|
||||||
<< "\",\"clear\":{\"stopRunningRecording\":" << json_bool(clear.stop_running_recording)
|
<< "\",\"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
|
<< ",\"frameCountAfterClear\":" << clear.frame_count_after_clear
|
||||||
<< "},\"export\":{\"frameCount\":" << export_plan.frame_count
|
<< "},\"export\":{\"frameCount\":" << export_plan.frame_count
|
||||||
<< ",\"progressTotal\":" << export_plan.progress_total
|
<< ",\"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";
|
<< "}}\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user