Advance app runtime ownership and modernization docs

This commit is contained in:
2026-06-16 06:35:59 +02:00
parent c3d757f4a4
commit a76560e3df
24 changed files with 6675 additions and 9009 deletions

View File

@@ -32,10 +32,6 @@
App* App::I = nullptr; // singleton
std::deque<AppTask> App::render_tasklist;
std::mutex App::render_task_mutex;
std::condition_variable App::render_cv;
namespace {
pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept
@@ -130,17 +126,6 @@ void apply_app_scissor_test(bool enabled)
}
}
std::thread App::render_thread;
std::thread::id App::render_thread_id;
bool App::render_running = false;
std::deque<AppTask> App::ui_tasklist;
std::mutex App::ui_task_mutex;
std::condition_variable App::ui_cv;
std::thread App::ui_thread;
std::thread::id App::ui_thread_id;
bool App::ui_running = false;
void App::create()
{
const auto initial_surface = pp::app::plan_app_initial_surface();
@@ -353,7 +338,7 @@ void App::async_redraw()
if (plan.set_redraw)
redraw = true;
if (plan.notify_ui)
ui_cv.notify_all();
runtime_.notify_ui_worker();
}
void App::async_end()
@@ -699,252 +684,40 @@ void App::rec_loop()
void App::render_thread_tick()
{
static uint32_t count = 0;
render_thread_id = std::this_thread::get_id();
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);
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 (drain_plan.wrap_in_render_context)
{
async_start();
while (!working_list.empty())
{
//LOG("render task %d", count);
//LOG("task %s", working_list.front().name.c_str());
count++;
working_list.front()();
working_list.pop_front();
}
async_end();
}
runtime_.render_thread_tick(*this);
}
void App::render_thread_main()
{
BT_SetTerminate();
uint32_t count = 0;
render_thread_id = std::this_thread::get_id();
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 (drain_plan.wrap_in_render_context)
{
async_start();
while (!working_list.empty())
{
//LOG("render task %d", count);
//LOG("task %s", working_list.front().name.c_str());
count++;
working_list.front()();
working_list.pop_front();
}
async_end();
}
}
runtime_.render_thread_main(*this);
}
void App::ui_thread_tick()
{
ui_thread_id = std::this_thread::get_id();
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 (tick_plan.execute_tasks)
{
while (!working_list.empty())
{
//LOG("ui task %d", count);
working_list.front()();
working_list.pop_front();
}
}
if (tick_plan.tick_app)
tick(0);
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);
render_task([this]
{
bind_default_render_target();
clear();
draw(0);
async_swap();
});
}
runtime_.ui_thread_tick(*this);
}
void App::ui_thread_main()
{
BT_SetTerminate();
uint32_t count = 0;
ui_thread_id = std::this_thread::get_id();
ui_running = pp::app::plan_app_thread_start().mark_running;
attach_ui_thread();
LOG("ui thread init()");
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)
{
std::deque<AppTask> working_list;
// move the task list locally to free the queue for other threads
{
std::unique_lock<std::mutex> lock(ui_task_mutex);
ui_cv.wait_for(lock, std::chrono::milliseconds(idle_ms),
[this] { return ui_tasklist.empty() && ui_running ? false : true; });
working_list = std::move(ui_tasklist);
}
// execute the tasks
if (!working_list.empty())
{
while (!working_list.empty())
{
//LOG("ui task %d", count);
count++;
working_list.front()();
working_list.pop_front();
}
}
auto t_now = std::chrono::high_resolution_clock::now();
float dt = std::chrono::duration<float>(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,
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;
if (timer_plan.value().report_rendered_frames)
report_rendered_frames(timer_plan.value().reported_frame_count);
if (timer_plan.value().check_live_asset_reload) {
if (ShaderManager::reload())
{
stroke->update_controls();
redraw = true;
}
if (layout.reload())
redraw = true;
if (layout_designer.reload())
redraw = true;
}
}
const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames);
if (redraw_plan.tick_app)
tick(dt);
if (redraw_plan.enqueue_render_frame)
{
if (redraw_plan.update_before_render)
update(t_frame);
render_task([this, t_frame]
{
bind_default_render_target();
clear();
draw(t_frame);
async_swap();
});
if (redraw_plan.reset_frame_accumulator)
t_frame = 0;
rendered_frames = redraw_plan.rendered_frames;
}
}
detach_ui_thread();
runtime_.ui_thread_main(*this);
}
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_running = plan.mark_running;
runtime_.render_thread_start(*this);
}
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;
if (plan.notify_worker)
render_cv.notify_all();
if (plan.join_thread)
render_thread.join();
runtime_.render_thread_stop();
}
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_running = plan.mark_running;
runtime_.ui_thread_start(*this);
}
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;
if (plan.notify_worker)
ui_cv.notify_all();
if (plan.join_thread)
ui_thread.join();
runtime_.ui_thread_stop();
}

