Route app thread orchestration through app core

This commit is contained in:
2026-06-05 07:01:51 +02:00
parent 32c95b224f
commit c50ea14a2a
12 changed files with 749 additions and 64 deletions

View File

@@ -565,6 +565,16 @@ add_test(NAME pp_app_core_app_frame_tests COMMAND pp_app_core_app_frame_tests)
set_tests_properties(pp_app_core_app_frame_tests PROPERTIES
LABELS "app;desktop-fast;fuzz")
add_executable(pp_app_core_app_thread_tests
app_core/app_thread_tests.cpp)
target_link_libraries(pp_app_core_app_thread_tests PRIVATE
pp_app_core
pp_test_harness)
add_test(NAME pp_app_core_app_thread_tests COMMAND pp_app_core_app_thread_tests)
set_tests_properties(pp_app_core_app_thread_tests PROPERTIES
LABELS "app;desktop-fast;fuzz")
add_executable(pp_app_core_app_input_tests
app_core/app_input_tests.cpp)
target_link_libraries(pp_app_core_app_input_tests PRIVATE
@@ -1012,6 +1022,31 @@ if(TARGET pano_cli)
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-frame\".*\"message\":\"resize dimensions")
add_test(NAME pano_cli_plan_app_thread_dispatch_smoke
COMMAND pano_cli plan-app-thread --kind dispatch --unique --queued-tasks 2 --wait)
set_tests_properties(pano_cli_plan_app_thread_dispatch_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"dispatch\".*\"queueTask\":true.*\"removeMatchingUniqueTask\":true.*\"notifyWorker\":true.*\"waitForCompletion\":true")
add_test(NAME pano_cli_plan_app_thread_ui_loop_smoke
COMMAND pano_cli plan-app-thread --kind ui-loop --dt 0.25 --frame-accumulator 0.5 --fps-accumulator 0.9 --reload-accumulator 0.9 --rendered-frames 7 --live-reload)
set_tests_properties(pano_cli_plan_app_thread_ui_loop_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"ui-loop\".*\"frameAccumulator\":0.75.*\"fpsAccumulator\":0.*\"reportRenderedFrames\":true.*\"reportedFrameCount\":7.*\"checkLiveAssetReload\":true")
add_test(NAME pano_cli_plan_app_thread_stop_smoke
COMMAND pano_cli plan-app-thread --kind stop --not-joinable)
set_tests_properties(pano_cli_plan_app_thread_stop_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"kind\":\"stop\".*\"markNotRunning\":true.*\"notifyWorker\":true.*\"joinThread\":false")
add_test(NAME pano_cli_plan_app_thread_rejects_bad_timer
COMMAND pano_cli plan-app-thread --kind ui-loop --bad-timer)
set_tests_properties(pano_cli_plan_app_thread_rejects_bad_timer PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-thread\".*\"message\":\"UI loop timer values")
add_test(NAME pano_cli_plan_app_input_pointer_smoke
COMMAND pano_cli plan-app-input --kind pointer --x 100 --y 50 --zoom 2)
set_tests_properties(pano_cli_plan_app_input_pointer_smoke PROPERTIES

View File

@@ -0,0 +1,134 @@
#include "app_core/app_thread.h"
#include "test_harness.h"
#include <cmath>
namespace {
void task_dispatch_executes_immediately_on_target_thread(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_task_dispatch(true, true, 3, true, true, true);
PP_EXPECT(harness, plan.execute_immediately);
PP_EXPECT(harness, !plan.queue_task);
PP_EXPECT(harness, !plan.remove_matching_unique_task);
PP_EXPECT(harness, !plan.notify_worker);
PP_EXPECT(harness, !plan.wait_for_completion);
PP_EXPECT(harness, plan.request_redraw);
}
void task_dispatch_queues_unique_work_and_waits_for_running_worker(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_task_dispatch(false, true, 2, true, true, false);
PP_EXPECT(harness, !plan.execute_immediately);
PP_EXPECT(harness, plan.queue_task);
PP_EXPECT(harness, plan.remove_matching_unique_task);
PP_EXPECT(harness, plan.notify_worker);
PP_EXPECT(harness, plan.wait_for_completion);
PP_EXPECT(harness, !plan.request_redraw);
}
void task_dispatch_does_not_wait_for_stopped_worker(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_task_dispatch(false, false, 0, false, true, false);
PP_EXPECT(harness, plan.queue_task);
PP_EXPECT(harness, plan.notify_worker);
PP_EXPECT(harness, !plan.wait_for_completion);
}
void render_queue_drain_wraps_non_empty_work_in_context(pp::tests::Harness& harness)
{
const auto empty = pp::app::plan_app_render_queue_drain(0);
const auto work = pp::app::plan_app_render_queue_drain(4);
PP_EXPECT(harness, empty.mark_running);
PP_EXPECT(harness, !empty.drain_tasks);
PP_EXPECT(harness, !empty.wrap_in_render_context);
PP_EXPECT(harness, work.mark_running);
PP_EXPECT(harness, work.drain_tasks);
PP_EXPECT(harness, work.wrap_in_render_context);
PP_EXPECT(harness, work.task_count == 4U);
}
void ui_thread_tick_runs_tasks_and_schedules_redraw(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_ui_thread_tick(3, true);
PP_EXPECT(harness, plan.mark_running);
PP_EXPECT(harness, plan.execute_tasks);
PP_EXPECT(harness, plan.tick_app);
PP_EXPECT(harness, plan.update_before_render);
PP_EXPECT(harness, plan.enqueue_render_frame);
PP_EXPECT(harness, plan.task_count == 3U);
}
void ui_loop_timers_report_fps_and_reload_on_threshold(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_ui_loop_timers(0.25F, 0.5F, 0.9F, 0.9F, 7, true);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, plan.value().update_platform_frame);
PP_EXPECT(harness, plan.value().frame_accumulator == 0.75F);
PP_EXPECT(harness, plan.value().fps_accumulator == 0.0F);
PP_EXPECT(harness, plan.value().report_rendered_frames);
PP_EXPECT(harness, plan.value().reported_frame_count == 7);
PP_EXPECT(harness, plan.value().rendered_frames_after_report == 0);
PP_EXPECT(harness, plan.value().reload_accumulator == 0.0F);
PP_EXPECT(harness, plan.value().check_live_asset_reload);
}
}
void ui_loop_timers_reject_invalid_values(pp::tests::Harness& harness)
{
PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(-0.1F, 0.0F, 0.0F, 0.0F, 0, false));
PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(0.1F, std::nanf(""), 0.0F, 0.0F, 0, false));
PP_EXPECT(harness, !pp::app::plan_app_ui_loop_timers(0.1F, 0.0F, 0.0F, 0.0F, -1, false));
}
void ui_loop_redraw_increments_rendered_frames(pp::tests::Harness& harness)
{
const auto idle = pp::app::plan_app_ui_loop_redraw(false, 2);
const auto redraw = pp::app::plan_app_ui_loop_redraw(true, 2);
PP_EXPECT(harness, idle.tick_app);
PP_EXPECT(harness, !idle.enqueue_render_frame);
PP_EXPECT(harness, idle.rendered_frames == 2);
PP_EXPECT(harness, redraw.update_before_render);
PP_EXPECT(harness, redraw.enqueue_render_frame);
PP_EXPECT(harness, redraw.reset_frame_accumulator);
PP_EXPECT(harness, redraw.rendered_frames == 3);
}
void thread_start_stop_preserve_legacy_intents(pp::tests::Harness& harness)
{
const auto start = pp::app::plan_app_thread_start();
const auto stop_joinable = pp::app::plan_app_thread_stop(true);
const auto stop_detached = pp::app::plan_app_thread_stop(false);
PP_EXPECT(harness, start.start_thread);
PP_EXPECT(harness, start.mark_running);
PP_EXPECT(harness, stop_joinable.mark_not_running);
PP_EXPECT(harness, stop_joinable.notify_worker);
PP_EXPECT(harness, stop_joinable.join_thread);
PP_EXPECT(harness, !stop_detached.join_thread);
}
} // namespace
int main()
{
pp::tests::Harness harness;
harness.run("task dispatch executes immediately on target thread", task_dispatch_executes_immediately_on_target_thread);
harness.run("task dispatch queues unique work and waits for running worker", task_dispatch_queues_unique_work_and_waits_for_running_worker);
harness.run("task dispatch does not wait for stopped worker", task_dispatch_does_not_wait_for_stopped_worker);
harness.run("render queue drain wraps non empty work in context", render_queue_drain_wraps_non_empty_work_in_context);
harness.run("ui thread tick runs tasks and schedules redraw", ui_thread_tick_runs_tasks_and_schedules_redraw);
harness.run("ui loop timers report fps and reload on threshold", ui_loop_timers_report_fps_and_reload_on_threshold);
harness.run("ui loop timers reject invalid values", ui_loop_timers_reject_invalid_values);
harness.run("ui loop redraw increments rendered frames", ui_loop_redraw_increments_rendered_frames);
harness.run("thread start stop preserve legacy intents", thread_start_stop_preserve_legacy_intents);
return harness.finish();
}