#include "pch.h" #include "app_runtime.h" #include "app.h" AppRuntime::AppRuntime() : prepared_file_worker_([this](std::stop_token stop_token) { prepared_file_worker_main(stop_token); }) , canvas_async_worker_([this](std::stop_token stop_token) { canvas_async_worker_main(stop_token); }) { } AppRuntime::~AppRuntime() { canvas_async_worker_stop(); prepared_file_worker_stop(); } bool AppRuntime::is_render_thread() const noexcept { return std::this_thread::get_id() == render_thread_id_; } bool AppRuntime::is_ui_thread() const noexcept { return std::this_thread::get_id() == ui_thread_id_; } void AppRuntime::notify_render_worker() noexcept { render_cv_.notify_all(); } void AppRuntime::notify_ui_worker() noexcept { ui_cv_.notify_all(); } void AppRuntime::prepared_file_task(std::function task) { { std::lock_guard lock(prepared_file_task_mutex_); if (!prepared_file_running_) return; prepared_file_tasklist_.push_back(std::move(task)); } prepared_file_cv_.notify_one(); } void AppRuntime::canvas_async_task(std::function task) { { std::lock_guard lock(canvas_async_task_mutex_); if (!canvas_async_running_) return; canvas_async_tasklist_.push_back(std::move(task)); } canvas_async_cv_.notify_one(); } void AppRuntime::prepared_file_worker_main(std::stop_token stop_token) { for (;;) { std::function task; { std::unique_lock lock(prepared_file_task_mutex_); prepared_file_cv_.wait(lock, [this, &stop_token] { return stop_token.stop_requested() || !prepared_file_running_ || !prepared_file_tasklist_.empty(); }); if ((stop_token.stop_requested() || !prepared_file_running_) && prepared_file_tasklist_.empty()) break; task = std::move(prepared_file_tasklist_.front()); prepared_file_tasklist_.pop_front(); } if (task) { try { task(); } catch (...) { LOG("prepared file worker task failed"); } } } } void AppRuntime::canvas_async_worker_main(std::stop_token stop_token) { for (;;) { std::function task; { std::unique_lock lock(canvas_async_task_mutex_); canvas_async_cv_.wait(lock, [this, &stop_token] { return stop_token.stop_requested() || !canvas_async_running_ || !canvas_async_tasklist_.empty(); }); if ((stop_token.stop_requested() || !canvas_async_running_) && canvas_async_tasklist_.empty()) break; task = std::move(canvas_async_tasklist_.front()); canvas_async_tasklist_.pop_front(); } if (task) { try { task(); } catch (...) { LOG("canvas async worker task failed"); } } } } void AppRuntime::prepared_file_worker_stop() { { std::lock_guard lock(prepared_file_task_mutex_); prepared_file_running_ = false; } prepared_file_cv_.notify_all(); if (prepared_file_worker_.joinable()) { prepared_file_worker_.request_stop(); prepared_file_worker_.join(); } } void AppRuntime::canvas_async_worker_stop() { { std::lock_guard lock(canvas_async_task_mutex_); canvas_async_running_ = false; } canvas_async_cv_.notify_all(); if (canvas_async_worker_.joinable()) { canvas_async_worker_.request_stop(); canvas_async_worker_.join(); } } void AppRuntime::render_thread_tick(App& app) { static uint32_t count = 0; render_thread_id_ = std::this_thread::get_id(); std::deque working_list; pp::app::AppQueueDrainPlan drain_plan; { std::unique_lock lock(render_task_mutex_); 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_); } if (drain_plan.wrap_in_render_context) { app.async_start(); while (!working_list.empty()) { count++; working_list.front()(); working_list.pop_front(); } app.async_end(); } } void AppRuntime::render_thread_main(App& app, std::stop_token stop_token) { BT_SetTerminate(); render_thread_id_ = std::this_thread::get_id(); render_running_ = pp::app::plan_app_thread_start().mark_running; while (render_running_ && !stop_token.stop_requested()) { std::deque working_list; pp::app::AppQueueDrainPlan drain_plan; { std::unique_lock lock(render_task_mutex_); render_cv_.wait(lock, [this, &stop_token] { return stop_token.stop_requested() || !render_running_ || !render_tasklist_.empty(); }); drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size()); working_list = std::move(render_tasklist_); } if (drain_plan.wrap_in_render_context) { app.async_start(); while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } app.async_end(); } } } void AppRuntime::ui_thread_tick(App& app) { ui_thread_id_ = std::this_thread::get_id(); std::deque working_list; pp::app::AppUiTickPlan tick_plan; { std::unique_lock lock(ui_task_mutex_); tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist_.size(), app.redraw); ui_running_ = tick_plan.mark_running; working_list = std::move(ui_tasklist_); } if (tick_plan.execute_tasks) { while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } } if (tick_plan.tick_app) app.tick(0); const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, 0); if (redraw_plan.enqueue_render_frame) { if (redraw_plan.update_before_render) app.update(0); app.render_task([&app] { app.bind_default_render_target(); app.clear(); app.draw(0); app.async_swap(); }); } } void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token) { BT_SetTerminate(); ui_thread_id_ = std::this_thread::get_id(); ui_running_ = pp::app::plan_app_thread_start().mark_running; app.attach_ui_thread(); LOG("ui thread init()"); app.init(); auto t_start = std::chrono::high_resolution_clock::now(); float t_frame = 0; float t_fps_counter = 0; float t_reloader = 0; int rendered_frames = 0; while (ui_running_ && !stop_token.stop_requested()) { std::deque working_list; { std::unique_lock lock(ui_task_mutex_); ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms), [this, &stop_token] { return stop_token.stop_requested() || !ui_running_ || !ui_tasklist_.empty(); }); working_list = std::move(ui_tasklist_); } if (!working_list.empty()) { while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } } auto t_now = std::chrono::high_resolution_clock::now(); float dt = std::chrono::duration(t_now - t_start).count(); 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, app.platform_enables_live_asset_reloading()); if (timer_plan) { if (timer_plan.value().update_platform_frame) app.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; if (timer_plan.value().report_rendered_frames) app.report_rendered_frames(timer_plan.value().reported_frame_count); if (timer_plan.value().check_live_asset_reload) { if (ShaderManager::reload()) { app.stroke->update_controls(); app.redraw = true; } if (app.layout.reload()) app.redraw = true; if (app.layout_designer.reload()) app.redraw = true; } } const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, rendered_frames); if (redraw_plan.tick_app) app.tick(dt); if (redraw_plan.enqueue_render_frame) { if (redraw_plan.update_before_render) app.update(t_frame); app.render_task([&app, t_frame] { app.bind_default_render_target(); app.clear(); app.draw(t_frame); app.async_swap(); }); if (redraw_plan.reset_frame_accumulator) t_frame = 0; rendered_frames = redraw_plan.rendered_frames; } } app.detach_ui_thread(); } void AppRuntime::render_thread_start(App& app) { const auto plan = pp::app::plan_app_thread_start(); if (plan.start_thread) render_thread_ = std::jthread([this, &app](std::stop_token stop_token) { render_thread_main(app, stop_token); }); render_running_ = plan.mark_running; } void AppRuntime::render_thread_stop() { const auto plan = pp::app::plan_app_thread_stop(render_thread_.joinable()); if (plan.mark_not_running) render_running_ = false; if (plan.join_thread) render_thread_.request_stop(); if (plan.notify_worker) render_cv_.notify_all(); if (plan.join_thread) render_thread_.join(); } void AppRuntime::ui_thread_start(App& app) { const auto plan = pp::app::plan_app_thread_start(); if (plan.start_thread) ui_thread_ = std::jthread([this, &app](std::stop_token stop_token) { ui_thread_main(app, stop_token); }); ui_running_ = plan.mark_running; } void AppRuntime::ui_thread_stop() { const auto plan = pp::app::plan_app_thread_stop(ui_thread_.joinable()); if (plan.mark_not_running) ui_running_ = false; if (plan.join_thread) ui_thread_.request_stop(); if (plan.notify_worker) ui_cv_.notify_all(); if (plan.join_thread) ui_thread_.join(); }