169
src/app.h
View File

@@ -25,6 +25,7 @@
#include "layout.h"
#include "app_core/document_session.h"
#include "app_core/app_thread.h"
#include "app_runtime.h"
namespace pp::platform {
class PlatformServices;
@@ -76,21 +77,6 @@ struct VRController
virtual float get_trigger_value() const { return 1.f; }
};
struct AppTask : public std::packaged_task<void()>
{
size_t task_id;
#ifdef _DEBUG
std::string name;
#endif
template<typename F> AppTask(F f) : std::packaged_task<void()>(f)
{
task_id = typeid(f).hash_code();
#ifdef _DEBUG
name = typeid(f).name();
#endif
}
};
class App
{
public:
@@ -345,16 +331,13 @@ public:
void cmd_convert(std::string pano_path, std::string out_path);
AppRuntime& runtime() noexcept { return runtime_; }
const AppRuntime& runtime() const noexcept { return runtime_; }
//////////////////////////////////////////////////////////////////////////
// RENDER THREAD
//////////////////////////////////////////////////////////////////////////
static std::deque<AppTask> render_tasklist;
static std::mutex render_task_mutex;
static std::condition_variable render_cv;
static std::thread render_thread;
static std::thread::id render_thread_id;
static bool render_running;
void render_thread_tick();
void render_thread_main();
void render_thread_start();
@@ -362,94 +345,30 @@ public:
bool is_render_thread()
{
return std::this_thread::get_id() == render_thread_id;
return runtime_.is_render_thread();
}
// don't capture a reference to this ptr as the object may be destroyed
// by the time the task is executed
template<typename T>
std::future<void> render_task_async(T task, bool unique = false)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_render_thread(),
unique,
0U,
render_running,
false,
false);
if (dispatch.execute_immediately)
{
pt();
}
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 (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));
}
if (dispatch.notify_worker)
render_cv.notify_all();
}
return f;
return runtime_.render_task_async(std::move(task), unique);
}
template<typename T>
void render_task(T task)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_render_thread(),
false,
0U,
render_running,
true,
false);
if (dispatch.execute_immediately)
{
pt();
}
else if (dispatch.queue_task)
{
{
std::lock_guard<std::mutex> lock(render_task_mutex);
render_tasklist.push_back(std::move(pt));
}
if (dispatch.notify_worker)
render_cv.notify_all();
}
if (dispatch.wait_for_completion)
f.get();
runtime_.render_task(std::move(task));
}
void render_sync()
{
render_task([] {});
runtime_.render_sync();
}
//////////////////////////////////////////////////////////////////////////
// UI THREAD
//////////////////////////////////////////////////////////////////////////
static std::deque<AppTask> ui_tasklist;
static std::mutex ui_task_mutex;
static std::condition_variable ui_cv;
static std::thread ui_thread;
static std::thread::id ui_thread_id;
static bool ui_running;
void ui_thread_tick();
void ui_thread_main();
void ui_thread_start();
@@ -457,83 +376,29 @@ public:
bool is_ui_thread()
{
return std::this_thread::get_id() == ui_thread_id;
return runtime_.is_ui_thread();
}
// don't capture a reference to this ptr as the object may be destroyed
// by the time the task is executed
template<typename T>
std::future<void> ui_task_async(T task, bool unique = false)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_ui_thread(),
unique,
0U,
ui_running,
false,
false);
if (dispatch.execute_immediately)
{
pt();
}
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 (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));
}
if (dispatch.notify_worker)
ui_cv.notify_all();
}
return f;
return runtime_.ui_task_async(std::move(task), unique);
}
template<typename T>
void ui_task(T task)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_ui_thread(),
false,
0U,
ui_running,
true,
true);
if (dispatch.execute_immediately)
{
pt();
}
else if (dispatch.queue_task)
{
{
std::lock_guard<std::mutex> lock(ui_task_mutex);
ui_tasklist.push_back(std::move(pt));
}
if (dispatch.notify_worker)
ui_cv.notify_all();
}
if (dispatch.wait_for_completion)
f.get();
if (dispatch.request_redraw)
runtime_.ui_task(std::move(task));
if (runtime_.request_redraw())
redraw = true;
runtime_.clear_request_redraw();
}
void ui_sync()
{
ui_task([] {});
runtime_.ui_sync();
}
private:
AppRuntime runtime_;
};

