Skip to content

Commit 200fbf1

Browse files
committed
Finalize task lifecycle fixes and add demo recordings
1 parent 6d46929 commit 200fbf1

29 files changed

Lines changed: 1027 additions & 206 deletions

mod/src/main/java/com/pyritone/bridge/PyritoneBridgeClientMod.java

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import com.pyritone.bridge.net.SocketBridgeServer;
1111
import com.pyritone.bridge.runtime.BaritoneGateway;
1212
import com.pyritone.bridge.runtime.TaskRegistry;
13+
import com.pyritone.bridge.runtime.TaskLifecycleResolver;
1314
import com.pyritone.bridge.runtime.TaskSnapshot;
1415
import com.pyritone.bridge.runtime.TaskState;
15-
import com.pyritone.bridge.runtime.TaskLifecycleResolver;
1616
import com.pyritone.bridge.runtime.WatchPatternRegistry;
1717
import net.fabricmc.api.ClientModInitializer;
1818
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
@@ -64,6 +64,7 @@ public void onInitializeClient() {
6464

6565
ClientTickEvents.END_CLIENT_TICK.register(client -> {
6666
baritoneGateway.tickRegisterPathListener();
67+
baritoneGateway.tickRegisterPyritoneHashCommand(this::forceCancelActiveTaskFromPyritoneCommand);
6768
baritoneGateway.tickApplyPyritoneChatBranding();
6869
tickTaskLifecycle();
6970
});
@@ -118,6 +119,10 @@ private void ensureBaritoneSettingsFile() {
118119
}
119120
}
120121
private boolean handleOutgoingChat(String message) {
122+
if (message != null && message.trim().equalsIgnoreCase("#pyritone cancel")) {
123+
forceCancelActiveTaskFromPyritoneCommand();
124+
return false;
125+
}
121126
emitWatchMatches("chat", message);
122127
return true;
123128
}
@@ -333,6 +338,35 @@ private JsonObject handleTaskCancel(String id) {
333338
return ProtocolCodec.successResponse(id, result);
334339
}
335340

