Harden app runtime and thin export shell
This commit is contained in:
@@ -180,7 +180,6 @@ set(PP_PANOPAINTER_APP_SOURCES
|
||||
src/legacy_document_open_services.h
|
||||
src/legacy_document_session_services.cpp
|
||||
src/legacy_document_session_services.h
|
||||
${PP_PLATFORM_WEB_SOURCES}
|
||||
src/version.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,23 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
|
||||
## Reductions
|
||||
|
||||
- 2026-06-17: `DEBT-0003` was narrowed again. `src/app_runtime.cpp` and
|
||||
`src/app_runtime.h` now synchronize render/UI worker running state, reject
|
||||
cross-thread work once those workers stop, and drain queued work through
|
||||
explicit runtime-owned post/shutdown semantics consumed by
|
||||
`src/legacy_app_runtime_shell_services.cpp`; broader singleton reach, app
|
||||
shell ownership, and remaining runtime service adoption still remain.
|
||||
- 2026-06-17: `DEBT-0017` was narrowed again.
|
||||
`cmake/PanoPainterSources.cmake` no longer compiles
|
||||
`${PP_PLATFORM_WEB_SOURCES}` into `PP_PANOPAINTER_APP_SOURCES`, so root app
|
||||
source ownership no longer mixes live Web platform implementation files into
|
||||
the retained app group; broader platform entrypoint/package cleanup remains.
|
||||
- 2026-06-17: `DEBT-0043` was narrowed again.
|
||||
`src/legacy_canvas_document_io_services.cpp` now delegates the live
|
||||
equirectangular export family through `src/legacy_document_export_services.*`
|
||||
instead of carrying that orchestration inline with the broader canvas
|
||||
document-I/O shell; retained export directory creation, Web handoff, and
|
||||
remaining legacy `Canvas` export execution still remain.
|
||||
- 2026-06-17: `DEBT-0003` was narrowed again.
|
||||
`src/platform_windows/windows_runtime_flow.*` now owns the live Win32
|
||||
startup/session composition flow, including the bound app/session preflight
|
||||
|
||||
@@ -76,12 +76,12 @@ Current conclusion:
|
||||
debt.
|
||||
- Renderer API contracts exist, but retained OpenGL resource classes still leak
|
||||
into app/UI/document code.
|
||||
- `AppRuntime` is a useful step toward owned queues, but thread affinity and
|
||||
shutdown safety are still expressed mostly by convention, mutable booleans,
|
||||
and singleton call sites.
|
||||
- Platform extraction improved substantially, but CMake and entrypoint cleanup
|
||||
are not complete. In particular, Web platform sources still appear inside
|
||||
`PP_PANOPAINTER_APP_SOURCES`, even though `pp_platform_web` also exists.
|
||||
- `AppRuntime` now owns synchronized running flags and explicit
|
||||
same-thread/post-reject queue behavior, but broader app/runtime singleton
|
||||
reach and retained shell ownership still remain.
|
||||
- Platform extraction improved substantially and the root app source group no
|
||||
longer compiles Web platform sources directly, but broader CMake and
|
||||
entrypoint cleanup are not complete.
|
||||
|
||||
## Target Architecture
|
||||
|
||||
|
||||
@@ -55,11 +55,9 @@ Key facts:
|
||||
- Raw `Node*` and callback captures remain a dominant UI lifetime risk.
|
||||
- `RTT`, `Texture2D`, `Shape`, `Shader`, `Font`, and `CanvasLayer` still route
|
||||
render work through `App::I` queues.
|
||||
- `AppRuntime` uses `std::jthread`, which is progress, but queue ownership,
|
||||
running flags, thread affinity, and shutdown contracts are not yet component
|
||||
contracts.
|
||||
- `PP_PANOPAINTER_APP_SOURCES` still includes `${PP_PLATFORM_WEB_SOURCES}`,
|
||||
which should be removed in favor of concrete `pp_platform_web` ownership.
|
||||
- `AppRuntime` now owns synchronized running flags plus explicit post/reject,
|
||||
same-thread execution, and queue-drain behavior, but broader singleton reach
|
||||
and app-shell ownership remain.
|
||||
|
||||
## Parallel Assignment Rules
|
||||
|
||||
|
||||
18
src/app.h
18
src/app.h
@@ -336,19 +336,19 @@ public:
|
||||
|
||||
bool is_render_thread()
|
||||
{
|
||||
return runtime_.is_render_thread();
|
||||
return runtime().is_render_thread();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::future<void> render_task_async(T task, bool unique = false)
|
||||
{
|
||||
return runtime_.render_task_async(std::move(task), unique);
|
||||
return runtime().render_task_async(std::move(task), unique);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void render_task(T task)
|
||||
{
|
||||
runtime_.render_task(std::move(task));
|
||||
runtime().render_task(std::move(task));
|
||||
}
|
||||
|
||||
void render_sync()
|
||||
@@ -367,27 +367,27 @@ public:
|
||||
|
||||
bool is_ui_thread()
|
||||
{
|
||||
return runtime_.is_ui_thread();
|
||||
return runtime().is_ui_thread();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::future<void> ui_task_async(T task, bool unique = false)
|
||||
{
|
||||
return runtime_.ui_task_async(std::move(task), unique);
|
||||
return runtime().ui_task_async(std::move(task), unique);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ui_task(T task)
|
||||
{
|
||||
runtime_.ui_task(std::move(task));
|
||||
if (runtime_.request_redraw())
|
||||
runtime().ui_task(std::move(task));
|
||||
if (runtime().request_redraw())
|
||||
redraw = true;
|
||||
runtime_.clear_request_redraw();
|
||||
runtime().clear_request_redraw();
|
||||
}
|
||||
|
||||
void ui_sync()
|
||||
{
|
||||
runtime_.ui_sync();
|
||||
runtime().ui_sync();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -19,6 +19,17 @@ struct AppTaskDispatchPlan {
|
||||
bool reject_unsafe_cross_thread_dispatch = false;
|
||||
};
|
||||
|
||||
struct AppRuntimeTaskDispatchPlan {
|
||||
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;
|
||||
bool reject_unsafe_cross_thread_dispatch = false;
|
||||
bool reject_stopped_worker_dispatch = false;
|
||||
};
|
||||
|
||||
struct AppAsyncRedrawPlan {
|
||||
bool set_redraw = true;
|
||||
bool notify_ui = true;
|
||||
@@ -93,6 +104,31 @@ struct AppThreadStopPlan {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppRuntimeTaskDispatchPlan plan_app_runtime_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,
|
||||
bool reject_unsafe_cross_thread_dispatch = false) noexcept
|
||||
{
|
||||
const bool queue_task = !already_on_target_thread
|
||||
&& worker_running
|
||||
&& !reject_unsafe_cross_thread_dispatch;
|
||||
return AppRuntimeTaskDispatchPlan {
|
||||
.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 && wait_for_completion,
|
||||
.request_redraw = (already_on_target_thread || queue_task) && request_redraw_after_dispatch,
|
||||
.reject_unsafe_cross_thread_dispatch = !already_on_target_thread
|
||||
&& reject_unsafe_cross_thread_dispatch,
|
||||
.reject_stopped_worker_dispatch = !already_on_target_thread && !worker_running,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppAsyncRedrawPlan plan_app_async_redraw() noexcept
|
||||
{
|
||||
return AppAsyncRedrawPlan {};
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
|
||||
#include "app.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void execute_render_worker_task(AppTask& task) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG("render worker task failed");
|
||||
}
|
||||
}
|
||||
|
||||
void execute_ui_worker_task(AppTask& task) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG("ui worker task failed");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AppRuntime::AppRuntime()
|
||||
: prepared_file_worker_([this](std::stop_token stop_token)
|
||||
{
|
||||
@@ -23,11 +51,13 @@ AppRuntime::~AppRuntime()
|
||||
|
||||
bool AppRuntime::is_render_thread() const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||
return std::this_thread::get_id() == render_thread_id_;
|
||||
}
|
||||
|
||||
bool AppRuntime::is_ui_thread() const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||
return std::this_thread::get_id() == ui_thread_id_;
|
||||
}
|
||||
|
||||
@@ -84,6 +114,174 @@ void AppRuntime::drain_main_thread_tasks()
|
||||
}
|
||||
}
|
||||
|
||||
std::future<void> AppRuntime::render_task_async(AppTask task, bool unique)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
const auto on_render_thread = is_render_thread();
|
||||
const auto running = render_running_.load(std::memory_order_acquire);
|
||||
const auto dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
on_render_thread,
|
||||
unique,
|
||||
0U,
|
||||
running,
|
||||
false,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
execute_render_worker_task(task);
|
||||
return future;
|
||||
}
|
||||
|
||||
if (!dispatch.queue_task)
|
||||
return future;
|
||||
|
||||
bool notify_worker = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||
if (!render_running_.load(std::memory_order_acquire))
|
||||
return future;
|
||||
|
||||
const auto queue_dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
false,
|
||||
unique,
|
||||
render_tasklist_.size(),
|
||||
true,
|
||||
false,
|
||||
false);
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
{
|
||||
render_tasklist_.erase(
|
||||
std::remove_if(render_tasklist_.begin(), render_tasklist_.end(),
|
||||
[id = task.task_id](AppTask const& t) { return t.task_id == id; }),
|
||||
render_tasklist_.end());
|
||||
}
|
||||
render_tasklist_.push_back(std::move(task));
|
||||
notify_worker = queue_dispatch.notify_worker;
|
||||
}
|
||||
if (notify_worker)
|
||||
render_cv_.notify_all();
|
||||
return future;
|
||||
}
|
||||
|
||||
void AppRuntime::render_task(AppTask task)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
const auto on_render_thread = is_render_thread();
|
||||
const auto running = render_running_.load(std::memory_order_acquire);
|
||||
const auto dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
on_render_thread,
|
||||
false,
|
||||
0U,
|
||||
running,
|
||||
true,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
execute_render_worker_task(task);
|
||||
}
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
bool notify_worker = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||
if (!render_running_.load(std::memory_order_acquire))
|
||||
return;
|
||||
render_tasklist_.push_back(std::move(task));
|
||||
notify_worker = dispatch.notify_worker;
|
||||
}
|
||||
if (notify_worker)
|
||||
render_cv_.notify_all();
|
||||
}
|
||||
|
||||
if (dispatch.wait_for_completion)
|
||||
future.get();
|
||||
}
|
||||
|
||||
std::future<void> AppRuntime::ui_task_async(AppTask task, bool unique)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
const auto on_ui_thread = is_ui_thread();
|
||||
const auto running = ui_running_.load(std::memory_order_acquire);
|
||||
const auto dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
on_ui_thread,
|
||||
unique,
|
||||
0U,
|
||||
running,
|
||||
false,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
execute_ui_worker_task(task);
|
||||
return future;
|
||||
}
|
||||
|
||||
if (!dispatch.queue_task)
|
||||
return future;
|
||||
|
||||
bool notify_worker = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||
if (!ui_running_.load(std::memory_order_acquire))
|
||||
return future;
|
||||
|
||||
const auto queue_dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
false,
|
||||
unique,
|
||||
ui_tasklist_.size(),
|
||||
true,
|
||||
false,
|
||||
false);
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
{
|
||||
ui_tasklist_.erase(
|
||||
std::remove_if(ui_tasklist_.begin(), ui_tasklist_.end(),
|
||||
[id = task.task_id](AppTask const& t) { return t.task_id == id; }),
|
||||
ui_tasklist_.end());
|
||||
}
|
||||
ui_tasklist_.push_back(std::move(task));
|
||||
notify_worker = queue_dispatch.notify_worker;
|
||||
}
|
||||
if (notify_worker)
|
||||
ui_cv_.notify_all();
|
||||
return future;
|
||||
}
|
||||
|
||||
void AppRuntime::ui_task(AppTask task)
|
||||
{
|
||||
auto future = task.get_future();
|
||||
const auto on_ui_thread = is_ui_thread();
|
||||
const auto running = ui_running_.load(std::memory_order_acquire);
|
||||
const auto dispatch = pp::app::plan_app_runtime_task_dispatch(
|
||||
on_ui_thread,
|
||||
false,
|
||||
0U,
|
||||
running,
|
||||
true,
|
||||
true);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
execute_ui_worker_task(task);
|
||||
}
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
bool notify_worker = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||
if (!ui_running_.load(std::memory_order_acquire))
|
||||
return;
|
||||
ui_tasklist_.push_back(std::move(task));
|
||||
notify_worker = dispatch.notify_worker;
|
||||
}
|
||||
if (notify_worker)
|
||||
ui_cv_.notify_all();
|
||||
}
|
||||
|
||||
if (dispatch.wait_for_completion)
|
||||
future.get();
|
||||
if (dispatch.request_redraw)
|
||||
request_redraw_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void AppRuntime::prepared_file_worker_main(std::stop_token stop_token)
|
||||
{
|
||||
for (;;)
|
||||
@@ -177,14 +375,17 @@ void AppRuntime::canvas_async_worker_stop()
|
||||
void AppRuntime::render_thread_tick(App& app)
|
||||
{
|
||||
static uint32_t count = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||
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;
|
||||
render_running_.store(drain_plan.mark_running, std::memory_order_release);
|
||||
if (!drain_plan.drain_tasks)
|
||||
return;
|
||||
working_list = std::move(render_tasklist_);
|
||||
@@ -196,7 +397,7 @@ void AppRuntime::render_thread_tick(App& app)
|
||||
while (!working_list.empty())
|
||||
{
|
||||
count++;
|
||||
working_list.front()();
|
||||
execute_render_worker_task(working_list.front());
|
||||
working_list.pop_front();
|
||||
}
|
||||
app.async_end();
|
||||
@@ -207,39 +408,46 @@ void AppRuntime::render_thread_main(App& app, std::stop_token stop_token)
|
||||
{
|
||||
BT_SetTerminate();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex_);
|
||||
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())
|
||||
}
|
||||
render_running_.store(pp::app::plan_app_thread_start().mark_running, std::memory_order_release);
|
||||
for (;;)
|
||||
{
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppQueueDrainPlan drain_plan;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(render_task_mutex_);
|
||||
render_cv_.wait(lock, [this, &stop_token]
|
||||
{
|
||||
return stop_token.stop_requested() || !render_running_ || !render_tasklist_.empty();
|
||||
return stop_token.stop_requested() || !render_running_.load(std::memory_order_acquire) || !render_tasklist_.empty();
|
||||
});
|
||||
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
|
||||
if (render_tasklist_.empty())
|
||||
{
|
||||
if (stop_token.stop_requested() || !render_running_.load(std::memory_order_acquire))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
working_list = std::move(render_tasklist_);
|
||||
}
|
||||
|
||||
if (drain_plan.wrap_in_render_context)
|
||||
{
|
||||
app.async_start();
|
||||
while (!working_list.empty())
|
||||
{
|
||||
working_list.front()();
|
||||
execute_render_worker_task(working_list.front());
|
||||
working_list.pop_front();
|
||||
}
|
||||
app.async_end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppRuntime::ui_thread_tick(App& app)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||
ui_thread_id_ = std::this_thread::get_id();
|
||||
}
|
||||
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppUiTickPlan tick_plan;
|
||||
@@ -247,7 +455,7 @@ void AppRuntime::ui_thread_tick(App& app)
|
||||
{
|
||||
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;
|
||||
ui_running_.store(tick_plan.mark_running, std::memory_order_release);
|
||||
working_list = std::move(ui_tasklist_);
|
||||
}
|
||||
|
||||
@@ -255,7 +463,7 @@ void AppRuntime::ui_thread_tick(App& app)
|
||||
{
|
||||
while (!working_list.empty())
|
||||
{
|
||||
working_list.front()();
|
||||
execute_ui_worker_task(working_list.front());
|
||||
working_list.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -282,8 +490,11 @@ void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token)
|
||||
{
|
||||
BT_SetTerminate();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex_);
|
||||
ui_thread_id_ = std::this_thread::get_id();
|
||||
ui_running_ = pp::app::plan_app_thread_start().mark_running;
|
||||
}
|
||||
ui_running_.store(pp::app::plan_app_thread_start().mark_running, std::memory_order_release);
|
||||
|
||||
app.attach_ui_thread();
|
||||
|
||||
@@ -295,7 +506,7 @@ void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token)
|
||||
float t_fps_counter = 0;
|
||||
float t_reloader = 0;
|
||||
int rendered_frames = 0;
|
||||
while (ui_running_ && !stop_token.stop_requested())
|
||||
for (;;)
|
||||
{
|
||||
std::deque<AppTask> working_list;
|
||||
|
||||
@@ -303,16 +514,24 @@ void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token)
|
||||
std::unique_lock<std::mutex> 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();
|
||||
return stop_token.stop_requested() || !ui_running_.load(std::memory_order_acquire) || !ui_tasklist_.empty();
|
||||
});
|
||||
if (ui_tasklist_.empty())
|
||||
{
|
||||
if (stop_token.stop_requested() || !ui_running_.load(std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
working_list = std::move(ui_tasklist_);
|
||||
}
|
||||
}
|
||||
|
||||
if (!working_list.empty())
|
||||
{
|
||||
while (!working_list.empty())
|
||||
{
|
||||
working_list.front()();
|
||||
execute_ui_worker_task(working_list.front());
|
||||
working_list.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -383,14 +602,14 @@ void AppRuntime::render_thread_start(App& app)
|
||||
{
|
||||
render_thread_main(app, stop_token);
|
||||
});
|
||||
render_running_ = plan.mark_running;
|
||||
render_running_.store(plan.mark_running, std::memory_order_release);
|
||||
}
|
||||
|
||||
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;
|
||||
render_running_.store(false, std::memory_order_release);
|
||||
if (plan.join_thread)
|
||||
render_thread_.request_stop();
|
||||
if (plan.notify_worker)
|
||||
@@ -407,14 +626,14 @@ void AppRuntime::ui_thread_start(App& app)
|
||||
{
|
||||
ui_thread_main(app, stop_token);
|
||||
});
|
||||
ui_running_ = plan.mark_running;
|
||||
ui_running_.store(plan.mark_running, std::memory_order_release);
|
||||
}
|
||||
|
||||
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;
|
||||
ui_running_.store(false, std::memory_order_release);
|
||||
if (plan.join_thread)
|
||||
ui_thread_.request_stop();
|
||||
if (plan.notify_worker)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "app_core/app_thread.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
@@ -39,8 +40,8 @@ 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; }
|
||||
[[nodiscard]] bool request_redraw() const noexcept { return request_redraw_.load(std::memory_order_acquire); }
|
||||
void clear_request_redraw() noexcept { request_redraw_.store(false, std::memory_order_release); }
|
||||
|
||||
void notify_render_worker() noexcept;
|
||||
void notify_ui_worker() noexcept;
|
||||
@@ -49,6 +50,11 @@ public:
|
||||
void main_thread_task(std::packaged_task<void()> task);
|
||||
void drain_main_thread_tasks();
|
||||
|
||||
std::future<void> render_task_async(AppTask task, bool unique = false);
|
||||
void render_task(AppTask task);
|
||||
std::future<void> ui_task_async(AppTask task, bool unique = false);
|
||||
void ui_task(AppTask task);
|
||||
|
||||
void render_thread_tick(App& app);
|
||||
void render_thread_main(App& app, std::stop_token stop_token);
|
||||
void render_thread_start(App& app);
|
||||
@@ -62,68 +68,13 @@ public:
|
||||
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;
|
||||
return render_task_async(AppTask(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();
|
||||
render_task(AppTask(std::move(task)));
|
||||
}
|
||||
|
||||
void render_sync()
|
||||
@@ -134,70 +85,13 @@ public:
|
||||
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;
|
||||
return ui_task_async(AppTask(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)
|
||||
request_redraw_ = true;
|
||||
ui_task(AppTask(std::move(task)));
|
||||
}
|
||||
|
||||
void ui_sync()
|
||||
@@ -227,17 +121,17 @@ private:
|
||||
std::mutex main_thread_task_mutex_;
|
||||
|
||||
std::deque<AppTask> render_tasklist_;
|
||||
std::mutex render_task_mutex_;
|
||||
mutable std::mutex render_task_mutex_;
|
||||
std::condition_variable render_cv_;
|
||||
std::jthread render_thread_;
|
||||
std::thread::id render_thread_id_;
|
||||
bool render_running_ = false;
|
||||
std::atomic_bool render_running_ = false;
|
||||
|
||||
std::deque<AppTask> ui_tasklist_;
|
||||
std::mutex ui_task_mutex_;
|
||||
mutable std::mutex ui_task_mutex_;
|
||||
std::condition_variable ui_cv_;
|
||||
std::jthread ui_thread_;
|
||||
std::thread::id ui_thread_id_;
|
||||
bool ui_running_ = false;
|
||||
bool request_redraw_ = false;
|
||||
std::atomic_bool ui_running_ = false;
|
||||
std::atomic_bool request_redraw_ = false;
|
||||
};
|
||||
|
||||
@@ -230,7 +230,7 @@ void App::async_redraw()
|
||||
if (plan.set_redraw)
|
||||
redraw = true;
|
||||
if (plan.notify_ui)
|
||||
runtime_.notify_ui_worker();
|
||||
runtime().notify_ui_worker();
|
||||
}
|
||||
|
||||
void App::async_end()
|
||||
@@ -370,5 +370,5 @@ void App::rec_loop()
|
||||
|
||||
void App::render_thread_tick()
|
||||
{
|
||||
runtime_.render_thread_tick(*this);
|
||||
runtime().render_thread_tick(*this);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "canvas.h"
|
||||
#include "app.h"
|
||||
#include "legacy_canvas_draw_merge_services.h"
|
||||
#include "legacy_document_export_services.h"
|
||||
#include "legacy_ui_gl_dispatch.h"
|
||||
#include "legacy_ui_overlay_services.h"
|
||||
#include "app_core/document_canvas.h"
|
||||
@@ -190,40 +191,14 @@ void Canvas::import_equirectangular_thread(std::string file_path, std::shared_pt
|
||||
|
||||
void Canvas::export_equirectangular(std::string file_path, std::function<void()> on_complete)
|
||||
{
|
||||
if (App::I->check_license())
|
||||
{
|
||||
App::I->runtime().canvas_async_task([this, file_path = std::move(file_path), on_complete = std::move(on_complete)]() mutable {
|
||||
BT_SetTerminate();
|
||||
export_equirectangular_thread(file_path);
|
||||
if (on_complete)
|
||||
App::I->ui_task([on_complete = std::move(on_complete)]() mutable { on_complete(); });
|
||||
});
|
||||
}
|
||||
pp::panopainter::execute_legacy_document_export_equirectangular(
|
||||
std::move(file_path),
|
||||
std::move(on_complete));
|
||||
}
|
||||
|
||||
void Canvas::export_equirectangular_thread(std::string file_path)
|
||||
{
|
||||
Image data;
|
||||
|
||||
App::I->render_task([&]
|
||||
{
|
||||
draw_merge(false);
|
||||
Texture2D equirect = m_layers_merge.gen_equirect();
|
||||
data = equirect.get_image();
|
||||
});
|
||||
|
||||
LOG("writing %s", file_path.c_str());
|
||||
if (file_path.substr(file_path.size() - 4) == ".jpg")
|
||||
{
|
||||
data.save_jpg(file_path, 100);
|
||||
inject_xmp(file_path);
|
||||
}
|
||||
else if (file_path.substr(file_path.size() - 4) == ".png")
|
||||
{
|
||||
data.save_png(file_path);
|
||||
}
|
||||
|
||||
App::I->publish_exported_image(file_path);
|
||||
pp::panopainter::execute_legacy_document_export_equirectangular_thread(std::move(file_path));
|
||||
}
|
||||
|
||||
void Canvas::inject_xmp(std::string jpg_path)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "legacy_document_export_services.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "canvas.h"
|
||||
#include "legacy_document_canvas_services.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
|
||||
@@ -130,6 +131,27 @@ pp::foundation::Status write_export_binary_file(std::string_view path, std::span
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void execute_legacy_document_export_equirectangular_thread_impl(std::string file_path)
|
||||
{
|
||||
Image data;
|
||||
|
||||
App::I->render_task([&] {
|
||||
Canvas::I->draw_merge(false);
|
||||
Texture2D equirect = Canvas::I->m_layers_merge.gen_equirect();
|
||||
data = equirect.get_image();
|
||||
});
|
||||
|
||||
LOG("writing %s", file_path.c_str());
|
||||
if (pp::app::document_export_path_is_jpeg_target(file_path)) {
|
||||
data.save_jpg(file_path, 100);
|
||||
Canvas::I->inject_xmp(file_path);
|
||||
} else if (pp::app::document_export_path_is_png_target(file_path)) {
|
||||
data.save_png(file_path);
|
||||
}
|
||||
|
||||
App::I->publish_exported_image(file_path);
|
||||
}
|
||||
|
||||
class LegacyExportWriteServices final
|
||||
: public pp::app::DocumentCubeFaceExportWriteServices
|
||||
, public pp::app::DocumentDepthExportWriteServices
|
||||
@@ -868,6 +890,28 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
void execute_legacy_document_export_equirectangular(
|
||||
std::string file_path,
|
||||
std::function<void()> on_complete)
|
||||
{
|
||||
if (App::I->check_license()) {
|
||||
App::I->runtime().canvas_async_task([file_path = std::move(file_path), on_complete = std::move(on_complete)]() mutable {
|
||||
BT_SetTerminate();
|
||||
execute_legacy_document_export_equirectangular_thread_impl(file_path);
|
||||
if (on_complete) {
|
||||
App::I->ui_task([on_complete = std::move(on_complete)]() mutable {
|
||||
on_complete();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void execute_legacy_document_export_equirectangular_thread(std::string file_path)
|
||||
{
|
||||
execute_legacy_document_export_equirectangular_thread_impl(std::move(file_path));
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_legacy_document_export_file(
|
||||
App& app,
|
||||
const pp::app::DocumentExportFileTarget& target)
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
class App;
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace pp::panopainter {
|
||||
|
||||
[[nodiscard]] pp::foundation::Status execute_legacy_document_export_file(
|
||||
@@ -35,4 +38,10 @@ namespace pp::panopainter {
|
||||
std::string_view path,
|
||||
bool asynchronous);
|
||||
|
||||
void execute_legacy_document_export_equirectangular(
|
||||
std::string file_path,
|
||||
std::function<void()> on_complete);
|
||||
|
||||
void execute_legacy_document_export_equirectangular_thread(std::string file_path);
|
||||
|
||||
} // namespace pp::panopainter
|
||||
|
||||
@@ -38,6 +38,33 @@ void task_dispatch_does_not_wait_for_stopped_worker(pp::tests::Harness& harness)
|
||||
PP_EXPECT(harness, !plan.wait_for_completion);
|
||||
}
|
||||
|
||||
void runtime_task_dispatch_rejects_stopped_worker_enqueue(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_runtime_task_dispatch(false, true, 2, false, 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);
|
||||
PP_EXPECT(harness, !plan.reject_unsafe_cross_thread_dispatch);
|
||||
PP_EXPECT(harness, plan.reject_stopped_worker_dispatch);
|
||||
}
|
||||
|
||||
void runtime_task_dispatch_executes_immediately_on_target_thread(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_runtime_task_dispatch(true, true, 3, false, 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);
|
||||
PP_EXPECT(harness, !plan.reject_stopped_worker_dispatch);
|
||||
}
|
||||
|
||||
void task_dispatch_rejects_unsafe_cross_thread_mutations(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_task_dispatch(false, true, 2, true, true, false, true);
|
||||
@@ -137,6 +164,8 @@ int main()
|
||||
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("runtime task dispatch rejects stopped worker enqueue", runtime_task_dispatch_rejects_stopped_worker_enqueue);
|
||||
harness.run("runtime task dispatch executes immediately on target thread", runtime_task_dispatch_executes_immediately_on_target_thread);
|
||||
harness.run("task dispatch can reject unsafe cross-thread mutations", task_dispatch_rejects_unsafe_cross_thread_mutations);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user