Skip to content

Commit 8bfb4e8

Browse files
committed
Restore AutoMesh progress UI without blocking MCP
Reintroduce the AutoMesh notification progress popup and cancel button using thread-safe shared progress state. Keep AutoMeshApplyResult as the completion contract so MCP tool calls still wait for mesh generation and main-thread application while the GUI main loop remains responsive.
1 parent 074cfe4 commit 8bfb4e8

1 file changed

Lines changed: 84 additions & 3 deletions

File tree

  • source/nijigenerate/commands/automesh

source/nijigenerate/commands/automesh/dynamic.d

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import vibe.core.core : vibeYield = yield;
2020
import std.stdio : writefln;
2121
import nijigenerate.api.mcp.task : ngMcpEnqueueAction, ngRunInMainThread; // scheduling helpers
2222
import nijigenerate.utils.crashdump : installNativeCrashDumpThreadHandler;
23+
import nijigenerate.widgets.notification : NotificationPopup;
24+
import bindbc.imgui : ImGuiIO, ImVec2, igText, igProgressBar, igSameLine;
2325

2426
version(CMD_LOG) private void cmdLog(T...)(T args) { writefln(args); }
2527
else private void cmdLog(T...)(T args) {}
@@ -66,6 +68,52 @@ private class AutoMeshApplyResult : CommandResult {
6668
}
6769
}
6870

71+
private class AutoMeshProgressState {
72+
private Mutex lock;
73+
size_t total;
74+
private size_t done_;
75+
private string currentName_;
76+
private bool canceled_;
77+
78+
this(size_t total) {
79+
lock = new Mutex();
80+
this.total = total;
81+
}
82+
83+
void beginTarget(string name) {
84+
synchronized (lock) {
85+
currentName_ = name;
86+
}
87+
}
88+
89+
void completeTarget() {
90+
synchronized (lock) {
91+
if (done_ < total) ++done_;
92+
}
93+
}
94+
95+
void requestCancel() {
96+
synchronized (lock) {
97+
canceled_ = true;
98+
}
99+
}
100+
101+
bool canceled() {
102+
synchronized (lock) {
103+
return canceled_;
104+
}
105+
}
106+
107+
void snapshot(out size_t done, out size_t totalOut, out string currentName, out bool canceledOut) {
108+
synchronized (lock) {
109+
done = done_;
110+
totalOut = total;
111+
currentName = currentName_;
112+
canceledOut = canceled_;
113+
}
114+
}
115+
}
116+
69117
// Compile-time presence check for initializer
70118
static if (__traits(compiles, { void _ct_probe(){ ngInitCommands!(AutoMeshKey)(); } })) {
71119
//pragma(msg, "[CT] automesh.dynamic: ngInitCommands!(AutoMeshKey) present");
@@ -147,17 +195,44 @@ template ApplyAutoMeshPT(alias PT)
147195
}
148196

149197
auto asyncResult = new AutoMeshApplyResult();
198+
auto progress = new AutoMeshProgressState(targets.length);
199+
string procName = chosen.displayName();
200+
ulong popupId = NotificationPopup.instance().popup((ImGuiIO* io) {
201+
import nijigenerate.widgets : incButtonColored;
202+
import std.string : toStringz;
203+
204+
size_t done, total;
205+
string currentName;
206+
bool canceled;
207+
progress.snapshot(done, total, currentName, canceled);
208+
float ratio = total > 0 ? cast(float)done / cast(float)total : 0;
209+
string title = "AutoMesh: " ~ procName ~ (currentName.length ? (" - " ~ currentName) : "");
210+
igText(title.toStringz);
211+
igProgressBar(ratio, ImVec2(320, 0));
212+
igSameLine(0, 8);
213+
if (canceled) {
214+
igText("Canceling...");
215+
} else if (incButtonColored("Cancel", ImVec2(96, 24))) {
216+
progress.requestCancel();
217+
}
218+
}, -1);
219+
150220
Thread th = new Thread({
151221
installNativeCrashDumpThreadHandler();
152222
IncMesh[uint] results;
153223
string workerError;
154224
size_t processed = 0;
155225
bool cb(Deformable d, IncMesh mesh) {
226+
if (mesh is null) {
227+
progress.beginTarget(d.name);
228+
return !progress.canceled();
229+
}
156230
if (mesh !is null) {
157231
results[d.uuid] = mesh;
158232
++processed;
233+
progress.completeTarget();
159234
}
160-
return true;
235+
return !progress.canceled();
161236
}
162237
void work() {
163238
Deformable[] bgTargets = targets;
@@ -175,11 +250,15 @@ template ApplyAutoMeshPT(alias PT)
175250

176251
if (workerError.length) {
177252
writefln("[AutoMesh] worker failed: %s", workerError);
178-
asyncResult.complete(CommandResult(false, workerError));
253+
ngMcpEnqueueAction({
254+
NotificationPopup.instance().close(popupId);
255+
asyncResult.complete(CommandResult(false, workerError));
256+
});
179257
return;
180258
}
181259

182260
ngMcpEnqueueAction({
261+
scope(exit) NotificationPopup.instance().close(popupId);
183262
size_t applied = 0;
184263
foreach (t; targets) {
185264
if (auto pm = t.uuid in results) {
@@ -194,7 +273,9 @@ template ApplyAutoMeshPT(alias PT)
194273
}
195274
}
196275

197-
if (applied == 0) {
276+
if (progress.canceled()) {
277+
asyncResult.complete(CommandResult(false, "AutoMesh canceled"));
278+
} else if (applied == 0) {
198279
asyncResult.complete(CommandResult(false, "AutoMesh generated no applicable meshes"));
199280
} else {
200281
asyncResult.complete(CommandResult(true));

0 commit comments

Comments
 (0)