341+
public boolean forceCancelActiveTaskFromPyritoneCommand() {
342+
Optional<TaskSnapshot> active = taskRegistry.active();
343+
if (active.isEmpty()) {
344+
emitPyritoneNotice("No active Py-Ritone task to cancel");
345+
return false;
346+
}
347+
348+
TaskSnapshot snapshot = active.orElseThrow();
349+
String detail = "Canceled by #pyritone cancel";
350+
351+
if (!baritoneGateway.isAvailable()) {
352+
detail = "Canceled by #pyritone cancel (Baritone unavailable)";
353+
emitPyritoneNotice("Hard cancel: Baritone unavailable, force-ending tracked task");
354+
} else {
355+
BaritoneGateway.Outcome outcome = baritoneGateway.cancelCurrent();
356+
if (outcome.ok()) {
357+
emitPyritoneNotice("Hard cancel accepted");
358+
} else {
359+
detail = "Canceled by #pyritone cancel (" + compactCommand(outcome.message()) + ")";
360+
emitPyritoneNotice("Hard cancel forced task end even though Baritone cancel returned an error");
361+
}
362+
}
363+
364+
taskLifecycleResolver.clearForTask(snapshot.taskId());
365+
taskRegistry.transitionActive(TaskState.CANCELED, detail)
366+
.ifPresent(canceled -> emitTaskEvent("task.canceled", canceled, "pyritone_cancel_command"));
367+
return true;
368+
}
369+
336370
private void onPathEvent(String pathEventName) {
337371
JsonObject data = new JsonObject();
338372
data.addProperty("path_event", pathEventName);
@@ -348,8 +382,6 @@ private void onPathEvent(String pathEventName) {
348382

349383
TaskSnapshot current = active.orElseThrow();
350384
taskLifecycleResolver.recordPathEvent(current.taskId(), pathEventName);
351-
taskRegistry.updateActiveDetail(pathEventDetail(pathEventName))
352-
.ifPresent(snapshot -> emitTaskEvent("task.progress", snapshot, pathEventName));
353385
}
354386

355387
private void tickTaskLifecycle() {
@@ -360,27 +392,41 @@ private void tickTaskLifecycle() {
360392
}
361393

362394
TaskSnapshot current = active.orElseThrow();
363-
Optional<TaskLifecycleResolver.TerminalDecision> terminal = taskLifecycleResolver.evaluate(
395+
Optional<TaskLifecycleResolver.LifecycleUpdate> lifecycleUpdate = taskLifecycleResolver.evaluate(
364396
current.taskId(),
365397
baritoneGateway.activitySnapshot()
366398
);
367399

368-
if (terminal.isEmpty()) {
400+
if (lifecycleUpdate.isEmpty()) {
369401
return;
370402
}
371403

372-
TaskLifecycleResolver.TerminalDecision decision = terminal.orElseThrow();
373-
taskRegistry.transitionActive(decision.state(), decision.detail())
374-
.ifPresent(snapshot -> emitTaskEvent(decision.eventName(), snapshot, decision.stage()));
404+
TaskLifecycleResolver.LifecycleUpdate update = lifecycleUpdate.orElseThrow();
405+
switch (update.kind()) {
406+
case PAUSED -> handlePausedUpdate(current, update.pauseStatus());
407+
case RESUMED -> handleResumedUpdate(current, update.pauseStatus());
408+
case TERMINAL -> {
409+
TaskLifecycleResolver.TerminalDecision decision = update.terminalDecision();
410+
if (decision == null) {
411+
return;
412+
}
413+
taskRegistry.transitionActive(decision.state(), decision.detail())
414+
.ifPresent(snapshot -> emitTaskEvent(decision.eventName(), snapshot, decision.stage()));
415+
}
416+
}
375417
}
376418

377-
private static String pathEventDetail(String pathEventName) {
378-
return switch (pathEventName) {
379-
case "AT_GOAL" -> "Reached goal (awaiting stable idle)";
380-
case "CANCELED" -> "Baritone canceled (awaiting stable idle)";
381-
case "CALC_FAILED", "NEXT_CALC_FAILED" -> "Path calculation failed (awaiting stable idle)";
382-
default -> "Path event: " + pathEventName;
383-
};
419+
private void handlePausedUpdate(TaskSnapshot current, TaskLifecycleResolver.PauseStatus pauseStatus) {
420+
String detail = pauseStatusDetail(pauseStatus);
421+
Optional<TaskSnapshot> updated = taskRegistry.updateActiveDetail(detail);
422+
TaskSnapshot snapshot = updated.orElse(current);
423+
emitPauseEvent("task.paused", snapshot, pauseStatus);
424+
}
425+
426+
private void handleResumedUpdate(TaskSnapshot current, TaskLifecycleResolver.PauseStatus pauseStatus) {
427+
Optional<TaskSnapshot> updated = taskRegistry.updateActiveDetail("Resumed after pause");
428+
TaskSnapshot snapshot = updated.orElse(current);
429+
emitPauseEvent("task.resumed", snapshot, pauseStatus);
384430
}
385431

386432
private void emitTaskEvent(String eventName, TaskSnapshot taskSnapshot, String stage) {
@@ -391,6 +437,40 @@ private void emitTaskEvent(String eventName, TaskSnapshot taskSnapshot, String s
391437
publishEvent(eventName, data);
392438
}
393439

440+
private void emitPauseEvent(String eventName, TaskSnapshot taskSnapshot, TaskLifecycleResolver.PauseStatus pauseStatus) {
441+
JsonObject data = taskSnapshot.toJson();
442+
data.addProperty("stage", eventName);
443+
data.add("pause", pauseStatusToJson(pauseStatus));
444+
publishEvent(eventName, data);
445+
}
446+
447+
private static JsonObject pauseStatusToJson(TaskLifecycleResolver.PauseStatus pauseStatus) {
448+
JsonObject object = new JsonObject();
449+
if (pauseStatus == null) {
450+
object.addProperty("reason_code", "PAUSED");
451+
object.addProperty("source_process", "");
452+
object.addProperty("command_type", "");
453+
return object;
454+
}
455+
456+
object.addProperty("reason_code", safeString(pauseStatus.reasonCode()));
457+
object.addProperty("source_process", safeString(pauseStatus.sourceProcess()));
458+
object.addProperty("command_type", safeString(pauseStatus.commandType()));
459+
return object;
460+
}
461+
462+
private static String pauseStatusDetail(TaskLifecycleResolver.PauseStatus pauseStatus) {
463+
if (pauseStatus == null) {
464+
return "Paused";
465+
}
466+
467+
return "Paused (" + safeString(pauseStatus.reasonCode()) + ")";
468+
}
469+
470+
private static String safeString(String value) {
471+
return value == null ? "" : value;
472+
}
473+
394474
private void publishEvent(String eventName, JsonObject data) {
395475
SocketBridgeServer currentServer = this.server;
396476
if (currentServer == null || !currentServer.isRunning()) {

mod/src/main/java/com/pyritone/bridge/command/PyritoneCommand.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ public static void register(PyritoneBridgeClientMod mod) {
1919
context.getSource().sendFeedback(Text.literal(mod.commandStatusLine()));
2020
return 1;
2121
}))
22+
.then(ClientCommandManager.literal("cancel")
23+
.executes(context -> {
24+
boolean canceled = mod.forceCancelActiveTaskFromPyritoneCommand();
25+
if (canceled) {
26+
context.getSource().sendFeedback(Text.literal("Requested hard cancel for active Py-Ritone task"));
27+
} else {
28+
context.getSource().sendFeedback(Text.literal("No active Py-Ritone task"));
29+
}
30+
return 1;
31+
}))
2232
.then(ClientCommandManager.literal("bridge-info")
2333
.executes(context -> {
2434
context.getSource().sendFeedback(Text.literal(mod.bridgeInfoPath().toString()));

0 commit comments

Comments
 (0)