View File

@@ -19,7 +19,7 @@ void App::cloud_upload()
void App::cloud_upload_all()
{
std::thread([this] {
pp::panopainter::queue_legacy_cloud_worker_task([this] {
BT_SetTerminate();
auto names = Asset::list_files(data_path, ".*\\.ppi");
@@ -28,7 +28,7 @@ void App::cloud_upload_all()
const auto status = pp::panopainter::execute_legacy_cloud_bulk_upload_plan(*this, plan);
if (!status.ok())
LOG("Cloud bulk upload action failed: %s", status.message);
}).detach();
});
}
void App::cloud_browse()

258
src/app_runtime.cpp Normal file
View File

@@ -0,0 +1,258 @@
#include "pch.h"
#include "app_runtime.h"
#include "app.h"
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::render_thread_tick(App& app)
{
static uint32_t count = 0;
render_thread_id_ = std::this_thread::get_id();
std::deque<AppTask> working_list;
pp::app::AppQueueDrainPlan drain_plan;
{
std::unique_lock<std::mutex> 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)
{
BT_SetTerminate();
render_thread_id_ = std::this_thread::get_id();
render_running_ = pp::app::plan_app_thread_start().mark_running;
while (render_running_)
{
std::deque<AppTask> working_list;
pp::app::AppQueueDrainPlan drain_plan;
{
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_);
}
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<AppTask> working_list;
pp::app::AppUiTickPlan tick_plan;
{
std::unique_lock<std::mutex> 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)
{
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_)
{
std::deque<AppTask> working_list;
{
std::unique_lock<std::mutex> lock(ui_task_mutex_);
ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms),
[this] { return ui_tasklist_.empty() && ui_running_ ? false : true; });
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<float>(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::thread(&AppRuntime::render_thread_main, this, std::ref(app));
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.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::thread(&AppRuntime::ui_thread_main, this, std::ref(app));
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.notify_worker)
ui_cv_.notify_all();
if (plan.join_thread)
ui_thread_.join();
}

216
src/app_runtime.h Normal file
View File

@@ -0,0 +1,216 @@
#pragma once
#include "app_core/app_thread.h"
#include <algorithm>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <functional>
#include <future>
#include <mutex>
#include <string>
#include <typeinfo>
#include <utility>
#include <thread>
class App;
struct AppTask : public std::packaged_task<void()>
{
size_t task_id;
#ifdef _DEBUG
std::string name;
#endif
template<typename F> AppTask(F f) : std::packaged_task<void()>(f)
{
task_id = typeid(f).hash_code();
#ifdef _DEBUG
name = typeid(f).name();
#endif
}
};
class AppRuntime
{
public:
[[nodiscard]] bool is_render_thread() const noexcept;
[[nodiscard]] bool is_ui_thread() const noexcept;
[[nodiscard]] bool request_redraw() const noexcept { return request_redraw_; }
void clear_request_redraw() noexcept { request_redraw_ = false; }
void notify_render_worker() noexcept;
void notify_ui_worker() noexcept;
void render_thread_tick(App& app);
void render_thread_main(App& app);
void render_thread_start(App& app);
void render_thread_stop();
void ui_thread_tick(App& app);
void ui_thread_main(App& app);
void ui_thread_start(App& app);
void ui_thread_stop();
template<typename T>
std::future<void> render_task_async(T task, bool unique = false)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_render_thread(),
unique,
0U,
render_running_,
false,
false);
if (dispatch.execute_immediately)
{
pt();
}
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);
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));
}
if (dispatch.notify_worker)
render_cv_.notify_all();
}
return f;
}
template<typename T>
void render_task(T task)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_render_thread(),
false,
0U,
render_running_,
true,
false);
if (dispatch.execute_immediately)
{
pt();
}
else if (dispatch.queue_task)
{
{
std::lock_guard<std::mutex> lock(render_task_mutex_);
render_tasklist_.push_back(std::move(pt));
}
if (dispatch.notify_worker)
render_cv_.notify_all();
}
if (dispatch.wait_for_completion)
f.get();
}
void render_sync()
{
render_task([] {});
}
template<typename T>
std::future<void> ui_task_async(T task, bool unique = false)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_ui_thread(),
unique,
0U,
ui_running_,
false,
false);
if (dispatch.execute_immediately)
{
pt();
}
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);
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));
}
if (dispatch.notify_worker)
ui_cv_.notify_all();
}
return f;
}
template<typename T>
void ui_task(T task)
{
AppTask pt(task);
auto f = pt.get_future();
const auto dispatch = pp::app::plan_app_task_dispatch(
is_ui_thread(),
false,
0U,
ui_running_,
true,
true);
if (dispatch.execute_immediately)
{
pt();
}
else if (dispatch.queue_task)
{
{
std::lock_guard<std::mutex> lock(ui_task_mutex_);
ui_tasklist_.push_back(std::move(pt));
}
if (dispatch.notify_worker)
ui_cv_.notify_all();
}
if (dispatch.wait_for_completion)
f.get();
if (dispatch.request_redraw)
request_redraw_ = true;
}
void ui_sync()
{
ui_task([] {});
}
private:
std::deque<AppTask> render_tasklist_;
std::mutex render_task_mutex_;
std::condition_variable render_cv_;
std::thread render_thread_;
std::thread::id render_thread_id_;
bool render_running_ = false;
std::deque<AppTask> ui_tasklist_;
std::mutex ui_task_mutex_;
std::condition_variable ui_cv_;
std::thread ui_thread_;
std::thread::id ui_thread_id_;
bool ui_running_ = false;
bool request_redraw_ = false;
};

