Route app thread orchestration through app core
This commit is contained in:
@@ -246,6 +246,7 @@ add_library(pp_app_core STATIC
|
|||||||
src/app_core/app_shutdown.h
|
src/app_core/app_shutdown.h
|
||||||
src/app_core/app_status.h
|
src/app_core/app_status.h
|
||||||
src/app_core/app_startup.h
|
src/app_core/app_startup.h
|
||||||
|
src/app_core/app_thread.h
|
||||||
src/app_core/brush_package_import.h
|
src/app_core/brush_package_import.h
|
||||||
src/app_core/brush_package_export.h
|
src/app_core/brush_package_export.h
|
||||||
src/app_core/brush_ui.h
|
src/app_core/brush_ui.h
|
||||||
|
|||||||
@@ -840,6 +840,11 @@ Known local toolchain state:
|
|||||||
visibility, main UI suppression in VR-only mode, tick layout selection,
|
visibility, main UI suppression in VR-only mode, tick layout selection,
|
||||||
resize render-target/redraw projection, invalid resize rejection, and redraw
|
resize render-target/redraw projection, invalid resize rejection, and redraw
|
||||||
reset planning.
|
reset planning.
|
||||||
|
- `pp_app_core_app_thread_tests` covers render/UI task dispatch, immediate
|
||||||
|
same-thread execution, unique queued-task replacement, stopped-worker
|
||||||
|
no-wait behavior, render queue context wrapping, UI tick redraw scheduling,
|
||||||
|
UI-loop frame/FPS/reload timer thresholds, malformed timer rejection, redraw
|
||||||
|
frame-count projection, and thread start/stop intents.
|
||||||
- `pp_app_core_app_input_tests` covers pointer coordinate normalization,
|
- `pp_app_core_app_input_tests` covers pointer coordinate normalization,
|
||||||
invalid pointer/gesture inputs, designer-first mouse routing, mouse-cancel
|
invalid pointer/gesture inputs, designer-first mouse routing, mouse-cancel
|
||||||
routing, gesture midpoint/distance/delta math, main-layout routing, key state
|
routing, gesture midpoint/distance/delta math, main-layout routing, key state
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -212,6 +212,14 @@ touch-lock attachment now live in `pp_app_core`; `App::mouse_*`,
|
|||||||
`App::set_stylus`, and `pano_cli plan-app-input` consume those plans while
|
`App::set_stylus`, and `pano_cli plan-app-input` consume those plans while
|
||||||
retained event objects, child-node mutation, and legacy `Node` dispatch stay
|
retained event objects, child-node mutation, and legacy `Node` dispatch stay
|
||||||
in the app shell.
|
in the app shell.
|
||||||
|
App thread orchestration decisions for render/UI task dispatch, unique queued
|
||||||
|
task replacement, queue draining, render-context wrapping, async redraw
|
||||||
|
notification, UI tick redraw scheduling, UI-loop timer/report/reload cadence,
|
||||||
|
and thread start/stop intents now live in `pp_app_core`; `App::render_task*`,
|
||||||
|
`App::ui_task*`, `App::async_redraw`, `App::render_thread_*`,
|
||||||
|
`App::ui_thread_*`, and `pano_cli plan-app-thread` consume those plans while
|
||||||
|
retained `std::thread`, condition-variable, OpenGL context, live reload, and
|
||||||
|
task execution remain in the app shell.
|
||||||
Shutdown lifecycle staging for UI-state save, stroke-preview renderer shutdown,
|
Shutdown lifecycle staging for UI-state save, stroke-preview renderer shutdown,
|
||||||
recording stop, texture/shader invalidation, layout unload, render-target
|
recording stop, texture/shader invalidation, layout unload, render-target
|
||||||
destruction, panel-node release, and quick-mode cleanup now lives in
|
destruction, panel-node release, and quick-mode cleanup now lives in
|
||||||
@@ -1685,6 +1693,14 @@ Results:
|
|||||||
`pano_cli_plan_app_input_stylus_smoke`,
|
`pano_cli_plan_app_input_stylus_smoke`,
|
||||||
`pano_cli_plan_app_input_rejects_bad_float`, and
|
`pano_cli_plan_app_input_rejects_bad_float`, and
|
||||||
`pano_cli_plan_app_input_rejects_missing_ui_panel`.
|
`pano_cli_plan_app_input_rejects_missing_ui_panel`.
|
||||||
|
- `PanoPainter`, `pp_app_core_app_thread_tests`, and `pano_cli` built after
|
||||||
|
render/UI task dispatch, queue draining, UI-loop timer cadence, async redraw,
|
||||||
|
and start/stop decisions moved into `pp_app_core`.
|
||||||
|
- Focused app-thread CTest coverage passed for `pp_app_core_app_thread_tests`,
|
||||||
|
`pano_cli_plan_app_thread_dispatch_smoke`,
|
||||||
|
`pano_cli_plan_app_thread_ui_loop_smoke`,
|
||||||
|
`pano_cli_plan_app_thread_stop_smoke`, and
|
||||||
|
`pano_cli_plan_app_thread_rejects_bad_timer`.
|
||||||
- `PanoPainter`, `pp_app_core_app_shutdown_tests`, and `pano_cli` built after
|
- `PanoPainter`, `pp_app_core_app_shutdown_tests`, and `pano_cli` built after
|
||||||
shutdown cleanup staging moved into `pp_app_core`.
|
shutdown cleanup staging moved into `pp_app_core`.
|
||||||
- Focused shutdown CTest coverage passed for `pp_app_core_app_shutdown_tests`,
|
- Focused shutdown CTest coverage passed for `pp_app_core_app_shutdown_tests`,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ param(
|
|||||||
"pp_app_core_about_menu_tests",
|
"pp_app_core_about_menu_tests",
|
||||||
"pp_app_core_app_preferences_tests",
|
"pp_app_core_app_preferences_tests",
|
||||||
"pp_app_core_app_frame_tests",
|
"pp_app_core_app_frame_tests",
|
||||||
|
"pp_app_core_app_thread_tests",
|
||||||
"pp_app_core_app_input_tests",
|
"pp_app_core_app_input_tests",
|
||||||
"pp_app_core_app_shutdown_tests",
|
"pp_app_core_app_shutdown_tests",
|
||||||
"pp_app_core_app_startup_tests",
|
"pp_app_core_app_startup_tests",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -u
|
|||||||
|
|
||||||
presets="${1:-android-arm64 android-x64 android-quest-arm64 android-focus-arm64}"
|
presets="${1:-android-arm64 android-x64 android-quest-arm64 android-focus-arm64}"
|
||||||
shift || true
|
shift || true
|
||||||
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_renderer_gl pp_paint_renderer pp_ui_core pp_platform_api pp_app_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_brush_package_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_paint_stroke_script_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_renderer_gl_capabilities_tests pp_renderer_gl_command_plan_tests pp_paint_renderer_compositor_tests pp_platform_api_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pp_app_core_about_menu_tests pp_app_core_app_preferences_tests pp_app_core_app_frame_tests pp_app_core_app_input_tests pp_app_core_app_shutdown_tests pp_app_core_app_startup_tests pp_app_core_app_status_tests pp_app_core_command_convert_tests pp_app_core_brush_package_export_tests pp_app_core_brush_package_import_tests pp_app_core_brush_ui_tests pp_app_core_canvas_hotkey_tests pp_app_core_canvas_tool_ui_tests pp_app_core_canvas_view_tests pp_app_core_document_animation_tests pp_app_core_document_canvas_tests pp_app_core_document_cloud_tests pp_app_core_document_export_tests pp_app_core_document_import_tests pp_app_core_document_layer_tests pp_app_core_document_platform_io_tests pp_app_core_document_recording_tests pp_app_core_document_resize_tests pp_app_core_document_route_tests pp_app_core_document_sharing_tests pp_app_core_document_session_tests pp_app_core_file_menu_tests pp_app_core_grid_ui_tests pp_app_core_history_ui_tests pp_app_core_main_toolbar_tests pp_app_core_quick_ui_tests pp_app_core_tools_menu_tests}"
|
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_renderer_gl pp_paint_renderer pp_ui_core pp_platform_api pp_app_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_brush_package_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_paint_stroke_script_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_renderer_gl_capabilities_tests pp_renderer_gl_command_plan_tests pp_paint_renderer_compositor_tests pp_platform_api_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pp_app_core_about_menu_tests pp_app_core_app_preferences_tests pp_app_core_app_frame_tests pp_app_core_app_thread_tests pp_app_core_app_input_tests pp_app_core_app_shutdown_tests pp_app_core_app_startup_tests pp_app_core_app_status_tests pp_app_core_command_convert_tests pp_app_core_brush_package_export_tests pp_app_core_brush_package_import_tests pp_app_core_brush_ui_tests pp_app_core_canvas_hotkey_tests pp_app_core_canvas_tool_ui_tests pp_app_core_canvas_view_tests pp_app_core_document_animation_tests pp_app_core_document_canvas_tests pp_app_core_document_cloud_tests pp_app_core_document_export_tests pp_app_core_document_import_tests pp_app_core_document_layer_tests pp_app_core_document_platform_io_tests pp_app_core_document_recording_tests pp_app_core_document_resize_tests pp_app_core_document_route_tests pp_app_core_document_sharing_tests pp_app_core_document_session_tests pp_app_core_file_menu_tests pp_app_core_grid_ui_tests pp_app_core_history_ui_tests pp_app_core_main_toolbar_tests pp_app_core_quick_ui_tests pp_app_core_tools_menu_tests}"
|
||||||
start="$(date +%s)"
|
start="$(date +%s)"
|
||||||
|
|
||||||
overall_exit=0
|
overall_exit=0
|
||||||
|
|||||||
89
src/app.cpp
89
src/app.cpp
@@ -9,6 +9,7 @@
|
|||||||
#include "app_core/app_shutdown.h"
|
#include "app_core/app_shutdown.h"
|
||||||
#include "app_core/app_status.h"
|
#include "app_core/app_status.h"
|
||||||
#include "app_core/app_startup.h"
|
#include "app_core/app_startup.h"
|
||||||
|
#include "app_core/app_thread.h"
|
||||||
#include "app_core/canvas_tool_ui.h"
|
#include "app_core/canvas_tool_ui.h"
|
||||||
#include "app_core/document_recording.h"
|
#include "app_core/document_recording.h"
|
||||||
#include "app_core/document_route.h"
|
#include "app_core/document_route.h"
|
||||||
@@ -475,7 +476,10 @@ void App::async_start()
|
|||||||
|
|
||||||
void App::async_redraw()
|
void App::async_redraw()
|
||||||
{
|
{
|
||||||
|
const auto plan = pp::app::plan_app_async_redraw();
|
||||||
|
if (plan.set_redraw)
|
||||||
redraw = true;
|
redraw = true;
|
||||||
|
if (plan.notify_ui)
|
||||||
ui_cv.notify_all();
|
ui_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,19 +806,21 @@ void App::render_thread_tick()
|
|||||||
{
|
{
|
||||||
static uint32_t count = 0;
|
static uint32_t count = 0;
|
||||||
render_thread_id = std::this_thread::get_id();
|
render_thread_id = std::this_thread::get_id();
|
||||||
render_running = true;
|
|
||||||
std::deque<AppTask> working_list;
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppQueueDrainPlan drain_plan;
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
// move the task list locally to free the queue for other threads
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(render_task_mutex);
|
std::unique_lock<std::mutex> lock(render_task_mutex);
|
||||||
if (render_tasklist.empty())
|
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size());
|
||||||
|
render_running = drain_plan.mark_running;
|
||||||
|
if (!drain_plan.drain_tasks)
|
||||||
return;
|
return;
|
||||||
working_list = std::move(render_tasklist);
|
working_list = std::move(render_tasklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the tasks
|
// execute the tasks
|
||||||
if (!working_list.empty())
|
if (drain_plan.wrap_in_render_context)
|
||||||
{
|
{
|
||||||
async_start();
|
async_start();
|
||||||
while (!working_list.empty())
|
while (!working_list.empty())
|
||||||
@@ -835,20 +841,22 @@ void App::render_thread_main()
|
|||||||
|
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
render_thread_id = std::this_thread::get_id();
|
render_thread_id = std::this_thread::get_id();
|
||||||
render_running = true;
|
render_running = pp::app::plan_app_thread_start().mark_running;
|
||||||
while (render_running)
|
while (render_running)
|
||||||
{
|
{
|
||||||
std::deque<AppTask> working_list;
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppQueueDrainPlan drain_plan;
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
// move the task list locally to free the queue for other threads
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(render_task_mutex);
|
std::unique_lock<std::mutex> lock(render_task_mutex);
|
||||||
render_cv.wait(lock, [this] { return render_tasklist.empty() && render_running ? false : true; });
|
render_cv.wait(lock, [this] { return render_tasklist.empty() && render_running ? false : true; });
|
||||||
|
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size());
|
||||||
working_list = std::move(render_tasklist);
|
working_list = std::move(render_tasklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the tasks
|
// execute the tasks
|
||||||
if (!working_list.empty())
|
if (drain_plan.wrap_in_render_context)
|
||||||
{
|
{
|
||||||
async_start();
|
async_start();
|
||||||
while (!working_list.empty())
|
while (!working_list.empty())
|
||||||
@@ -867,18 +875,20 @@ void App::render_thread_main()
|
|||||||
void App::ui_thread_tick()
|
void App::ui_thread_tick()
|
||||||
{
|
{
|
||||||
ui_thread_id = std::this_thread::get_id();
|
ui_thread_id = std::this_thread::get_id();
|
||||||
ui_running = true;
|
|
||||||
|
|
||||||
std::deque<AppTask> working_list;
|
std::deque<AppTask> working_list;
|
||||||
|
pp::app::AppUiTickPlan tick_plan;
|
||||||
|
|
||||||
// move the task list locally to free the queue for other threads
|
// move the task list locally to free the queue for other threads
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(ui_task_mutex);
|
std::unique_lock<std::mutex> lock(ui_task_mutex);
|
||||||
|
tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist.size(), redraw);
|
||||||
|
ui_running = tick_plan.mark_running;
|
||||||
working_list = std::move(ui_tasklist);
|
working_list = std::move(ui_tasklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the tasks
|
// execute the tasks
|
||||||
if (!working_list.empty())
|
if (tick_plan.execute_tasks)
|
||||||
{
|
{
|
||||||
while (!working_list.empty())
|
while (!working_list.empty())
|
||||||
{
|
{
|
||||||
@@ -888,10 +898,13 @@ void App::ui_thread_tick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tick_plan.tick_app)
|
||||||
tick(0);
|
tick(0);
|
||||||
|
|
||||||
if (redraw)
|
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, 0);
|
||||||
|
if (redraw_plan.enqueue_render_frame)
|
||||||
{
|
{
|
||||||
|
if (redraw_plan.update_before_render)
|
||||||
update(0);
|
update(0);
|
||||||
render_task([this]
|
render_task([this]
|
||||||
{
|
{
|
||||||
@@ -909,7 +922,7 @@ void App::ui_thread_main()
|
|||||||
|
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
ui_thread_id = std::this_thread::get_id();
|
ui_thread_id = std::this_thread::get_id();
|
||||||
ui_running = true;
|
ui_running = pp::app::plan_app_thread_start().mark_running;
|
||||||
|
|
||||||
attach_ui_thread();
|
attach_ui_thread();
|
||||||
|
|
||||||
@@ -949,25 +962,25 @@ void App::ui_thread_main()
|
|||||||
float dt = std::chrono::duration<float>(t_now - t_start).count();
|
float dt = std::chrono::duration<float>(t_now - t_start).count();
|
||||||
t_start = t_now;
|
t_start = t_now;
|
||||||
|
|
||||||
|
const auto timer_plan = pp::app::plan_app_ui_loop_timers(
|
||||||
|
dt,
|
||||||
|
t_frame,
|
||||||
|
t_fps_counter,
|
||||||
|
t_reloader,
|
||||||
|
rendered_frames,
|
||||||
|
platform_enables_live_asset_reloading());
|
||||||
|
if (timer_plan) {
|
||||||
|
if (timer_plan.value().update_platform_frame)
|
||||||
update_platform_frame(dt);
|
update_platform_frame(dt);
|
||||||
|
t_frame = timer_plan.value().frame_accumulator;
|
||||||
|
t_fps_counter = timer_plan.value().fps_accumulator;
|
||||||
|
t_reloader = timer_plan.value().reload_accumulator;
|
||||||
|
rendered_frames = timer_plan.value().rendered_frames_after_report;
|
||||||
|
|
||||||
// increment timers
|
if (timer_plan.value().report_rendered_frames)
|
||||||
t_frame += dt;
|
report_rendered_frames(timer_plan.value().reported_frame_count);
|
||||||
t_fps_counter += dt;
|
|
||||||
|
|
||||||
if (t_fps_counter > 1.f)
|
if (timer_plan.value().check_live_asset_reload) {
|
||||||
{
|
|
||||||
report_rendered_frames(rendered_frames);
|
|
||||||
t_fps_counter = 0;
|
|
||||||
rendered_frames = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform_enables_live_asset_reloading())
|
|
||||||
{
|
|
||||||
t_reloader += dt;
|
|
||||||
if (t_reloader > 1.0)
|
|
||||||
{
|
|
||||||
t_reloader = 0;
|
|
||||||
if (ShaderManager::reload())
|
if (ShaderManager::reload())
|
||||||
{
|
{
|
||||||
stroke->update_controls();
|
stroke->update_controls();
|
||||||
@@ -980,10 +993,13 @@ void App::ui_thread_main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames);
|
||||||
|
if (redraw_plan.tick_app)
|
||||||
tick(dt);
|
tick(dt);
|
||||||
|
|
||||||
if (redraw)
|
if (redraw_plan.enqueue_render_frame)
|
||||||
{
|
{
|
||||||
|
if (redraw_plan.update_before_render)
|
||||||
update(t_frame);
|
update(t_frame);
|
||||||
render_task([this, t_frame]
|
render_task([this, t_frame]
|
||||||
{
|
{
|
||||||
@@ -992,8 +1008,9 @@ void App::ui_thread_main()
|
|||||||
draw(t_frame);
|
draw(t_frame);
|
||||||
async_swap();
|
async_swap();
|
||||||
});
|
});
|
||||||
|
if (redraw_plan.reset_frame_accumulator)
|
||||||
t_frame = 0;
|
t_frame = 0;
|
||||||
rendered_frames++;
|
rendered_frames = redraw_plan.rendered_frames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
detach_ui_thread();
|
detach_ui_thread();
|
||||||
@@ -1001,28 +1018,38 @@ void App::ui_thread_main()
|
|||||||
|
|
||||||
void App::render_thread_start()
|
void App::render_thread_start()
|
||||||
{
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_start();
|
||||||
|
if (plan.start_thread)
|
||||||
render_thread = std::thread(&App::render_thread_main, this);
|
render_thread = std::thread(&App::render_thread_main, this);
|
||||||
render_running = true;
|
render_running = plan.mark_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::render_thread_stop()
|
void App::render_thread_stop()
|
||||||
{
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_stop(render_thread.joinable());
|
||||||
|
if (plan.mark_not_running)
|
||||||
render_running = false;
|
render_running = false;
|
||||||
|
if (plan.notify_worker)
|
||||||
render_cv.notify_all();
|
render_cv.notify_all();
|
||||||
if (render_thread.joinable())
|
if (plan.join_thread)
|
||||||
render_thread.join();
|
render_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_start()
|
void App::ui_thread_start()
|
||||||
{
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_start();
|
||||||
|
if (plan.start_thread)
|
||||||
ui_thread = std::thread(&App::ui_thread_main, this);
|
ui_thread = std::thread(&App::ui_thread_main, this);
|
||||||
ui_running = true;
|
ui_running = plan.mark_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ui_thread_stop()
|
void App::ui_thread_stop()
|
||||||
{
|
{
|
||||||
|
const auto plan = pp::app::plan_app_thread_stop(ui_thread.joinable());
|
||||||
|
if (plan.mark_not_running)
|
||||||
ui_running = false;
|
ui_running = false;
|
||||||
|
if (plan.notify_worker)
|
||||||
ui_cv.notify_all();
|
ui_cv.notify_all();
|
||||||
if (ui_thread.joinable())
|
if (plan.join_thread)
|
||||||
ui_thread.join();
|
ui_thread.join();
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/app.h
72
src/app.h
@@ -25,6 +25,7 @@
|
|||||||
#include "node_panel_animation.h"
|
#include "node_panel_animation.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "app_core/document_session.h"
|
#include "app_core/document_session.h"
|
||||||
|
#include "app_core/app_thread.h"
|
||||||
|
|
||||||
namespace pp::platform {
|
namespace pp::platform {
|
||||||
class PlatformServices;
|
class PlatformServices;
|
||||||
@@ -374,20 +375,35 @@ public:
|
|||||||
{
|
{
|
||||||
AppTask pt(task);
|
AppTask pt(task);
|
||||||
auto f = pt.get_future();
|
auto f = pt.get_future();
|
||||||
if (is_render_thread())
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_render_thread(),
|
||||||
|
unique,
|
||||||
|
0U,
|
||||||
|
render_running,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
{
|
{
|
||||||
pt();
|
pt();
|
||||||
}
|
}
|
||||||
else
|
else if (dispatch.queue_task)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
std::lock_guard<std::mutex> lock(render_task_mutex);
|
||||||
|
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
false,
|
||||||
|
unique,
|
||||||
|
render_tasklist.size(),
|
||||||
|
render_running,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
// remove any previously queued task from the same lambda
|
// remove any previously queued task from the same lambda
|
||||||
if (unique && !render_tasklist.empty())
|
if (queue_dispatch.remove_matching_unique_task)
|
||||||
render_tasklist.erase(std::remove_if(render_tasklist.begin(), render_tasklist.end(),
|
render_tasklist.erase(std::remove_if(render_tasklist.begin(), render_tasklist.end(),
|
||||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist.end());
|
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist.end());
|
||||||
render_tasklist.push_back(std::move(pt));
|
render_tasklist.push_back(std::move(pt));
|
||||||
}
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
render_cv.notify_all();
|
render_cv.notify_all();
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
@@ -398,19 +414,27 @@ public:
|
|||||||
{
|
{
|
||||||
AppTask pt(task);
|
AppTask pt(task);
|
||||||
auto f = pt.get_future();
|
auto f = pt.get_future();
|
||||||
if (is_render_thread())
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_render_thread(),
|
||||||
|
false,
|
||||||
|
0U,
|
||||||
|
render_running,
|
||||||
|
true,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
{
|
{
|
||||||
pt();
|
pt();
|
||||||
}
|
}
|
||||||
else
|
else if (dispatch.queue_task)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
std::lock_guard<std::mutex> lock(render_task_mutex);
|
||||||
render_tasklist.push_back(std::move(pt));
|
render_tasklist.push_back(std::move(pt));
|
||||||
}
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
render_cv.notify_all();
|
render_cv.notify_all();
|
||||||
}
|
}
|
||||||
if (render_running)
|
if (dispatch.wait_for_completion)
|
||||||
f.get();
|
f.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,20 +470,35 @@ public:
|
|||||||
{
|
{
|
||||||
AppTask pt(task);
|
AppTask pt(task);
|
||||||
auto f = pt.get_future();
|
auto f = pt.get_future();
|
||||||
if (is_ui_thread())
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_ui_thread(),
|
||||||
|
unique,
|
||||||
|
0U,
|
||||||
|
ui_running,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
{
|
{
|
||||||
pt();
|
pt();
|
||||||
}
|
}
|
||||||
else
|
else if (dispatch.queue_task)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
||||||
|
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
false,
|
||||||
|
unique,
|
||||||
|
ui_tasklist.size(),
|
||||||
|
ui_running,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
// remove any previously queued task from the same lambda
|
// remove any previously queued task from the same lambda
|
||||||
if (unique && !ui_tasklist.empty())
|
if (queue_dispatch.remove_matching_unique_task)
|
||||||
ui_tasklist.erase(std::remove_if(ui_tasklist.begin(), ui_tasklist.end(),
|
ui_tasklist.erase(std::remove_if(ui_tasklist.begin(), ui_tasklist.end(),
|
||||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist.end());
|
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist.end());
|
||||||
ui_tasklist.push_back(std::move(pt));
|
ui_tasklist.push_back(std::move(pt));
|
||||||
}
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
ui_cv.notify_all();
|
ui_cv.notify_all();
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
@@ -470,20 +509,29 @@ public:
|
|||||||
{
|
{
|
||||||
AppTask pt(task);
|
AppTask pt(task);
|
||||||
auto f = pt.get_future();
|
auto f = pt.get_future();
|
||||||
if (is_ui_thread())
|
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||||
|
is_ui_thread(),
|
||||||
|
false,
|
||||||
|
0U,
|
||||||
|
ui_running,
|
||||||
|
true,
|
||||||
|
true);
|
||||||
|
if (dispatch.execute_immediately)
|
||||||
{
|
{
|
||||||
pt();
|
pt();
|
||||||
}
|
}
|
||||||
else
|
else if (dispatch.queue_task)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
||||||
ui_tasklist.push_back(std::move(pt));
|
ui_tasklist.push_back(std::move(pt));
|
||||||
}
|
}
|
||||||
|
if (dispatch.notify_worker)
|
||||||
ui_cv.notify_all();
|
ui_cv.notify_all();
|
||||||
}
|
}
|
||||||
if (ui_running)
|
if (dispatch.wait_for_completion)
|
||||||
f.get();
|
f.get();
|
||||||
|
if (dispatch.request_redraw)
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
200
src/app_core/app_thread.h
Normal file
200
src/app_core/app_thread.h
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "foundation/result.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace pp::app {
|
||||||
|
|
||||||
|
struct AppTaskDispatchPlan {
|
||||||
|
bool execute_immediately = false;
|
||||||
|
bool queue_task = false;
|
||||||
|
bool remove_matching_unique_task = false;
|
||||||
|
bool notify_worker = false;
|
||||||
|
bool wait_for_completion = false;
|
||||||
|
bool request_redraw = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppAsyncRedrawPlan {
|
||||||
|
bool set_redraw = true;
|
||||||
|
bool notify_ui = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppQueueDrainPlan {
|
||||||
|
bool mark_running = true;
|
||||||
|
bool drain_tasks = false;
|
||||||
|
bool wrap_in_render_context = false;
|
||||||
|
std::size_t task_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppUiTickPlan {
|
||||||
|
bool mark_running = true;
|
||||||
|
bool execute_tasks = false;
|
||||||
|
bool tick_app = true;
|
||||||
|
bool update_before_render = false;
|
||||||
|
bool enqueue_render_frame = false;
|
||||||
|
std::size_t task_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppUiLoopTimerPlan {
|
||||||
|
bool update_platform_frame = true;
|
||||||
|
float frame_accumulator = 0.0F;
|
||||||
|
float fps_accumulator = 0.0F;
|
||||||
|
float reload_accumulator = 0.0F;
|
||||||
|
bool report_rendered_frames = false;
|
||||||
|
int reported_frame_count = 0;
|
||||||
|
int rendered_frames_after_report = 0;
|
||||||
|
bool check_live_asset_reload = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppUiLoopRedrawPlan {
|
||||||
|
bool tick_app = true;
|
||||||
|
bool update_before_render = false;
|
||||||
|
bool enqueue_render_frame = false;
|
||||||
|
bool reset_frame_accumulator = false;
|
||||||
|
int rendered_frames = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppThreadStartPlan {
|
||||||
|
bool start_thread = true;
|
||||||
|
bool mark_running = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppThreadStopPlan {
|
||||||
|
bool mark_not_running = true;
|
||||||
|
bool notify_worker = true;
|
||||||
|
bool join_thread = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppTaskDispatchPlan plan_app_task_dispatch(
|
||||||
|
bool already_on_target_thread,
|
||||||
|
bool unique,
|
||||||
|
std::size_t queued_task_count,
|
||||||
|
bool worker_running,
|
||||||
|
bool wait_for_completion,
|
||||||
|
bool request_redraw_after_dispatch) noexcept
|
||||||
|
{
|
||||||
|
const bool queue_task = !already_on_target_thread;
|
||||||
|
return AppTaskDispatchPlan {
|
||||||
|
.execute_immediately = already_on_target_thread,
|
||||||
|
.queue_task = queue_task,
|
||||||
|
.remove_matching_unique_task = queue_task && unique && queued_task_count > 0U,
|
||||||
|
.notify_worker = queue_task,
|
||||||
|
.wait_for_completion = queue_task && worker_running && wait_for_completion,
|
||||||
|
.request_redraw = request_redraw_after_dispatch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppAsyncRedrawPlan plan_app_async_redraw() noexcept
|
||||||
|
{
|
||||||
|
return AppAsyncRedrawPlan {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppQueueDrainPlan plan_app_render_queue_drain(std::size_t queued_task_count) noexcept
|
||||||
|
{
|
||||||
|
const bool drain = queued_task_count > 0U;
|
||||||
|
return AppQueueDrainPlan {
|
||||||
|
.mark_running = true,
|
||||||
|
.drain_tasks = drain,
|
||||||
|
.wrap_in_render_context = drain,
|
||||||
|
.task_count = queued_task_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppQueueDrainPlan plan_app_ui_queue_drain(std::size_t queued_task_count) noexcept
|
||||||
|
{
|
||||||
|
return AppQueueDrainPlan {
|
||||||
|
.mark_running = true,
|
||||||
|
.drain_tasks = queued_task_count > 0U,
|
||||||
|
.wrap_in_render_context = false,
|
||||||
|
.task_count = queued_task_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppUiTickPlan plan_app_ui_thread_tick(
|
||||||
|
std::size_t queued_task_count,
|
||||||
|
bool redraw) noexcept
|
||||||
|
{
|
||||||
|
return AppUiTickPlan {
|
||||||
|
.mark_running = true,
|
||||||
|
.execute_tasks = queued_task_count > 0U,
|
||||||
|
.tick_app = true,
|
||||||
|
.update_before_render = redraw,
|
||||||
|
.enqueue_render_frame = redraw,
|
||||||
|
.task_count = queued_task_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline pp::foundation::Result<AppUiLoopTimerPlan> plan_app_ui_loop_timers(
|
||||||
|
float delta_time_seconds,
|
||||||
|
float frame_accumulator,
|
||||||
|
float fps_accumulator,
|
||||||
|
float reload_accumulator,
|
||||||
|
int rendered_frames,
|
||||||
|
bool live_asset_reloading_enabled)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(delta_time_seconds) || !std::isfinite(frame_accumulator)
|
||||||
|
|| !std::isfinite(fps_accumulator) || !std::isfinite(reload_accumulator)) {
|
||||||
|
return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
|
||||||
|
pp::foundation::Status::invalid_argument("UI loop timer values must be finite"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta_time_seconds < 0.0F || frame_accumulator < 0.0F
|
||||||
|
|| fps_accumulator < 0.0F || reload_accumulator < 0.0F || rendered_frames < 0) {
|
||||||
|
return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
|
||||||
|
pp::foundation::Status::invalid_argument("UI loop timer values must not be negative"));
|
||||||
|
}
|
||||||
|
|
||||||
|
AppUiLoopTimerPlan plan;
|
||||||
|
plan.frame_accumulator = frame_accumulator + delta_time_seconds;
|
||||||
|
plan.fps_accumulator = fps_accumulator + delta_time_seconds;
|
||||||
|
plan.reload_accumulator = reload_accumulator;
|
||||||
|
plan.rendered_frames_after_report = rendered_frames;
|
||||||
|
|
||||||
|
if (plan.fps_accumulator > 1.0F) {
|
||||||
|
plan.report_rendered_frames = true;
|
||||||
|
plan.reported_frame_count = rendered_frames;
|
||||||
|
plan.fps_accumulator = 0.0F;
|
||||||
|
plan.rendered_frames_after_report = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (live_asset_reloading_enabled) {
|
||||||
|
plan.reload_accumulator += delta_time_seconds;
|
||||||
|
if (plan.reload_accumulator > 1.0F) {
|
||||||
|
plan.reload_accumulator = 0.0F;
|
||||||
|
plan.check_live_asset_reload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp::foundation::Result<AppUiLoopTimerPlan>::success(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppUiLoopRedrawPlan plan_app_ui_loop_redraw(
|
||||||
|
bool redraw,
|
||||||
|
int rendered_frames) noexcept
|
||||||
|
{
|
||||||
|
return AppUiLoopRedrawPlan {
|
||||||
|
.tick_app = true,
|
||||||
|
.update_before_render = redraw,
|
||||||
|
.enqueue_render_frame = redraw,
|
||||||
|
.reset_frame_accumulator = redraw,
|
||||||
|
.rendered_frames = rendered_frames + (redraw ? 1 : 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppThreadStartPlan plan_app_thread_start() noexcept
|
||||||
|
{
|
||||||
|
return AppThreadStartPlan {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr AppThreadStopPlan plan_app_thread_stop(bool thread_joinable) noexcept
|
||||||
|
{
|
||||||
|
return AppThreadStopPlan {
|
||||||
|
.mark_not_running = true,
|
||||||
|
.notify_worker = true,
|
||||||
|
.join_thread = thread_joinable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pp::app
|
||||||
@@ -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
|
set_tests_properties(pp_app_core_app_frame_tests PROPERTIES
|
||||||
LABELS "app;desktop-fast;fuzz")
|
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
|
add_executable(pp_app_core_app_input_tests
|
||||||
app_core/app_input_tests.cpp)
|
app_core/app_input_tests.cpp)
|
||||||
target_link_libraries(pp_app_core_app_input_tests PRIVATE
|
target_link_libraries(pp_app_core_app_input_tests PRIVATE
|
||||||
@@ -1012,6 +1022,31 @@ if(TARGET pano_cli)
|
|||||||
WILL_FAIL TRUE
|
WILL_FAIL TRUE
|
||||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-frame\".*\"message\":\"resize dimensions")
|
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
|
add_test(NAME pano_cli_plan_app_input_pointer_smoke
|
||||||
COMMAND pano_cli plan-app-input --kind pointer --x 100 --y 50 --zoom 2)
|
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
|
set_tests_properties(pano_cli_plan_app_input_pointer_smoke PROPERTIES
|
||||||
|
|||||||
134
tests/app_core/app_thread_tests.cpp
Normal file
134
tests/app_core/app_thread_tests.cpp
Normal 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();
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "app_core/app_shutdown.h"
|
#include "app_core/app_shutdown.h"
|
||||||
#include "app_core/app_status.h"
|
#include "app_core/app_status.h"
|
||||||
#include "app_core/app_startup.h"
|
#include "app_core/app_startup.h"
|
||||||
|
#include "app_core/app_thread.h"
|
||||||
#include "app_core/brush_package_import.h"
|
#include "app_core/brush_package_import.h"
|
||||||
#include "app_core/brush_package_export.h"
|
#include "app_core/brush_package_export.h"
|
||||||
#include "app_core/brush_ui.h"
|
#include "app_core/brush_ui.h"
|
||||||
@@ -264,6 +265,25 @@ struct PlanAppFrameArgs {
|
|||||||
bool bad_resize = false;
|
bool bad_resize = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PlanAppThreadArgs {
|
||||||
|
std::string kind = "dispatch";
|
||||||
|
bool on_target_thread = false;
|
||||||
|
bool unique = false;
|
||||||
|
bool worker_running = true;
|
||||||
|
bool wait = false;
|
||||||
|
bool request_redraw = false;
|
||||||
|
bool redraw = false;
|
||||||
|
bool live_reload = false;
|
||||||
|
bool joinable = true;
|
||||||
|
std::uint32_t queued_tasks = 0;
|
||||||
|
int rendered_frames = 0;
|
||||||
|
float delta_time_seconds = 0.25F;
|
||||||
|
float frame_accumulator = 0.5F;
|
||||||
|
float fps_accumulator = 0.9F;
|
||||||
|
float reload_accumulator = 0.9F;
|
||||||
|
bool bad_timer = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct PlanAppInputArgs {
|
struct PlanAppInputArgs {
|
||||||
std::string kind = "pointer";
|
std::string kind = "pointer";
|
||||||
float x0 = 100.0F;
|
float x0 = 100.0F;
|
||||||
@@ -2075,6 +2095,7 @@ void print_help()
|
|||||||
<< " 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"
|
||||||
<< " plan-app-frame [--redraw] [--animate] [--no-designer-layout] [--no-main-layout] [--no-canvas] [--no-canvas-document] [--vr-active] [--ui-hidden] [--vr-only] [--resize-width N] [--resize-height N] [--bad-resize]\n"
|
<< " plan-app-frame [--redraw] [--animate] [--no-designer-layout] [--no-main-layout] [--no-canvas] [--no-canvas-document] [--vr-active] [--ui-hidden] [--vr-only] [--resize-width N] [--resize-height N] [--bad-resize]\n"
|
||||||
|
<< " plan-app-thread --kind dispatch|render-drain|ui-drain|ui-tick|ui-loop|redraw|start|stop [--on-target-thread] [--unique] [--worker-stopped] [--wait] [--request-redraw] [--redraw] [--live-reload] [--not-joinable] [--queued-tasks N] [--rendered-frames N] [--dt N] [--frame-accumulator N] [--fps-accumulator N] [--reload-accumulator N] [--bad-timer]\n"
|
||||||
<< " plan-app-input --kind pointer|gesture|cancel|main|key|ui-toggle|stylus [--x N] [--y N] [--x1 N] [--y1 N] [--prev-x N] [--prev-y N] [--prev-x1 N] [--prev-y1 N] [--zoom N] [--no-designer-layout] [--no-main-layout] [--spacebar] [--vr-active] [--key-up] [--ui-hidden] [--no-canvas] [--main-child-count N] [--panel-child-count N] [--bad-float]\n"
|
<< " plan-app-input --kind pointer|gesture|cancel|main|key|ui-toggle|stylus [--x N] [--y N] [--x1 N] [--y1 N] [--prev-x N] [--prev-y N] [--prev-x1 N] [--prev-y1 N] [--zoom N] [--no-designer-layout] [--no-main-layout] [--spacebar] [--vr-active] [--key-up] [--ui-hidden] [--no-canvas] [--main-child-count N] [--panel-child-count N] [--bad-float]\n"
|
||||||
<< " plan-app-shutdown\n"
|
<< " plan-app-shutdown\n"
|
||||||
<< " plan-command-convert [--project FILE] [--output FILE] [--canvas-resolution N]\n"
|
<< " plan-command-convert [--project FILE] [--output FILE] [--canvas-resolution N]\n"
|
||||||
@@ -3885,6 +3906,190 @@ int plan_app_frame(int argc, char** argv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp::foundation::Status parse_plan_app_thread_args(
|
||||||
|
int argc,
|
||||||
|
char** argv,
|
||||||
|
PlanAppThreadArgs& args)
|
||||||
|
{
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
const std::string_view key(argv[i]);
|
||||||
|
if (key == "--kind") {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||||
|
}
|
||||||
|
args.kind = argv[++i];
|
||||||
|
} else if (key == "--on-target-thread") {
|
||||||
|
args.on_target_thread = true;
|
||||||
|
} else if (key == "--unique") {
|
||||||
|
args.unique = true;
|
||||||
|
} else if (key == "--worker-stopped") {
|
||||||
|
args.worker_running = false;
|
||||||
|
} else if (key == "--wait") {
|
||||||
|
args.wait = true;
|
||||||
|
} else if (key == "--request-redraw") {
|
||||||
|
args.request_redraw = true;
|
||||||
|
} else if (key == "--redraw") {
|
||||||
|
args.redraw = true;
|
||||||
|
} else if (key == "--live-reload") {
|
||||||
|
args.live_reload = true;
|
||||||
|
} else if (key == "--not-joinable") {
|
||||||
|
args.joinable = false;
|
||||||
|
} else if (key == "--bad-timer") {
|
||||||
|
args.bad_timer = true;
|
||||||
|
} else if (key == "--queued-tasks") {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||||
|
}
|
||||||
|
const auto value = pp::foundation::parse_u32(argv[++i]);
|
||||||
|
if (!value) {
|
||||||
|
return value.status();
|
||||||
|
}
|
||||||
|
args.queued_tasks = value.value();
|
||||||
|
} else if (key == "--rendered-frames") {
|
||||||
|
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.rendered_frames = value.value();
|
||||||
|
} else if (key == "--dt" || key == "--frame-accumulator"
|
||||||
|
|| key == "--fps-accumulator" || key == "--reload-accumulator") {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||||
|
}
|
||||||
|
const auto value = parse_float_arg(argv[++i]);
|
||||||
|
if (!value) {
|
||||||
|
return value.status();
|
||||||
|
}
|
||||||
|
if (key == "--dt") {
|
||||||
|
args.delta_time_seconds = value.value();
|
||||||
|
} else if (key == "--frame-accumulator") {
|
||||||
|
args.frame_accumulator = value.value();
|
||||||
|
} else if (key == "--fps-accumulator") {
|
||||||
|
args.fps_accumulator = value.value();
|
||||||
|
} else {
|
||||||
|
args.reload_accumulator = value.value();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return pp::foundation::Status::invalid_argument("unknown option");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
int plan_app_thread(int argc, char** argv)
|
||||||
|
{
|
||||||
|
PlanAppThreadArgs args;
|
||||||
|
const auto status = parse_plan_app_thread_args(argc, argv, args);
|
||||||
|
if (!status.ok()) {
|
||||||
|
print_error("plan-app-thread", status.message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "{\"ok\":true,\"command\":\"plan-app-thread\",\"kind\":\""
|
||||||
|
<< json_escape(args.kind) << "\"";
|
||||||
|
|
||||||
|
if (args.kind == "dispatch") {
|
||||||
|
const auto plan = pp::app::plan_app_task_dispatch(
|
||||||
|
args.on_target_thread,
|
||||||
|
args.unique,
|
||||||
|
args.queued_tasks,
|
||||||
|
args.worker_running,
|
||||||
|
args.wait,
|
||||||
|
args.request_redraw);
|
||||||
|
std::cout << ",\"plan\":{\"executeImmediately\":" << json_bool(plan.execute_immediately)
|
||||||
|
<< ",\"queueTask\":" << json_bool(plan.queue_task)
|
||||||
|
<< ",\"removeMatchingUniqueTask\":" << json_bool(plan.remove_matching_unique_task)
|
||||||
|
<< ",\"notifyWorker\":" << json_bool(plan.notify_worker)
|
||||||
|
<< ",\"waitForCompletion\":" << json_bool(plan.wait_for_completion)
|
||||||
|
<< ",\"requestRedraw\":" << json_bool(plan.request_redraw)
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "render-drain" || args.kind == "ui-drain") {
|
||||||
|
const auto plan = args.kind == "render-drain"
|
||||||
|
? pp::app::plan_app_render_queue_drain(args.queued_tasks)
|
||||||
|
: pp::app::plan_app_ui_queue_drain(args.queued_tasks);
|
||||||
|
std::cout << ",\"plan\":{\"markRunning\":" << json_bool(plan.mark_running)
|
||||||
|
<< ",\"drainTasks\":" << json_bool(plan.drain_tasks)
|
||||||
|
<< ",\"wrapInRenderContext\":" << json_bool(plan.wrap_in_render_context)
|
||||||
|
<< ",\"taskCount\":" << plan.task_count
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "ui-tick") {
|
||||||
|
const auto plan = pp::app::plan_app_ui_thread_tick(args.queued_tasks, args.redraw);
|
||||||
|
std::cout << ",\"plan\":{\"markRunning\":" << json_bool(plan.mark_running)
|
||||||
|
<< ",\"executeTasks\":" << json_bool(plan.execute_tasks)
|
||||||
|
<< ",\"tickApp\":" << json_bool(plan.tick_app)
|
||||||
|
<< ",\"updateBeforeRender\":" << json_bool(plan.update_before_render)
|
||||||
|
<< ",\"enqueueRenderFrame\":" << json_bool(plan.enqueue_render_frame)
|
||||||
|
<< ",\"taskCount\":" << plan.task_count
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "ui-loop") {
|
||||||
|
const auto plan = pp::app::plan_app_ui_loop_timers(
|
||||||
|
args.bad_timer ? -1.0F : args.delta_time_seconds,
|
||||||
|
args.frame_accumulator,
|
||||||
|
args.fps_accumulator,
|
||||||
|
args.reload_accumulator,
|
||||||
|
args.rendered_frames,
|
||||||
|
args.live_reload);
|
||||||
|
if (!plan) {
|
||||||
|
print_error("plan-app-thread", plan.status().message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
std::cout << ",\"plan\":{\"updatePlatformFrame\":" << json_bool(plan.value().update_platform_frame)
|
||||||
|
<< ",\"frameAccumulator\":" << plan.value().frame_accumulator
|
||||||
|
<< ",\"fpsAccumulator\":" << plan.value().fps_accumulator
|
||||||
|
<< ",\"reloadAccumulator\":" << plan.value().reload_accumulator
|
||||||
|
<< ",\"reportRenderedFrames\":" << json_bool(plan.value().report_rendered_frames)
|
||||||
|
<< ",\"reportedFrameCount\":" << plan.value().reported_frame_count
|
||||||
|
<< ",\"renderedFramesAfterReport\":" << plan.value().rendered_frames_after_report
|
||||||
|
<< ",\"checkLiveAssetReload\":" << json_bool(plan.value().check_live_asset_reload)
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "redraw") {
|
||||||
|
const auto plan = pp::app::plan_app_ui_loop_redraw(args.redraw, args.rendered_frames);
|
||||||
|
std::cout << ",\"plan\":{\"tickApp\":" << json_bool(plan.tick_app)
|
||||||
|
<< ",\"updateBeforeRender\":" << json_bool(plan.update_before_render)
|
||||||
|
<< ",\"enqueueRenderFrame\":" << json_bool(plan.enqueue_render_frame)
|
||||||
|
<< ",\"resetFrameAccumulator\":" << json_bool(plan.reset_frame_accumulator)
|
||||||
|
<< ",\"renderedFrames\":" << plan.rendered_frames
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "start") {
|
||||||
|
const auto plan = pp::app::plan_app_thread_start();
|
||||||
|
std::cout << ",\"plan\":{\"startThread\":" << json_bool(plan.start_thread)
|
||||||
|
<< ",\"markRunning\":" << json_bool(plan.mark_running)
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.kind == "stop") {
|
||||||
|
const auto plan = pp::app::plan_app_thread_stop(args.joinable);
|
||||||
|
std::cout << ",\"plan\":{\"markNotRunning\":" << json_bool(plan.mark_not_running)
|
||||||
|
<< ",\"notifyWorker\":" << json_bool(plan.notify_worker)
|
||||||
|
<< ",\"joinThread\":" << json_bool(plan.join_thread)
|
||||||
|
<< "}}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error("plan-app-thread", "unknown app thread plan kind");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
pp::foundation::Status parse_plan_app_input_args(
|
pp::foundation::Status parse_plan_app_input_args(
|
||||||
int argc,
|
int argc,
|
||||||
char** argv,
|
char** argv,
|
||||||
@@ -10469,6 +10674,10 @@ int main(int argc, char** argv)
|
|||||||
return plan_app_frame(argc, argv);
|
return plan_app_frame(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command == "plan-app-thread") {
|
||||||
|
return plan_app_thread(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
if (command == "plan-app-input") {
|
if (command == "plan-app-input") {
|
||||||
return plan_app_input(argc, argv);
|
return plan_app_input(argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user