Thin app dialog and renderer adapter ownership

This commit is contained in:
2026-06-17 18:43:43 +02:00
parent dd638e5af4
commit 1a64118b2c
11 changed files with 181 additions and 178 deletions

View File

@@ -94,6 +94,8 @@ set(PP_LEGACY_APP_SOURCES
src/legacy_canvas_mode_transform.cpp
src/legacy_app_shell_services.cpp
src/legacy_app_shell_services.h
src/legacy_app_shell_performance_test_services.cpp
src/legacy_app_shell_performance_test_services.h
src/legacy_canvas_tool_services.cpp
src/legacy_canvas_tool_services.h
src/legacy_canvas_view_services.cpp

View File

@@ -18,6 +18,26 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions
- 2026-06-17: `DEBT-0036` was narrowed again. `src/canvas_layer.cpp` now
routes retained `Layer` / `LayerFrame` render queueing through
`src/renderer_gl/render_runtime_dispatch.h` instead of direct
`App::I->render_task*` calls; retained `Canvas::I` draw-state use and the
broader canvas renderer execution remain.
- 2026-06-17: `DEBT-0034` was narrowed again.
`LegacyAboutMenuServices::run_performance_test(...)` in
`src/legacy_app_shell_services.cpp` now delegates the retained stroke/timing
workflow to `src/legacy_app_shell_performance_test_services.*`, so the About
menu adapter no longer owns the inline render-task/canvas execution body.
- 2026-06-17: `DEBT-0040`/`DEBT-0042` were narrowed again. `App::dialog_save()`
and `App::dialog_save_ver()` in `src/app_dialogs_workflow.cpp` now delegate
Save As / Save Version acceptance through
`src/legacy_document_session_services.*`, so the app dialog shell no longer
owns the retained plan/target lookup entrypoints for those save paths.
- 2026-06-17: `DEBT-0047` was narrowed again. The desktop async PPBR export
path in `src/legacy_brush_package_export_services.cpp` now uses
`AppRuntime::canvas_async_task` plus named completion helpers instead of a
file-static worker singleton, while retained preset-panel execution and
dialog-owned request extraction still remain.
- 2026-06-17: `DEBT-0036` was narrowed again. Retained `Shape`,
`src/shader.cpp`, and `src/font.cpp` now queue render-thread create/update/
destroy work through `src/renderer_gl/render_runtime_dispatch.h` instead of

View File

@@ -53,9 +53,9 @@ Key facts:
- `Canvas::I` still appears hundreds of times in retained canvas modes, panels,
and workflow bridges.
- Raw `Node*` and callback captures remain a dominant UI lifetime risk.
- `CanvasLayer` and retained stroke-preview/runtime draw paths still depend on
legacy render/runtime helpers, but `RTT`, `Texture2D`, `Shape`, `Shader`,
and `TextMesh` no longer call `App::I` directly for queueing.
- Retained stroke-preview/runtime draw paths still depend on legacy
render/runtime helpers, but `RTT`, `Texture2D`, `Shape`, `Shader`,
`TextMesh`, and `CanvasLayer` no longer call `App::I` directly for queueing.
- `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.

View File

@@ -43,21 +43,7 @@ void wire_document_save_dialog_buttons(
{
dialog->btn_ok->on_click = [&app, dialog](Node*)
{
std::string name = dialog->input->m_text;
const auto plan = pp::app::plan_document_file_save(
app.work_path,
name,
[](const std::string& path) {
return Asset::exist(path);
});
if (!plan)
{
app.message_box("Warning", "You need to specify a name to file.");
return;
}
const auto status =
pp::panopainter::execute_legacy_document_file_save_plan(app, plan.value(), dialog);
const auto status = pp::panopainter::execute_legacy_document_file_save_dialog(app, dialog);
if (!status.ok())
LOG("Document file save action failed: %s", status.message);
};
@@ -67,24 +53,6 @@ void wire_document_save_dialog_buttons(
};
}
void save_document_version(App& app)
{
const auto target = pp::app::find_next_document_version_target(
app.doc_dir,
app.doc_name,
[](const std::string& path) {
return Asset::exist(path);
});
if (!target) {
app.message_box("Saving Error", target.status().message);
return;
}
const auto status = pp::panopainter::execute_legacy_document_version_save(app, target.value());
if (!status.ok())
LOG("Document version save action failed: %s", status.message);
}
} // namespace
void App::continue_document_workflow_after_optional_save(std::function<void()> action)
@@ -247,7 +215,9 @@ void App::dialog_save_ver()
return;
}
save_document_version(*this);
const auto status = pp::panopainter::execute_legacy_document_version_save_dialog(*this);
if (!status.ok())
LOG("Document version save action failed: %s", status.message);
}
void App::save_document(pp::app::DocumentSaveIntent intent)

View File

@@ -5,6 +5,7 @@
#include "legacy_ui_gl_dispatch.h"
#include "log.h"
#include "renderer_gl/opengl_capabilities.h"
#include "renderer_gl/render_runtime_dispatch.h"
#include "rtt.h"
#include "util.h"
@@ -105,7 +106,7 @@ TextureCube Layer::gen_cube()
ret.create(w);
for (int i = 0; i < 6; i++)
{
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
ret.bind();
rtt(i).bindFramebuffer();
@@ -120,7 +121,7 @@ Texture2D Layer::gen_equirect(glm::ivec2 size /*= { 0, 0 }*/)
{
Texture2D ret;
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
gl_state gl;
gl.save();
@@ -177,7 +178,7 @@ PBO Layer::gen_equirect_pbo(glm::ivec2 size /*= { 0, 0 }*/)
latlong.create(size.x, size.y);
std::this_thread::yield();
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
apply_layer_capability(pp::renderer::gl::blend_state(), false);
@@ -317,7 +318,7 @@ void Layer::duplicate_frame(int frame)
void Layer::frames_gpu_update()
{
App::I->render_task_async([=]
pp::renderer::gl::render_runtime_dispatch().render_task_async([=]
{
for (int j = 0; j < m_frames.size(); j++)
{
@@ -471,22 +472,6 @@ LayerFrame& LayerFrame::operator=(LayerFrame&& other)
bool LayerFrame::create(int width, int height, int duration /*= 1*/)
{
bool success = true;
//App::I->render_task([&]
//{
// for (int i = 0; i < 6; i++)
// {
// if (!m_rtt[i].create(width, height))
// {
// success = false;
// return;
// }
// m_rtt[i].bindFramebuffer();
// m_rtt[i].clear();
// m_rtt[i].unbindFramebuffer();
// m_dirty_box[i] = glm::vec4(width, height, 0, 0); // reset bounding box
// m_dirty_face[i] = false;
// }
//});
for (int i = 0; i < 6; i++)
{
m_dirty_box[i] = glm::vec4(width, height, 0, 0); // reset bounding box
@@ -517,7 +502,7 @@ bool LayerFrame::resize(int width, int height)
void LayerFrame::clear(const glm::vec4& c)
{
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
// push clear color state
const auto cc = query_layer_clear_color();
@@ -566,7 +551,7 @@ LayerFrame LayerFrame::clone() noexcept
void LayerFrame::restore(const Snapshot& snap)
{
App::I->render_task([this, &snap]
pp::renderer::gl::render_runtime_dispatch().render_task([this, &snap]
{
clear({ 0, 0, 0, 0 });
for (int i = 0; i < 6; i++)
@@ -605,7 +590,7 @@ LayerFrame::Snapshot LayerFrame::snapshot(std::array<glm::vec4, 6>* dirty_box /*
Snapshot snap;
snap.width = w;
snap.height = h;
App::I->render_task([this, &snap, dirty_face, dirty_box]
pp::renderer::gl::render_runtime_dispatch().render_task([this, &snap, dirty_face, dirty_box]
{
for (int i = 0; i < 6; i++)
{

View File

@@ -0,0 +1,43 @@
#include "pch.h"
#include "legacy_app_shell_performance_test_services.h"
#include "app.h"
#include "canvas.h"
namespace pp::panopainter {
std::string run_legacy_about_menu_performance_test(
App& app,
Canvas& canvas,
int performance_iterations)
{
std::string message;
app.render_task([&]
{
const auto start = std::chrono::high_resolution_clock::now();
canvas.stroke_start({ 0, 0, 0 }, 0.9f);
for (int i = 0; i < performance_iterations; ++i)
{
canvas.stroke_update({ 100, 100, 0 }, 0.9f);
canvas.stroke_update({ 200, 200, 0 }, 0.9f);
canvas.stroke_update({ 200, 100, 0 }, 0.9f);
canvas.stroke_update({ 100, 200, 0 }, 0.9f);
canvas.stroke_update({ 300, 300, 0 }, 0.9f);
canvas.stroke_update({ 200, 500, 0 }, 0.9f);
canvas.stroke_update({ 500, 500, 0 }, 0.9f);
canvas.stroke_update({ 400, 400, 0 }, 0.9f);
canvas.stroke_update({ 0, 200, 0 }, 0.9f);
canvas.stroke_update({ 200, 0, 0 }, 0.9f);
canvas.stroke_draw();
}
canvas.stroke_end();
const auto diff = std::chrono::high_resolution_clock::now() - start;
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
LOG("%lld ms", static_cast<long long>(ms));
message = "Time " + std::to_string(ms) + " ms";
});
return message;
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,15 @@
#pragma once
#include <string>
class App;
class Canvas;
namespace pp::panopainter {
[[nodiscard]] std::string run_legacy_about_menu_performance_test(
App& app,
Canvas& canvas,
int performance_iterations);
} // namespace pp::panopainter

View File

@@ -5,6 +5,7 @@
#include "app.h"
#include "app_core/document_import.h"
#include "legacy_app_dialog_services.h"
#include "legacy_app_shell_performance_test_services.h"
#include "legacy_canvas_view_services.h"
#include "legacy_document_image_import_services.h"
#include "legacy_document_canvas_services.h"
@@ -217,36 +218,14 @@ public:
void run_performance_test(const pp::app::AboutMenuPlan& plan) override
{
if (!Canvas::I)
if (!app_.canvas || !app_.canvas->m_canvas)
return;
LOG("perf");
std::string message;
const int performance_iterations = plan.performance_iterations;
app_.render_task([&]
{
auto start = std::chrono::high_resolution_clock::now();
Canvas::I->stroke_start({ 0, 0, 0 }, 0.9f);
for (int i = 0; i < performance_iterations; i++)
{
Canvas::I->stroke_update({ 100, 100, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 100, 0 }, 0.9f);
Canvas::I->stroke_update({ 100, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 300, 300, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 500, 0 }, 0.9f);
Canvas::I->stroke_update({ 500, 500, 0 }, 0.9f);
Canvas::I->stroke_update({ 400, 400, 0 }, 0.9f);
Canvas::I->stroke_update({ 0, 200, 0 }, 0.9f);
Canvas::I->stroke_update({ 200, 0, 0 }, 0.9f);
Canvas::I->stroke_draw();
}
Canvas::I->stroke_end();
auto diff = std::chrono::high_resolution_clock::now() - start;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
LOG("%lld ms", ms);
message = "Time " + std::to_string(ms) + " ms";
});
const auto message = run_legacy_about_menu_performance_test(
app_,
*app_.canvas->m_canvas,
plan.performance_iterations);
app_.message_box("Performance test", message);
}

View File

@@ -7,88 +7,44 @@
#include "node_dialog_export_ppbr.h"
#include "node_panel_brush.h"
#include <condition_variable>
#include <deque>
#include <functional>
#include <string>
#include <mutex>
#include <stop_token>
#include <thread>
namespace pp::panopainter {
namespace {
class LegacyBrushPackageWorker final {
public:
LegacyBrushPackageWorker()
: worker_([this](std::stop_token stop_token) {
run(stop_token);
})
void queue_legacy_brush_package_export_success_prompt(
App& app,
std::shared_ptr<NodeDialogExportPPBR> dialog,
std::string path)
{
const auto plan = pp::app::plan_brush_package_export_success_dialog(path);
app.ui_task([dialog = std::move(dialog), plan] {
if (dialog) {
pp::panopainter::close_legacy_dialog_node(*dialog);
}
~LegacyBrushPackageWorker()
{
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();
App::I->message_box(plan.title, plan.message, plan.show_cancel);
});
if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
break;
task = std::move(tasks_.front());
tasks_.pop_front();
}
if (task) {
try {
task();
} catch (...) {
LOG("brush package export worker task failed");
}
}
}
}
std::mutex mutex_;
std::condition_variable cv_;
std::deque<std::function<void()>> tasks_;
bool stopping_ = false;
std::jthread worker_;
};
LegacyBrushPackageWorker& brush_package_worker()
void queue_legacy_brush_package_export_job(
App& app,
std::shared_ptr<NodeDialogExportPPBR> dialog,
std::shared_ptr<NodePanelBrushPreset> presets,
std::string path_string,
NodePanelBrushPreset::PPBRInfo info)
{
static LegacyBrushPackageWorker worker;
return worker;
app.runtime().canvas_async_task([app = &app,
presets = std::move(presets),
dialog = std::move(dialog),
path_string = std::move(path_string),
info = std::move(info)]() mutable {
BT_SetTerminate();
if (presets) {
presets->export_ppbr(path_string, info);
}
queue_legacy_brush_package_export_success_prompt(*app, std::move(dialog), std::move(path_string));
});
}
NodePanelBrushPreset::PPBRInfo to_legacy_ppbr_info(
@@ -124,21 +80,13 @@ public:
const auto info = to_legacy_ppbr_info(request, dialog_);
auto presets = app_.presets;
if (mode_ == LegacyBrushPackageExportMode::desktop_async_close_and_message) {
auto* app = &app_;
auto dialog = std::static_pointer_cast<NodeDialogExportPPBR>(dialog_.shared_from_this());
brush_package_worker().post([app, presets, dialog, path_string, info] {
BT_SetTerminate();
if (presets) {
presets->export_ppbr(path_string, info);
}
const auto plan = pp::app::plan_brush_package_export_success_dialog(path_string);
app->ui_task([dialog, plan] {
if (dialog) {
pp::panopainter::close_legacy_dialog_node(*dialog);
}
App::I->message_box(plan.title, plan.message, plan.show_cancel);
});
});
queue_legacy_brush_package_export_job(
app_,
std::move(dialog),
std::move(presets),
path_string,
info);
return;
}

View File

@@ -231,7 +231,7 @@ private:
std::shared_ptr<NodeDialogSave> dialog_;
};
void save_legacy_document_version(App& app, const pp::app::DocumentVersionTarget& target);
pp::foundation::Status save_legacy_document_version(App& app, const pp::app::DocumentVersionTarget& target);
class LegacyDocumentVersionSaveServices final : public pp::app::DocumentVersionSaveServices {
public:
@@ -249,7 +249,7 @@ private:
App& app_;
};
void save_legacy_document_version(App& app, const pp::app::DocumentVersionTarget& target)
pp::foundation::Status save_legacy_document_version(App& app, const pp::app::DocumentVersionTarget& target)
{
const auto history_status = pp::panopainter::execute_legacy_history_plan(
pp::app::plan_document_version_save_history(target));
@@ -261,6 +261,7 @@ void save_legacy_document_version(App& app, const pp::app::DocumentVersionTarget
app.canvas->m_canvas->m_unsaved = true;
app.title_update();
project_save_after_snapshot(app, app.doc_path);
return pp::foundation::Status::success();
}
class LegacyCloseRequestServices final : public pp::app::CloseRequestServices {
@@ -450,6 +451,24 @@ pp::foundation::Status execute_legacy_document_file_save_plan(
return pp::app::execute_document_file_save_plan(plan, services);
}
pp::foundation::Status execute_legacy_document_file_save_dialog(
App& app,
std::shared_ptr<NodeDialogSave> dialog)
{
const auto plan = pp::app::plan_document_file_save(
app.work_path,
dialog->input->m_text,
[](const std::string& path) {
return Asset::exist(path);
});
if (!plan) {
app.message_box("Warning", "You need to specify a name to file.");
return pp::foundation::Status::success();
}
return execute_legacy_document_file_save_plan(app, plan.value(), std::move(dialog));
}
pp::foundation::Status execute_legacy_document_version_save(
App& app,
const pp::app::DocumentVersionTarget& target)
@@ -458,6 +477,22 @@ pp::foundation::Status execute_legacy_document_version_save(
return pp::app::execute_document_version_save(target, services);
}
pp::foundation::Status execute_legacy_document_version_save_dialog(App& app)
{
const auto target = pp::app::find_next_document_version_target(
app.doc_dir,
app.doc_name,
[](const std::string& path) {
return Asset::exist(path);
});
if (!target) {
app.message_box("Saving Error", target.status().message);
return pp::foundation::Status::success();
}
return execute_legacy_document_version_save(app, target.value());
}
void execute_legacy_document_save_before_cloud_upload(App& app)
{
Canvas::I->project_save_thread(app.doc_path, true);

View File

@@ -36,10 +36,16 @@ namespace pp::panopainter {
const pp::app::DocumentFileSavePlan& plan,
std::shared_ptr<NodeDialogSave> dialog);
[[nodiscard]] pp::foundation::Status execute_legacy_document_file_save_dialog(
App& app,
std::shared_ptr<NodeDialogSave> dialog);
[[nodiscard]] pp::foundation::Status execute_legacy_document_version_save(
App& app,
const pp::app::DocumentVersionTarget& target);
[[nodiscard]] pp::foundation::Status execute_legacy_document_version_save_dialog(App& app);
void execute_legacy_document_save_before_cloud_upload(App& app);
} // namespace pp::panopainter