View File

@@ -21,6 +21,79 @@ pp::foundation::Status execute_legacy_cloud_download_selection_action(
namespace {
class LegacyCloudWorker final {
public:
LegacyCloudWorker()
: worker_([this](std::stop_token stop_token) {
run(stop_token);
})
{
}
~LegacyCloudWorker()
{
shutdown();
}
void post(std::function<void()> task)
{
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopping_)
return;
tasks_.push_back(std::move(task));
}
cv_.notify_one();
}
private:
void shutdown()
{
{
std::lock_guard<std::mutex> lock(mutex_);
stopping_ = true;
}
cv_.notify_all();
}
void run(std::stop_token stop_token)
{
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] {
return stopping_ || stop_token.stop_requested() || !tasks_.empty();
});
if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
break;
task = std::move(tasks_.front());
tasks_.pop_front();
}
if (task) {
try {
task();
} catch (...) {
LOG("cloud worker task failed");
}
}
}
}
std::mutex mutex_;
std::condition_variable cv_;
std::deque<std::function<void()>> tasks_;
bool stopping_ = false;
std::jthread worker_;
};
LegacyCloudWorker& cloud_worker()
{
static LegacyCloudWorker worker;
return worker;
}
#if WITH_CURL
int progress_callback_download(void* clientp, curl_off_t dltotal,
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
@@ -233,28 +306,12 @@ void execute_cloud_download_thread(
execute_cloud_download_flow(app, request);
}
void launch_cloud_download_thread(
App& app,
const pp::app::CloudDownloadRequest& request)
{
std::thread([app = &app, request] {
execute_cloud_download_thread(*app, request);
}).detach();
}
void execute_cloud_publish_worker(App& app, bool save_before_upload)
{
BT_SetTerminate();
execute_cloud_publish_transfer_and_success_prompt(app, save_before_upload);
}
void launch_cloud_publish_thread(App& app, bool save_before_upload)
{
std::thread([app = &app, save_before_upload] {
execute_cloud_publish_worker(*app, save_before_upload);
}).detach();
}
void wire_cloud_publish_prompt_buttons(
const std::shared_ptr<NodeMessageBox>& dialog,
std::function<void()> upload_thread)
@@ -273,7 +330,9 @@ void setup_cloud_publish_prompt(App& app, bool save_before_upload)
const auto prompt_plan = pp::app::plan_cloud_publish_prompt();
auto dialog = app.message_box(prompt_plan.title, prompt_plan.message, prompt_plan.show_cancel);
wire_cloud_publish_prompt_buttons(dialog, [&app, save_before_upload] {
launch_cloud_publish_thread(app, save_before_upload);
queue_legacy_cloud_worker_task([app = &app, save_before_upload] {
execute_cloud_publish_worker(*app, save_before_upload);
});
});
}
@@ -379,7 +438,9 @@ public:
void start_download(const pp::app::CloudDownloadRequest& request) override
{
launch_cloud_download_thread(app_, request);
queue_legacy_cloud_worker_task([app = &app_, request] {
execute_cloud_download_thread(*app, request);
});
}
private:
@@ -395,6 +456,11 @@ void show_cloud_save_required_warning(App& app)
} // namespace
void queue_legacy_cloud_worker_task(std::function<void()> task)
{
cloud_worker().post(std::move(task));
}
pp::foundation::Status execute_legacy_cloud_upload_plan(
App& app,
const pp::app::CloudUploadPlan& plan)

