Own preview and recording workers

This commit is contained in:
2026-06-16 07:18:08 +02:00
parent 4d7a23a1fd
commit 3366b54c7f
8 changed files with 78 additions and 51 deletions

File diff suppressed because one or more lines are too long

View File

@@ -106,9 +106,9 @@ Current architecture mismatches that must be treated as real blockers:
- `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files - `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files
rather than thin composition/binding surfaces. rather than thin composition/binding surfaces.
- `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use - `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use
global singleton reach, raw observer pointers, detached `std::thread` global singleton reach, raw observer pointers, retained static worker
launches in preview/recording paths, and ad hoc ownership in several app families, and ad hoc mutex/condition-variable
mutex/condition-variable ownership. ownership.
- Modern C++23 usage exists in extracted components, especially `std::span`, - Modern C++23 usage exists in extracted components, especially `std::span`,
explicit result/status objects, and a few concepts, but the live app still explicit result/status objects, and a few concepts, but the live app still
does not consistently express ownership, thread affinity, or renderer does not consistently express ownership, thread affinity, or renderer

View File

@@ -49,7 +49,9 @@ Completed, blocked, and superseded task history moved to
- `platform_legacy` is still part of the live app shell - `platform_legacy` is still part of the live app shell
- The app runtime boundary is not finished: - The app runtime boundary is not finished:
- render/UI queues are static `App` state - render/UI queues are static `App` state
- detached workers still launch from preview and recording code - app-facing detached launches are no longer the main issue; preview and
recording now use owned worker threads, but those families still rely on
retained static/global ownership and ad hoc runtime control
- canvas async import/export/save/open now run through an owned in-file - canvas async import/export/save/open now run through an owned in-file
worker, but their retained progress execution is still not a clean runtime worker, but their retained progress execution is still not a clean runtime
service boundary service boundary
@@ -359,7 +361,8 @@ Current slice:
workers with explicit UI-thread handoff workers with explicit UI-thread handoff
- canvas async import/export/save/open and timelapse export now also use owned - canvas async import/export/save/open and timelapse export now also use owned
worker queues instead of detached threads worker queues instead of detached threads
- preview and recording-side detached work are still open - preview background rendering and recording thread ownership now also use
`std::jthread`, but their retained loop/control flow is still open
Write scope: Write scope:
- `src/canvas.cpp` - `src/canvas.cpp`

View File

@@ -85,7 +85,7 @@ public:
std::string work_path{ "." }; std::string work_path{ "." };
std::string rec_path{ "." }; std::string rec_path{ "." };
std::string tmp_path{ "." }; std::string tmp_path{ "." };
std::thread rec_thread; std::jthread rec_thread;
bool rec_running = false; bool rec_running = false;
int rec_count = 0; int rec_count = 0;
std::mutex rec_mutex; std::mutex rec_mutex;

View File

