Skip to content

Commit 8a6b7ea

Browse files
refactor: threading logic of breeze-ui and js
The concept is that any js callback should be run in js thread, and any dom operation should be locked with rt_lock. We post the task to js thread in detached mode, and add the lock to dom operation apis, then it would be safe to operate dom anywhere in js as no js code would block breeze-ui update/render anymore.
1 parent d4d9a1b commit 8a6b7ea

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

dependencies/breeze-ui.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package("breeze-glfw")
55

66
package("breeze-ui")
77
add_urls("https://github.com/std-microblock/breeze-ui.git")
8-
add_versions("2025.10.06", "2c98b78472a1e493a9f0a961b472b4e0a8601e55")
8+
add_versions("2025.10.07", "f39fa973d50499825535cddac9ad6202c9239824")
99
add_deps("breeze-glfw", "nanovg", "glad", "nanosvg")
1010
add_configs("shared", {description = "Build shared library.", default = false, type = "boolean", readonly = true})
1111

src/shell/script/binding_types.cc

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,10 @@ void network::get_async(std::string url,
467467
std::function<void(std::string)> callback,
468468
std::function<void(std::string)> error_callback) {
469469
std::thread([url, callback, error_callback,
470-
&lock = ui::render_target::current->rt_lock]() {
470+
&ctx = *qjs::Context::current]() {
471471
try {
472472
auto res = get(url);
473-
std::lock_guard l(lock);
474-
callback(res);
473+
ctx.enqueueJob([=]() { callback(res); });
475474
} catch (std::exception &e) {
476475
std::cerr << "Error in network::get_async: " << e.what()
477476
<< std::endl;
@@ -484,11 +483,10 @@ void network::post_async(std::string url, std::string data,
484483
std::function<void(std::string)> callback,
485484
std::function<void(std::string)> error_callback) {
486485
std::thread([url, data, callback, error_callback,
487-
&lock = ui::render_target::current->rt_lock]() {
486+
&ctx = *qjs::Context::current]() {
488487
try {
489488
auto res = post(url, data);
490-
std::lock_guard l(lock);
491-
callback(res);
489+
ctx.enqueueJob([=]() { callback(res); });
492490
} catch (std::exception &e) {
493491
std::cerr << "Error in network::post_async: " << e.what()
494492
<< std::endl;
@@ -544,11 +542,10 @@ subproc_result_data subproc::run(std::string cmd) {
544542
}
545543
void subproc::run_async(std::string cmd,
546544
std::function<void(subproc_result_data)> callback) {
547-
std::thread([cmd, callback, &lock = ui::render_target::current->rt_lock]() {
545+
std::thread([cmd, callback, &ctx = *qjs::Context::current]() {
548546
try {
549547
auto res = run(cmd);
550-
std::lock_guard l(lock);
551-
callback(res);
548+
ctx.enqueueJob([=]() { callback(res); });
552549
} catch (std::exception &e) {
553550
std::cerr << "Error in subproc::run_async: " << e.what()
554551
<< std::endl;
@@ -640,12 +637,12 @@ void network::download_async(std::string url, std::string path,
640637
std::function<void(std::string)> error_callback) {
641638

642639
std::thread([url, path, callback, error_callback,
643-
&lock = ui::render_target::current->rt_lock]() {
640+
&ctx = *qjs::Context::current]() {
644641
try {
645642
auto data = get(url);
646643
fs::write_binary(path,
647644
std::vector<uint8_t>(data.begin(), data.end()));
648-
std::lock_guard l(lock);
645+
ctx.enqueueJob([=]() { callback(); });
649646
callback();
650647
} catch (std::exception &e) {
651648
error_callback(e.what());
@@ -822,11 +819,10 @@ void subproc::open(std::string path, std::string args) {
822819
void subproc::open_async(std::string path, std::string args,
823820
std::function<void()> callback) {
824821
std::thread([path, callback, args,
825-
&lock = ui::render_target::current->rt_lock]() {
822+
&ctx = *qjs::Context::current]() {
826823
try {
827824
open(path, args);
828-
std::lock_guard l(lock);
829-
callback();
825+
ctx.enqueueJob([=]() { callback(); });
830826
} catch (std::exception &e) {
831827
std::cerr << "Error in subproc::open_async: " << e.what()
832828
<< std::endl;
@@ -981,7 +977,7 @@ std::string infra::btoa(std::string str) {
981977

982978
void fs::copy_shfile(std::string src_path, std::string dest_path,
983979
std::function<void(bool, std::string)> callback) {
984-
std::thread([=, &lock = ui::render_target::current->rt_lock] {
980+
std::thread([=, &ctx = *qjs::Context::current] {
985981
SHFILEOPSTRUCTW FileOp = {GetForegroundWindow()};
986982
std::wstring wsrc = utf8_to_wstring(src_path);
987983
std::wstring wdest = utf8_to_wstring(dest_path);
@@ -1026,14 +1022,14 @@ void fs::copy_shfile(std::string src_path, std::string dest_path,
10261022
}
10271023

10281024
std::string utf8_path = wstring_to_utf8(final_path);
1029-
std::lock_guard l(lock);
1030-
callback(success, utf8_path);
1025+
1026+
ctx.enqueueJob([=]() { callback(success, utf8_path); });
10311027
}).detach();
10321028
}
10331029

10341030
void fs::move_shfile(std::string src_path, std::string dest_path,
10351031
std::function<void(bool)> callback) {
1036-
std::thread([=, &lock = ui::render_target::current->rt_lock] {
1032+
std::thread([=, &ctx = *qjs::Context::current] {
10371033
SHFILEOPSTRUCTW FileOp = {GetForegroundWindow()};
10381034
std::wstring wsrc = utf8_to_wstring(src_path);
10391035
std::wstring wdest = utf8_to_wstring(dest_path);
@@ -1043,8 +1039,7 @@ void fs::move_shfile(std::string src_path, std::string dest_path,
10431039
FileOp.pTo = wdest.c_str();
10441040

10451041
auto res = SHFileOperationW(&FileOp);
1046-
std::lock_guard l(lock);
1047-
callback(res == 0);
1042+
ctx.enqueueJob([=]() { callback(res == 0); });
10481043
}).detach();
10491044
}
10501045
size_t win32::load_file_icon(std::string path) {

src/shell/script/binding_types_breeze_ui.cc

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace mb_shell::js {
2323
auto widget = std::dynamic_pointer_cast<widget_type>($widget); \
2424
if (!widget) \
2525
return; \
26+
auto lock = $rt_lock(); \
2627
widget->prop_name->animate_to(value); \
2728
}
2829

@@ -38,6 +39,7 @@ namespace mb_shell::js {
3839
auto widget = std::dynamic_pointer_cast<widget_type>($widget); \
3940
if (!widget) \
4041
return; \
42+
auto lock = $rt_lock(); \
4143
widget->prop_name = value; \
4244
}
4345

@@ -53,6 +55,7 @@ namespace mb_shell::js {
5355
auto widget = std::dynamic_pointer_cast<widget_type>($widget); \
5456
if (!widget) \
5557
return; \
58+
auto lock = $rt_lock(); \
5659
widget->prop_name = callback; \
5760
}
5861

@@ -93,6 +96,7 @@ namespace mb_shell::js {
9396
auto widget = std::dynamic_pointer_cast<widget_type>($widget); \
9497
if (!widget || !paint) \
9598
return; \
99+
auto lock = $rt_lock(); \
96100
widget->prop_name = paint->$paint; \
97101
}
98102

@@ -119,6 +123,7 @@ void breeze_ui::js_text_widget::set_text(std::string text) {
119123
auto text_widget = std::dynamic_pointer_cast<ui::text_widget>($widget);
120124
if (!text_widget)
121125
return;
126+
auto lock = $rt_lock();
122127
text_widget->text = text;
123128
text_widget->needs_repaint = true;
124129
}
@@ -134,6 +139,7 @@ void breeze_ui::js_text_widget::set_font_size(int size) {
134139
auto text_widget = std::dynamic_pointer_cast<ui::text_widget>($widget);
135140
if (!text_widget)
136141
return;
142+
auto lock = $rt_lock();
137143
text_widget->font_size = size;
138144
text_widget->needs_repaint = true;
139145
}
@@ -163,7 +169,11 @@ void breeze_ui::js_text_widget::set_color(
163169

164170
void breeze_ui::js_widget::append_child_after(std::shared_ptr<js_widget> child,
165171
int after_index) {
172+
if (!$widget)
173+
return;
174+
auto lock = $rt_lock();
166175
if (child && child->$widget) {
176+
child->$widget->owner_rt = $widget->owner_rt;
167177
$widget->children_dirty = true;
168178
$widget->needs_repaint = true;
169179
if (after_index < 0) {
@@ -186,8 +196,9 @@ void breeze_ui::js_widget::prepend_child(std::shared_ptr<js_widget> child) {
186196
void breeze_ui::js_widget::remove_child(std::shared_ptr<js_widget> child) {
187197
if (!$widget)
188198
return;
189-
199+
auto lock = $rt_lock();
190200
if (child && child->$widget) {
201+
child->$widget->owner_rt = nullptr;
191202
auto it = std::find($widget->children.begin(), $widget->children.end(),
192203
child->$widget);
193204
if (it != $widget->children.end()) {
@@ -306,40 +317,40 @@ struct widget_js_base : public ui::widget_flex {
306317
ctx.rt.post_loop_thread_task(
307318
[=, callback = this->on_update]() mutable {
308319
callback(ctx);
309-
});
320+
}, true);
310321
}
311322

312323
if (ctx.hovered(this) && ctx.mouse_clicked && on_click) {
313324
ctx.rt.post_loop_thread_task(
314-
[callback = this->on_click]() mutable { callback(0); });
325+
[callback = this->on_click]() mutable { callback(0); }, true);
315326
}
316327

317328
if (ctx.hovered(this) && !previous_hovered && on_mouse_enter) {
318329
ctx.rt.post_loop_thread_task(
319330
[=, callback = this->on_mouse_enter]() mutable {
320331
callback();
321-
});
332+
}, true);
322333
} else if (!ctx.hovered(this) && previous_hovered &&
323334
on_mouse_leave) {
324335
ctx.rt.post_loop_thread_task(
325336
[=, callback = this->on_mouse_leave]() mutable {
326337
callback();
327-
});
338+
}, true);
328339
}
329340

330341
previous_hovered = ctx.hovered(this);
331342
if (ctx.mouse_down_on(this) && on_mouse_down) {
332343
ctx.rt.post_loop_thread_task(
333344
[=, callback = this->on_mouse_down]() mutable {
334345
callback();
335-
});
346+
}, true);
336347
}
337348

338349
if (ctx.mouse_up && on_mouse_up) {
339350
ctx.rt.post_loop_thread_task(
340351
[=, callback = this->on_mouse_up]() mutable {
341352
callback();
342-
});
353+
}, true);
343354
}
344355

345356
if (ctx.mouse_x != prev_mouse_x || ctx.mouse_y != prev_mouse_y) {
@@ -593,4 +604,12 @@ void breeze_ui::js_widget::set_animation(std::string variable_name,
593604
#undef IMPL_COLOR_PROP
594605
#undef IMPL_PAINT_PROP
595606

607+
std::optional<std::unique_lock<std::recursive_mutex>>
608+
breeze_ui::js_widget::$rt_lock() {
609+
if ($widget && $widget->owner_rt) {
610+
return std::optional<std::unique_lock<std::recursive_mutex>>{
611+
std::in_place, $widget->owner_rt->rt_lock};
612+
}
613+
return std::nullopt;
614+
}
596615
} // namespace mb_shell::js

src/shell/script/binding_types_breeze_ui.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include <functional>
33
#include <memory>
4+
#include <mutex>
45
#include <optional>
56
#include <string>
67
#include <tuple>
@@ -28,6 +29,8 @@ struct breeze_ui {
2829
js_widget(std::shared_ptr<ui::widget> widget) : $widget(widget) {}
2930
virtual ~js_widget() = default;
3031

32+
std::optional<std::unique_lock<std::recursive_mutex>> $rt_lock();
33+
3134
std::vector<std::shared_ptr<js_widget>> children() const;
3235
void append_child(std::shared_ptr<js_widget> child);
3336
void prepend_child(std::shared_ptr<js_widget> child);

0 commit comments

Comments
 (0)