View File

@@ -3,6 +3,8 @@
#include "app_core/document_cloud.h"
#include "foundation/result.h"
#include <functional>
class App;
class NodeDialogCloud;
@@ -22,4 +24,6 @@ namespace pp::panopainter {
pp::app::CloudDownloadSelectionAction action,
NodeDialogCloud& dialog);
void queue_legacy_cloud_worker_task(std::function<void()> task);
} // namespace pp::panopainter

View File

@@ -174,6 +174,25 @@ void close_legacy_overlay_handle_ignoring_status(
(void)close_legacy_overlay_node(anchor, overlay);
}
void close_legacy_overlay_handle_and_reset(
Node& anchor,
pp::ui::NodeHandle& overlay) noexcept
{
if (overlay.valid()) {
close_legacy_overlay_handle_ignoring_status(anchor, overlay);
overlay = {};
}
}
void close_legacy_overlay_handles_and_reset(
Node& anchor,
pp::ui::NodeHandle& popup_overlay,
pp::ui::NodeHandle& tick_overlay) noexcept
{
close_legacy_overlay_handle_and_reset(anchor, popup_overlay);
close_legacy_overlay_handle_and_reset(anchor, tick_overlay);
}
void close_legacy_overlay_handles_if_open(
Node& anchor,
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,

View File

@@ -111,6 +111,13 @@ void close_legacy_popup_overlay(Node& node) noexcept;
void close_legacy_overlay_handle_ignoring_status(
Node& anchor,
pp::ui::NodeHandle overlay) noexcept;
void close_legacy_overlay_handle_and_reset(
Node& anchor,
pp::ui::NodeHandle& overlay) noexcept;
void close_legacy_overlay_handles_and_reset(
Node& anchor,
pp::ui::NodeHandle& popup_overlay,
pp::ui::NodeHandle& tick_overlay) noexcept;
void close_legacy_overlay_handles_if_open(
Node& anchor,
const pp::foundation::Result<pp::ui::NodeHandle>& popup_overlay,

View File

@@ -932,8 +932,8 @@ int main(int argc, char** argv)
wglMakeCurrent(NULL, NULL);
running = 1;
App::I->render_thread_start();
App::I->ui_thread_start();
App::I->runtime().render_thread_start(*App::I);
App::I->runtime().ui_thread_start(*App::I);
#ifdef _DEBUG
glad_set_pre_callback(_pre_call_callback);
@@ -1035,8 +1035,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
running = 0;
if (hmd_renderer.joinable())
hmd_renderer.join();
App::I->ui_thread_stop();
App::I->render_thread_stop();
App::I->runtime().ui_thread_stop();
App::I->runtime().render_thread_stop();
App::I->terminate();
delete App::I;
PostQuitMessage(0);

View File

@@ -42,7 +42,7 @@
void Node::app_redraw()
{
App::I->redraw = true;
App::I->render_cv.notify_all();
App::I->runtime().notify_render_worker();
}
void Node::watch(std::function<bool(Node*)> observer)

View File

@@ -68,7 +68,7 @@ void NodeDialogCloud::init_controls()
btn_cancel = find<NodeButton>("btn-cancel");
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
container = find<Node>("files-list");
std::thread(&NodeDialogCloud::load_thumbs_thread, this).detach();
load_thumbs_worker_ = std::jthread(&NodeDialogCloud::load_thumbs_thread, this);
}
void NodeDialogCloud::loaded()
@@ -79,6 +79,7 @@ void NodeDialogCloud::removed(Node* parent)
{
NodeBorder::removed(parent);
closed = true;
load_thumbs_worker_.request_stop();
}
NodeText* NodeDialogCloud::create_loading_status_text()
@@ -143,24 +144,30 @@ std::vector<NodeDialogCloudItem*> NodeDialogCloud::create_cloud_file_items(const
return nodes;
}
void NodeDialogCloud::load_thumbs_thread()
void NodeDialogCloud::load_thumbs_thread(std::stop_token stop)
{
#if WITH_CURL
BT_SetTerminate();
CURL *curl = curl_easy_init();
auto curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>(curl_easy_init(), curl_easy_cleanup);
std::string res;
if (curl)
{
if (stop.stop_requested() || closed)
return;
auto* text = create_loading_status_text();
auto* align = text->m_parent;
if (!load_cloud_file_list(curl, res, *text))
if (!load_cloud_file_list(curl.get(), res, *text))
{
return;
}
pp::panopainter::destroy_legacy_node(*align);
if (stop.stop_requested() || closed)
return;
LOG("CLOUD LIST: %s", res.c_str());
auto names = split(res, ',');
@@ -171,13 +178,12 @@ void NodeDialogCloud::load_thumbs_thread()
{
const auto& n = names[i];
auto* node = nodes[i];
if (closed)
if (stop.stop_requested() || closed)
break;
if (!load_cloud_thumb(curl, n, node, res))
if (!load_cloud_thumb(curl.get(), n, node, res))
break;
}
curl_easy_cleanup(curl);
}
#endif //CURL
}

View File

@@ -6,6 +6,8 @@
#include "node_text_input.h"
#include <vector>
#include <stop_token>
#include <thread>
class NodeDialogCloudItem : public NodeBorder
{
@@ -46,8 +48,11 @@ public:
void init_controls();
virtual void loaded() override;
virtual void removed(Node* parent) override;
void load_thumbs_thread();
void load_thumbs_thread(std::stop_token stop);
NodeText* create_loading_status_text();
bool load_cloud_file_list(CURL* curl, std::string& response, NodeText& status_text);
std::vector<NodeDialogCloudItem*> create_cloud_file_items(const std::vector<std::string>& names);
private:
std::jthread load_thumbs_worker_;
};

View File

@@ -722,14 +722,10 @@ void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeCont
void NodePanelStroke::close_popup_overlay_handles() noexcept
{
if (m_popup_overlay_handle.valid()) {
pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_popup_overlay_handle);
m_popup_overlay_handle = {};
}
if (m_tick_overlay_handle.valid()) {
pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_tick_overlay_handle);
m_tick_overlay_handle = {};
}
pp::panopainter::close_legacy_overlay_handles_and_reset(
*this,
m_popup_overlay_handle,
m_tick_overlay_handle);
}
kEventResult NodePanelStroke::handle_event(Event* e)

