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

200
src/app_core/app_thread.h Normal file
View 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