Route app thread orchestration through app core
This commit is contained in:
117
src/app.cpp
117
src/app.cpp
@@ -9,6 +9,7 @@
|
||||
#include "app_core/app_shutdown.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/app_startup.h"
|
||||
#include "app_core/app_thread.h"
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_recording.h"
|
||||
#include "app_core/document_route.h"
|
||||
@@ -475,8 +476,11 @@ void App::async_start()
|
||||
|
||||
void App::async_redraw()
|
||||
{
|
||||
redraw = true;
|
||||
ui_cv.notify_all();
|
||||
const auto plan = pp::app::plan_app_async_redraw();
|
||||
if (plan.set_redraw)
|
||||
redraw = true;
|
||||
if (plan.notify_ui)
|
||||
ui_cv.notify_all();
|
||||
}
|
||||
|
||||
void App::async_end()
|
||||
@@ -802,19 +806,21 @@ void App::render_thread_tick()
|
||||
{
|
||||
static uint32_t count = 0;
|
||||
render_thread_id = std::this_thread::get_id();
|
||||
render_running = true;
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppQueueDrainPlan drain_plan;
|
||||
|
||||
// move the task list locally to free the queue for other threads
|
||||
{
|
||||
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;
|
||||
working_list = std::move(render_tasklist);
|
||||
}
|
||||
|
||||
// execute the tasks
|
||||
if (!working_list.empty())
|
||||
if (drain_plan.wrap_in_render_context)
|
||||
{
|
||||
async_start();
|
||||
while (!working_list.empty())
|
||||
@@ -835,20 +841,22 @@ void App::render_thread_main()
|
||||
|
||||
uint32_t count = 0;
|
||||
render_thread_id = std::this_thread::get_id();
|
||||
render_running = true;
|
||||
render_running = pp::app::plan_app_thread_start().mark_running;
|
||||
while (render_running)
|
||||
{
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppQueueDrainPlan drain_plan;
|
||||
|
||||
// move the task list locally to free the queue for other threads
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(render_task_mutex);
|
||||
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);
|
||||
}
|
||||
|
||||
// execute the tasks
|
||||
if (!working_list.empty())
|
||||
if (drain_plan.wrap_in_render_context)
|
||||
{
|
||||
async_start();
|
||||
while (!working_list.empty())
|
||||
@@ -867,18 +875,20 @@ void App::render_thread_main()
|
||||
void App::ui_thread_tick()
|
||||
{
|
||||
ui_thread_id = std::this_thread::get_id();
|
||||
ui_running = true;
|
||||
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppUiTickPlan tick_plan;
|
||||
|
||||
// move the task list locally to free the queue for other threads
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// execute the tasks
|
||||
if (!working_list.empty())
|
||||
if (tick_plan.execute_tasks)
|
||||
{
|
||||
while (!working_list.empty())
|
||||
{
|
||||
@@ -888,11 +898,14 @@ void App::ui_thread_tick()
|
||||
}
|
||||
}
|
||||
|
||||
tick(0);
|
||||
if (tick_plan.tick_app)
|
||||
tick(0);
|
||||
|
||||
if (redraw)
|
||||
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, 0);
|
||||
if (redraw_plan.enqueue_render_frame)
|
||||
{
|
||||
update(0);
|
||||
if (redraw_plan.update_before_render)
|
||||
update(0);
|
||||
render_task([this]
|
||||
{
|
||||
bind_default_render_target();
|
||||
@@ -909,7 +922,7 @@ void App::ui_thread_main()
|
||||
|
||||
uint32_t count = 0;
|
||||
ui_thread_id = std::this_thread::get_id();
|
||||
ui_running = true;
|
||||
ui_running = pp::app::plan_app_thread_start().mark_running;
|
||||
|
||||
attach_ui_thread();
|
||||
|
||||
@@ -949,25 +962,25 @@ void App::ui_thread_main()
|
||||
float dt = std::chrono::duration<float>(t_now - t_start).count();
|
||||
t_start = t_now;
|
||||
|
||||
update_platform_frame(dt);
|
||||
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);
|
||||
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
|
||||
t_frame += dt;
|
||||
t_fps_counter += dt;
|
||||
if (timer_plan.value().report_rendered_frames)
|
||||
report_rendered_frames(timer_plan.value().reported_frame_count);
|
||||
|
||||
if (t_fps_counter > 1.f)
|
||||
{
|
||||
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 (timer_plan.value().check_live_asset_reload) {
|
||||
if (ShaderManager::reload())
|
||||
{
|
||||
stroke->update_controls();
|
||||
@@ -980,11 +993,14 @@ void App::ui_thread_main()
|
||||
}
|
||||
}
|
||||
|
||||
tick(dt);
|
||||
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames);
|
||||
if (redraw_plan.tick_app)
|
||||
tick(dt);
|
||||
|
||||
if (redraw)
|
||||
if (redraw_plan.enqueue_render_frame)
|
||||
{
|
||||
update(t_frame);
|
||||
if (redraw_plan.update_before_render)
|
||||
update(t_frame);
|
||||
render_task([this, t_frame]
|
||||
{
|
||||
bind_default_render_target();
|
||||
@@ -992,8 +1008,9 @@ void App::ui_thread_main()
|
||||
draw(t_frame);
|
||||
async_swap();
|
||||
});
|
||||
t_frame = 0;
|
||||
rendered_frames++;
|
||||
if (redraw_plan.reset_frame_accumulator)
|
||||
t_frame = 0;
|
||||
rendered_frames = redraw_plan.rendered_frames;
|
||||
}
|
||||
}
|
||||
detach_ui_thread();
|
||||
@@ -1001,28 +1018,38 @@ void App::ui_thread_main()
|
||||
|
||||
void App::render_thread_start()
|
||||
{
|
||||
render_thread = std::thread(&App::render_thread_main, this);
|
||||
render_running = true;
|
||||
const auto plan = pp::app::plan_app_thread_start();
|
||||
if (plan.start_thread)
|
||||
render_thread = std::thread(&App::render_thread_main, this);
|
||||
render_running = plan.mark_running;
|
||||
}
|
||||
|
||||
void App::render_thread_stop()
|
||||
{
|
||||
render_running = false;
|
||||
render_cv.notify_all();
|
||||
if (render_thread.joinable())
|
||||
const auto plan = pp::app::plan_app_thread_stop(render_thread.joinable());
|
||||
if (plan.mark_not_running)
|
||||
render_running = false;
|
||||
if (plan.notify_worker)
|
||||
render_cv.notify_all();
|
||||
if (plan.join_thread)
|
||||
render_thread.join();
|
||||
}
|
||||
|
||||
void App::ui_thread_start()
|
||||
{
|
||||
ui_thread = std::thread(&App::ui_thread_main, this);
|
||||
ui_running = true;
|
||||
const auto plan = pp::app::plan_app_thread_start();
|
||||
if (plan.start_thread)
|
||||
ui_thread = std::thread(&App::ui_thread_main, this);
|
||||
ui_running = plan.mark_running;
|
||||
}
|
||||
|
||||
void App::ui_thread_stop()
|
||||
{
|
||||
ui_running = false;
|
||||
ui_cv.notify_all();
|
||||
if (ui_thread.joinable())
|
||||
const auto plan = pp::app::plan_app_thread_stop(ui_thread.joinable());
|
||||
if (plan.mark_not_running)
|
||||
ui_running = false;
|
||||
if (plan.notify_worker)
|
||||
ui_cv.notify_all();
|
||||
if (plan.join_thread)
|
||||
ui_thread.join();
|
||||
}
|
||||
|
||||
82
src/app.h
82
src/app.h
@@ -25,6 +25,7 @@
|
||||
#include "node_panel_animation.h"
|
||||
#include "layout.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "app_core/app_thread.h"
|
||||
|
||||
namespace pp::platform {
|
||||
class PlatformServices;
|
||||
@@ -374,21 +375,36 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
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();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
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
|
||||
if (unique && !render_tasklist.empty())
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
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());
|
||||
render_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
render_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
render_cv.notify_all();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
@@ -398,19 +414,27 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
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();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
||||
render_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
render_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
render_cv.notify_all();
|
||||
}
|
||||
if (render_running)
|
||||
if (dispatch.wait_for_completion)
|
||||
f.get();
|
||||
}
|
||||
|
||||
@@ -446,21 +470,36 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
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();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
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
|
||||
if (unique && !ui_tasklist.empty())
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
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());
|
||||
ui_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
ui_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
ui_cv.notify_all();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
@@ -470,21 +509,30 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
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();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
||||
ui_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
ui_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
ui_cv.notify_all();
|
||||
}
|
||||
if (ui_running)
|
||||
if (dispatch.wait_for_completion)
|
||||
f.get();
|
||||
redraw = true;
|
||||
if (dispatch.request_redraw)
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
void ui_sync()
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user