View File

@@ -4,6 +4,7 @@
#include "node_popup_menu.h"
#include "node_button_custom.h"
#include "app.h"
#include <memory>
Node* NodePopupMenu::clone_instantiate() const
{
@@ -33,19 +34,22 @@ kEventResult NodePopupMenu::handle_event(Event* e)
case kEventType::MouseDownL:
break;
case kEventType::MouseUpL:
if (m_mouse_inside)
{
for (int i = 0; i < m_children.size(); i++)
auto self = std::static_pointer_cast<NodePopupMenu>(shared_from_this());
if (m_mouse_inside)
{
if (m_children[i]->m_mouse_inside)
for (int i = 0; i < m_children.size(); i++)
{
if (on_select)
on_select(this, i);
break;
if (m_children[i]->m_mouse_inside)
{
if (on_select)
on_select(self.get(), i);
break;
}
}
}
close_popup();
}
close_popup();
break;
default:
return kEventResult::Available;
@@ -61,13 +65,19 @@ void NodePopupMenu::added(Node* parent)
m_mouse_ignore = false;
m_flood_events = true;
m_capture_children = false;
auto self = std::static_pointer_cast<NodePopupMenu>(shared_from_this());
for (int i = 0; i < m_children.size(); i++)
{
if (auto b = std::dynamic_pointer_cast<NodeButtonCustom>(m_children[i]))
{
b->on_click = [this, i](Node* target) {
if (on_select)
on_select(this, i);
std::weak_ptr<NodePopupMenu> weak_self = self;
b->on_click = [weak_self, i](Node* target) {
auto self = weak_self.lock();
if (!self) {
return;
}
if (self->on_select)
self->on_select(self.get(), i);
};
}
}