Skip to content
2 changes: 2 additions & 0 deletions companion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ The scope publishes one `RuntimeBinding` for the installed inventory. The bindin
The index loader retains ownership while a candidate is prepared. The application detaches the previous runtime, attaches the new compiler/insight bindings, and completes publication under the existing lifecycle lock. Debugger and UI follow-up runs afterward and cannot return an installed index to the loader's failure cleanup. Closing a runtime detaches its consumers before releasing the index, and is idempotent. A rejected candidate closes its own prepared consumers while leaving index disposal to the loader.

The script compiler and code-insight worker remain application-lived. Open local editors retain the code-insight service, so a runtime changes its binding rather than replacing that service instance. MCP tool declarations identify project-bound requests. Mutations use the captured scope's atomic admission gate; late results check that scope, and navigation also checks runtime identity. Instance state is flushed before retirement and detach. Stateless JDT parsing is in `JavaAst`; editor analysis/listeners remain in `ASTCache`.

Java editors receive an `EditorContext` containing their project, runtime-facing services, navigation and window callbacks. Search and settings windows receive their own specific collaborators. `ScriptExecutionService` shares authenticated execution and cancellation between UI and MCP; execution-result subscriptions belong to `CompanionSession` and are removed by their UI/job owners. Window disposal and project switching share the same auxiliary-window cleanup.
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
import com.github.minecraft_ta.totalDebugCompanion.debugger.DebuggerSessionController;
import com.github.minecraft_ta.totalDebugCompanion.mcp.CodeModeJobService;
import com.github.minecraft_ta.totalDebugCompanion.script.ScriptCompilationService;
import com.github.minecraft_ta.totalDebugCompanion.script.ScriptExecutionService;
import com.github.minecraft_ta.totaldebug.protocol.scnet.OpenClassMessage;
import com.github.minecraft_ta.totalDebugCompanion.script.ScriptCompilationService.CompilationResult;
import com.github.minecraft_ta.totaldebug.protocol.execution.ExecutionResult;
import com.github.minecraft_ta.totaldebug.protocol.execution.ScriptExecutionEnvironment;
import com.github.minecraft_ta.totaldebug.protocol.scnet.StopScriptMessage;
import com.github.minecraft_ta.totalDebugCompanion.mcp.CompanionMcpServer;
import com.github.minecraft_ta.totaldebug.protocol.scnet.DebugTargetMessage;
import com.github.minecraft_ta.totaldebug.protocol.scnet.RetryRuntimeInventoryMessage;
import com.github.minecraft_ta.totaldebug.protocol.scnet.RuntimeInventoryMessage;
import com.github.minecraft_ta.totaldebug.protocol.scnet.ServerManifestMessage;
import com.github.minecraft_ta.totalDebugCompanion.model.ServiceStatus;
Expand All @@ -51,7 +50,6 @@
import com.github.minecraft_ta.totalDebugCompanion.ui.theme.ThemeManager;
import com.github.minecraft_ta.totalDebugCompanion.ui.views.MainWindow;
import com.github.minecraft_ta.totalDebugCompanion.util.UIUtils;
import com.github.tth05.scnet.Server;
import com.github.tth05.scnet.message.AbstractMessage;
import com.github.tth05.jindex.ClassIndex;
import org.eclipse.jdt.core.dom.ASTParser;
Expand Down Expand Up @@ -82,13 +80,12 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