@@ -21,11 +21,12 @@ public:
void start_thread() override void start_thread() override
{ {
app_.update_rec_frames(); app_.update_rec_frames();
app_.rec_thread = std::thread(&App::rec_loop, &app_); app_.rec_thread = std::jthread(&App::rec_loop, &app_);
} }
void stop_thread() override void stop_thread() override
{ {
app_.rec_thread.request_stop();
app_.rec_running = false; app_.rec_running = false;
app_.rec_cv.notify_all(); app_.rec_cv.notify_all();
if (app_.rec_thread.joinable()) if (app_.rec_thread.joinable())

View File

@@ -19,6 +19,7 @@
#include "util.h" #include "util.h"
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <stop_token>
namespace { namespace {
@@ -410,7 +411,6 @@ void execute_stroke_preview_background_capture_pass(
std::atomic_int NodeStrokePreview::s_instances{ 0 }; std::atomic_int NodeStrokePreview::s_instances{ 0 };
std::atomic_bool NodeStrokePreview::s_running{ false }; std::atomic_bool NodeStrokePreview::s_running{ false };
std::mutex NodeStrokePreview::s_render_mutex; std::mutex NodeStrokePreview::s_render_mutex;
std::thread NodeStrokePreview::s_renderer;
BlockingQueue<std::shared_ptr<NodeStrokePreview>> NodeStrokePreview::s_queue; BlockingQueue<std::shared_ptr<NodeStrokePreview>> NodeStrokePreview::s_queue;
RTT NodeStrokePreview::m_rtt; RTT NodeStrokePreview::m_rtt;
@@ -422,16 +422,18 @@ Sampler NodeStrokePreview::m_sampler_linear;
Sampler NodeStrokePreview::m_sampler_linear_repeat; Sampler NodeStrokePreview::m_sampler_linear_repeat;
Sampler NodeStrokePreview::m_sampler_mipmap; Sampler NodeStrokePreview::m_sampler_mipmap;
DynamicShape NodeStrokePreview::m_brush_shape; DynamicShape NodeStrokePreview::m_brush_shape;
std::jthread NodeStrokePreview::s_renderer;
void NodeStrokePreview::terminate_renderer() void NodeStrokePreview::terminate_renderer()
{ {
if (s_running && s_renderer.joinable()) if (!s_renderer.joinable())
{ return;
s_running = false;
s_queue.UnlockGetters(); s_running = false;
s_renderer.join(); s_renderer.request_stop();
} s_queue.UnlockGetters();
s_renderer.join();
} }
void NodeStrokePreview::empty_queue() void NodeStrokePreview::empty_queue()
@@ -906,11 +908,11 @@ void NodeStrokePreview::draw_stroke()
{ {
if (m_size.x == 0 || m_size.y == 0) if (m_size.x == 0 || m_size.y == 0)
return; return;
s_queue.mutex.lock(); std::unique_lock<std::mutex> queue_lock(s_queue.mutex);
if (!s_running) if (!s_renderer.joinable())
{ {
s_running = true; s_running = true;
s_renderer = std::thread([] { s_renderer = std::jthread([](std::stop_token stop_token) {
BT_SetTerminate(); BT_SetTerminate();
m_sampler_linear.create(); m_sampler_linear.create();
@@ -922,7 +924,7 @@ void NodeStrokePreview::draw_stroke()
pp::renderer::gl::linear_mipmap_linear_texture_filter(), pp::renderer::gl::linear_mipmap_linear_texture_filter(),
pp::renderer::gl::linear_texture_filter()); pp::renderer::gl::linear_texture_filter());
m_brush_shape.create(); m_brush_shape.create();
while (s_running) while (s_running && !stop_token.stop_requested())
{ {
auto node = s_queue.Get(); auto node = s_queue.Get();
if (node) if (node)
@@ -970,9 +972,10 @@ void NodeStrokePreview::draw_stroke()
m_tex_dual.destroy(); m_tex_dual.destroy();
m_tex_background.destroy(); m_tex_background.destroy();
m_brush_shape.destroy(); m_brush_shape.destroy();
s_running = false;
}); });
} }
s_queue.mutex.unlock(); queue_lock.unlock();
s_queue.PostUnique(std::static_pointer_cast<NodeStrokePreview>(shared_from_this()), m_draw_first); s_queue.PostUnique(std::static_pointer_cast<NodeStrokePreview>(shared_from_this()), m_draw_first);
} }

View File

@@ -1,4 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <thread>
#include "node_border.h" #include "node_border.h"
#include "rtt.h" #include "rtt.h"
#include "brush.h" #include "brush.h"
@@ -33,7 +35,7 @@ public:
static std::atomic_int s_instances; static std::atomic_int s_instances;
static std::atomic_bool s_running; static std::atomic_bool s_running;
static std::mutex s_render_mutex; static std::mutex s_render_mutex;
static std::thread s_renderer; static std::jthread s_renderer;
static BlockingQueue<std::shared_ptr<NodeStrokePreview>> s_queue; static BlockingQueue<std::shared_ptr<NodeStrokePreview>> s_queue;
static void terminate_renderer(); static void terminate_renderer();
static void empty_queue(); static void empty_queue();

View File

@@ -108,84 +108,87 @@ public:
[[nodiscard]] pp::platform::apple::AppleDocumentPlatformServices& active_apple_document_platform_services() [[nodiscard]] pp::platform::apple::AppleDocumentPlatformServices& active_apple_document_platform_services()
{ {
#ifdef __IOS__ #ifdef __IOS__
auto* const ios_view = App::I->ios_view;
static pp::platform::apple::AppleDocumentPlatformServices services( static pp::platform::apple::AppleDocumentPlatformServices services(
pp::platform::PlatformFamily::ios, pp::platform::PlatformFamily::ios,
[] { [ios_view] {
pp::platform::apple::AppleDocumentPickerBridge bridge; pp::platform::apple::AppleDocumentPickerBridge bridge;
bridge.clipboard_text = [] { bridge.clipboard_text = [ios_view] {
return [App::I->ios_view clipboard_get_string]; return [ios_view clipboard_get_string];
}; };
bridge.set_clipboard_text = [](std::string_view text) { bridge.set_clipboard_text = [ios_view](std::string_view text) {
const std::string value(text); const std::string value(text);
return [App::I->ios_view clipboard_set_string:value]; return [ios_view clipboard_set_string:value];
}; };
bridge.display_file = [](std::string path) { bridge.display_file = [ios_view](std::string path) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view display_file:path]; [ios_view display_file:path];
}); });
}; };
bridge.share_file = [](std::string path) { bridge.share_file = [ios_view](std::string path) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view share_file:[NSString stringWithUTF8String:path.c_str()]]; [ios_view share_file:[NSString stringWithUTF8String:path.c_str()]];
}); });
}; };
bridge.pick_image = [](pp::platform::PickedPathCallback callback) { bridge.pick_image = [ios_view](pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_photo:callback]; [ios_view pick_photo:callback];
}); });
}; };
bridge.pick_file = []( bridge.pick_file = [ios_view](
std::vector<std::string> file_types, std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) { pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_file:apple_file_types_array(file_types) then:callback]; [ios_view pick_file:apple_file_types_array(file_types) then:callback];
}); });
}; };
return bridge; return bridge;
}()); }());
return services; return services;
#else #else
auto* const osx_view = App::I->osx_view;
auto* const osx_app = App::I->osx_app;
static pp::platform::apple::AppleDocumentPlatformServices services( static pp::platform::apple::AppleDocumentPlatformServices services(
pp::platform::PlatformFamily::macos, pp::platform::PlatformFamily::macos,
[] { [osx_view, osx_app] {
pp::platform::apple::AppleDocumentPickerBridge bridge; pp::platform::apple::AppleDocumentPickerBridge bridge;
bridge.clipboard_text = [] { bridge.clipboard_text = [osx_view] {
return [App::I->osx_view clipboard_get_string]; return [osx_view clipboard_get_string];
}; };
bridge.set_clipboard_text = [](std::string_view text) { bridge.set_clipboard_text = [osx_view](std::string_view text) {
const std::string value(text); const std::string value(text);
return [App::I->osx_view clipboard_set_string:value]; return [osx_view clipboard_set_string:value];
}; };
bridge.share_file = [](std::string path) { bridge.share_file = [osx_view](std::string path) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[App::I->osx_view share_file:[NSString stringWithUTF8String:path.c_str()]]; [osx_view share_file:[NSString stringWithUTF8String:path.c_str()]];
}); });
}; };
bridge.set_cursor_visible = [](bool visible) { bridge.set_cursor_visible = [osx_view](bool visible) {
[App::I->osx_view show_cursor:visible]; [osx_view show_cursor:visible];
}; };
bridge.save_ui_state = [] { bridge.save_ui_state = [osx_app] {
[App::I->osx_app save_ui_state]; [osx_app save_ui_state];
}; };
bridge.pick_file = []( bridge.pick_file = [osx_view](
std::vector<std::string> file_types, std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) { pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_file:apple_file_types_array(file_types)]; const std::string path = [osx_view pick_file:apple_file_types_array(file_types)];
callback(path); callback(path);
}); });
}; };
bridge.pick_save_file = []( bridge.pick_save_file = [osx_view](
std::vector<std::string> file_types, std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) { pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_file_save:apple_file_types_array(file_types)]; const std::string path = [osx_view pick_file_save:apple_file_types_array(file_types)];
callback(path); callback(path);
}); });
}; };
bridge.pick_directory = [](pp::platform::PickedPathCallback callback) { bridge.pick_directory = [osx_view](pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_dir]; const std::string path = [osx_view pick_dir];
callback(path); callback(path);
}); });
}; };