public final class CompanionApp {
private static final SecureRandom TOKEN_RANDOM = new SecureRandom();
private static final CountDownLatch EXIT = new CountDownLatch(1);

public static Server SERVER;
private static ScriptExecutionService scriptExecutions;
private static CompanionSession session;
private static CompanionLaunchConfiguration launchConfiguration;
private static final Object lifecycleLock = new Object();
Expand Down Expand Up @@ -186,6 +183,10 @@ static int run(String[] args, Map<String, String> environment, CompanionTimeouts
restoreProfile();

session = new CompanionSession(token, CompanionApp::attachSelectedProfile, new CompanionSession.Listener() {
@Override public void openClass(OpenClassMessage message) {
CompanionApp.openClass(message.binaryName(), message.targetType(), message.targetIdentifier());
}
@Override public void focusWindow() { CompanionApp.focusWindow(); }
@Override
public void connecting() {
updateGameStatus(new ServiceStatus(
Expand Down Expand Up @@ -234,7 +235,7 @@ public void debugTarget(DebugTargetMessage message) {
handleDebugTarget(message);
}
});
SERVER = session.server();
scriptExecutions = new ScriptExecutionService(session, scriptCompiler, CompanionApp::isConnected);
session.setProjectSelectionHandler(hello -> {
try { openProject(CompanionProfile.fromHello(hello)).join(); }
catch (java.util.concurrent.CompletionException failure) {
Expand Down Expand Up @@ -563,6 +564,9 @@ private static void finishRuntimeInstallation(RuntimeBinding installed, ProjectS
}

static void configureWithoutSession(CompanionProfile developmentProfile) {
session = new CompanionSession("ui-development");
scriptExecutions = new ScriptExecutionService(session, scriptCompiler, CompanionApp::isConnected);
runtimeIndexService = new RuntimeIndexService(lifecycleLock, CompanionApp::installRuntimeSnapshot);
debuggerController = createDebuggerController();
try {
activateProfile(Objects.requireNonNull(developmentProfile, "developmentProfile"));
Expand Down Expand Up @@ -667,7 +671,7 @@ private static void prewarmJavaParser() {

private static void startMcpServer() throws Exception {
CodeModeJobService jobs = new CodeModeJobService(
SERVER,
session, scriptExecutions, CompanionApp::requireProject,
() -> isConnected(),
CompanionApp::runtimeContext
);
Expand Down Expand Up @@ -735,13 +739,18 @@ private static Map<String, Object> runtimeContext() {
return Map.copyOf(context);
}

public static MainWindow createMainWindow() {
return new MainWindow(CompanionApp::currentScope, getDebuggerController(), codeInsightService,
scriptExecutions, session, runtimeIndexService, CompanionApp::openDebugFrame, CompanionApp::exit);
}

private static void startUi() throws InvocationTargetException, InterruptedException {
SwingUtilities.invokeAndWait(() -> {
uiStarted = true;
MainWindow.INSTANCE.setSize(1280, 720);
MainWindow.INSTANCE.setRuntimeIndexStatus(getRuntimeIndexStatus());
MainWindow.INSTANCE.setVisible(true);
UIUtils.centerJFrame(MainWindow.INSTANCE);
UIUtils.centerJFrame(MainWindow.INSTANCE, MainWindow.INSTANCE);
ToolTipManager.sharedInstance().setInitialDelay(200);
});
}
Expand Down Expand Up @@ -863,21 +872,8 @@ public static boolean isCurrentRuntimeInventory(String inventoryId) {
return isConnected() && scriptCompiler.isCurrentInventory(inventoryId);
}

public static boolean runScript(int id, String source, boolean serverSide,
ScriptExecutionEnvironment environment, Consumer<ExecutionResult> failureHandler) {
synchronized (lifecycleLock) {
ProjectScope scope = current;
if (scope == null || !scope.isActive() || !isConnected()) return false;
return scope.admit(() -> {
scriptCompiler.submit(id, source, serverSide, environment, failureHandler);
return true;
});
}
}

public static boolean stopScript(int id) {
return scriptCompiler.cancel(id) || send(new StopScriptMessage(id));
}
public static ScriptExecutionService scriptExecutions() { return scriptExecutions; }
public static CompanionSession session() { return session; }
Comment thread
Pelotrio marked this conversation as resolved.

public static void openClass(String binaryName, int targetType, String targetIdentifier) {
openOrQueue(
Expand Down Expand Up @@ -1021,28 +1017,6 @@ public static RuntimeIndexService.Status getRuntimeIndexStatus() {
: service.status();
}

public static void addRuntimeIndexStatusListener(Consumer<RuntimeIndexService.Status> listener) {
RuntimeIndexService service = runtimeIndexService;
if (service != null) {
service.addStatusListener(listener);
} else {
listener.accept(getRuntimeIndexStatus());
}
}

public static void removeRuntimeIndexStatusListener(Consumer<RuntimeIndexService.Status> listener) {
RuntimeIndexService service = runtimeIndexService;
if (service != null) service.removeStatusListener(listener);
}

public static void retryRuntimeIndex() {
RuntimeIndexService service = runtimeIndexService;
if (service != null) {
service.waiting("Requesting runtime inventory again");
}
send(new RetryRuntimeInventoryMessage());
}

private static CompanionProfile requireProfile() {
CompanionProfile current = currentProject();
if (current == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.github.minecraft_ta.totalDebugCompanion.jdt.symbol.JavaSymbolResolver;
import com.github.minecraft_ta.totalDebugCompanion.navigation.NavigationTarget;
import com.github.minecraft_ta.totalDebugCompanion.navigation.RuntimeMember;
import com.github.minecraft_ta.totalDebugCompanion.ui.views.MainWindow;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaModelException;
Expand Down Expand Up @@ -35,13 +34,13 @@ interface ElementResolver {
private final Consumer<NavigationTarget> navigator;
private final Path sourcePath;

public CustomJavaLinkGenerator(String identifier) {
public CustomJavaLinkGenerator(String identifier, BiConsumer<String, String> packageNavigator, Consumer<NavigationTarget> navigator) {
this(
offset -> JavaSymbolResolver.selectElement(identifier, offset),
offset -> JavaSymbolResolver.navigationOwnerClass(identifier, offset),
MainWindow.INSTANCE::revealPackage,
packageNavigator,
Path.of(identifier),
target -> MainWindow.INSTANCE.navigation().navigate(target)
navigator
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import com.github.minecraft_ta.totaldebug.protocol.execution.ScriptExecutionEnvironment;
import com.github.minecraft_ta.totalDebugCompanion.script.ExecutionValuePresentation;
import com.github.minecraft_ta.totalDebugCompanion.CompanionApp;
import com.github.minecraft_ta.totalDebugCompanion.project.ProjectScope;
import com.github.minecraft_ta.totalDebugCompanion.script.ScriptExecutionService;
import com.github.minecraft_ta.totalDebugCompanion.session.CompanionSession;
import com.github.minecraft_ta.totaldebug.protocol.scnet.ExecutionResultMessage;
import com.github.minecraft_ta.totaldebug.protocol.execution.ExecutionResult;
import com.github.minecraft_ta.totaldebug.protocol.execution.ExecutionText;
import com.github.tth05.scnet.Server;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
Expand All @@ -31,7 +32,8 @@ public final class CodeModeJobService implements AutoCloseable {
static final int MAX_RETAINED_JOBS = 256;
static final int MAX_WAIT_MILLISECONDS = 120_000;

private final Server server;
private final CompanionSession session;
private Consumer<ExecutionResultMessage> resultListener;
private final ExecutorService statusExecutor;
private final BooleanSupplier available;
private final Transport transport;
Expand All @@ -43,12 +45,14 @@ public final class CodeModeJobService implements AutoCloseable {
private volatile boolean closed;

public CodeModeJobService(
Server server,
CompanionSession session,
ScriptExecutionService scripts,
Supplier<ProjectScope> project,
BooleanSupplier available,
Supplier<Map<String, Object>> runtimeContext
) {
this(
Objects.requireNonNull(server, "server"),
Objects.requireNonNull(session, "session"),
Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "Companion code-mode status");
thread.setDaemon(true);
Expand All @@ -64,7 +68,8 @@ public void execute(
ExecutionEnvironment environment,
Consumer<ExecutionResult> failureHandler
) {
boolean sent = CompanionApp.runScript(
boolean sent = scripts.run(
project.get(),
scriptId,
source,
side == ExecutionSide.SERVER,
Expand All @@ -78,22 +83,23 @@ public void execute(

@Override
public void cancel(int scriptId) {
if (!CompanionApp.stopScript(scriptId)) {
if (!scripts.stop(scriptId)) {
throw new IllegalStateException("Minecraft disconnected before cancellation was sent");
}
}
},
runtimeContext,
Clock.systemUTC()
);
server.getMessageBus().listenAlways(ExecutionResultMessage.class, this, message -> {
this.resultListener = message -> {
int scriptId = message.scriptId();
ExecutionResult result = message.result();
try {
this.statusExecutor.execute(() -> acceptResult(scriptId, result));
} catch (RejectedExecutionException ignored) {
}
});
};
session.addExecutionResultListener(this.resultListener);
}

CodeModeJobService(
Expand Down Expand Up @@ -121,14 +127,14 @@ public void cancel(int scriptId) {
}

private CodeModeJobService(
Server server,
CompanionSession session,
ExecutorService statusExecutor,
BooleanSupplier available,
Transport transport,
Supplier<Map<String, Object>> runtimeContext,
Clock clock
) {
this.server = server;
this.session = session;
this.statusExecutor = statusExecutor;
this.available = Objects.requireNonNull(available, "available");
this.transport = Objects.requireNonNull(transport, "transport");
Expand Down Expand Up @@ -355,8 +361,8 @@ public void close() {
return;
}
this.closed = true;
if (this.server != null) {
this.server.getMessageBus().unregister(ExecutionResultMessage.class, this);
if (this.session != null) {
this.session.removeExecutionResultListener(this.resultListener);
}
if (this.statusExecutor != null) {
this.statusExecutor.shutdownNow();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.minecraft_ta.totalDebugCompanion.model;

import com.github.minecraft_ta.totalDebugCompanion.runtime.RuntimeBinding;
import com.github.minecraft_ta.totalDebugCompanion.ui.EditorContext;
import com.formdev.flatlaf.util.StringUtils;
import com.github.minecraft_ta.totalDebugCompanion.CompanionApp;
import com.github.minecraft_ta.totalDebugCompanion.Icons;
import com.github.minecraft_ta.totalDebugCompanion.decompile.DecompiledSource;
import com.github.minecraft_ta.totalDebugCompanion.debugger.DebugEngine;
Expand Down Expand Up @@ -31,27 +31,27 @@ public class CodeView implements IEditorPanel {
private final CodeViewPanel codeViewPanel;
private volatile CompletableFuture<Void> ready = CompletableFuture.completedFuture(null);

public CodeView(Path path, int offset) {
this(path, offset, EditorLocation.forFile(path, CompanionApp.getWorkspaceDirectory()));
public CodeView(EditorContext context, Path path, int offset) {
this(context, path, offset, EditorLocation.forFile(path, context.project().profile().workspaceDirectory()));
}

public CodeView(Path path, int offset, EditorLocation location) {
public CodeView(EditorContext context, Path path, int offset, EditorLocation location) {
this.runtimeBinding = null;
this.path = path;
this.location = location;
this.debugSource = null;
this.navigationTarget = new NavigationTarget.LocalFile(path);
this.codeViewPanel = new CodeViewPanel(this);
this.codeViewPanel = new CodeViewPanel(context, this);
reload(offset);
}

public CodeView(DecompiledSource source, int offset, EditorLocation location, RuntimeBinding runtimeBinding) {
public CodeView(EditorContext context, DecompiledSource source, int offset, EditorLocation location, RuntimeBinding runtimeBinding) {
this.runtimeBinding = runtimeBinding;
this.path = source.path();
this.location = location;
this.debugSource = source.debugSource();
this.navigationTarget = new NavigationTarget.RuntimeClass(source.binaryName());
this.codeViewPanel = new CodeViewPanel(this);
this.codeViewPanel = new CodeViewPanel(context, this);
setCode(source.contents(), offset);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.minecraft_ta.totalDebugCompanion.model;

import com.github.minecraft_ta.totalDebugCompanion.runtime.RuntimeBinding;
import com.github.minecraft_ta.totalDebugCompanion.CompanionApp;
import com.github.minecraft_ta.totalDebugCompanion.ui.EditorContext;
import com.github.minecraft_ta.totalDebugCompanion.Icons;
import com.github.minecraft_ta.totalDebugCompanion.bytecode.reference.ReferenceQuery;
import com.github.minecraft_ta.totalDebugCompanion.navigation.NavigationTarget;
Expand All @@ -18,15 +18,15 @@ public final class LiteralUsagesView implements IEditorPanel {
private final String literal;
private final UsagesViewPanel panel;

public LiteralUsagesView(String literal, RuntimeBinding runtimeBinding) {
public LiteralUsagesView(EditorContext context, String literal, RuntimeBinding runtimeBinding) {
if (runtimeBinding == null) throw new IllegalStateException("Reference search is unavailable");
this.runtimeBinding = runtimeBinding;
this.literal = Objects.requireNonNull(literal, "literal");
this.panel = new UsagesViewPanel(
ReferenceQuery.stringLiteral(literal),
quotedPreview(literal),
Icons.VALUE,
runtimeBinding.references()
runtimeBinding.references(), context.navigation()::navigate
);
}

Expand Down
Loading