mirror of
https://github.com/System-End/Discord-MC-Chat.git
synced 2026-04-19 18:35:15 +00:00
Implement callback-based completion for Minecraft command execution and enhance timeout handling
This commit is contained in:
parent
79a9aec67b
commit
f1ee8b59e2
5 changed files with 67 additions and 17 deletions
|
|
@ -47,6 +47,8 @@ import static com.xujiayao.discord_mc_chat.Constants.LOGGER;
|
|||
*/
|
||||
public class ClientHandler extends SimpleChannelInboundHandler<Packet> {
|
||||
|
||||
private static final int CONSOLE_COMMAND_TIMEOUT_SECONDS = 10;
|
||||
|
||||
private final NettyClient client;
|
||||
private final CompletableFuture<Boolean> initialLoginFuture;
|
||||
private boolean allowReconnect = true; // Default to true for network errors
|
||||
|
|
@ -150,7 +152,7 @@ public class ClientHandler extends SimpleChannelInboundHandler<Packet> {
|
|||
}
|
||||
}
|
||||
case ConsoleRequestPacket p -> {
|
||||
// Handle Minecraft command execution via CoreEvents
|
||||
// Handle Minecraft command execution via CoreEvents with callback-based completion
|
||||
StringBuilder responseBuilder = new StringBuilder();
|
||||
|
||||
CommandSender captureSender = new CommandSender() {
|
||||
|
|
@ -168,14 +170,16 @@ public class ClientHandler extends SimpleChannelInboundHandler<Packet> {
|
|||
}
|
||||
};
|
||||
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(captureSender, p.commandLine));
|
||||
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
|
||||
|
||||
// Note: Minecraft command execution is dispatched to the server thread.
|
||||
// We schedule the response to be sent after a short delay to allow for execution.
|
||||
// TODO: Consider a callback-based approach for more reliable response timing.
|
||||
ctx.channel().eventLoop().schedule(() -> {
|
||||
ctx.writeAndFlush(new ConsoleResponsePacket(p.requestId, responseBuilder.toString()));
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(captureSender, p.commandLine, completionFuture));
|
||||
|
||||
// Use the completion future with a timeout to send the response reliably
|
||||
completionFuture
|
||||
.orTimeout(CONSOLE_COMMAND_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.whenComplete((v, ex) -> {
|
||||
ctx.writeAndFlush(new ConsoleResponsePacket(p.requestId, responseBuilder.toString()));
|
||||
});
|
||||
}
|
||||
case ExecuteAutoCompleteRequestPacket p -> {
|
||||
// Handle DMCC command auto-complete with OP level filtering
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit;
|
|||
public class ConsoleCommand implements Command {
|
||||
|
||||
private static final int CONSOLE_TIMEOUT_SECONDS = 30;
|
||||
private static final int LOCAL_COMMAND_TIMEOUT_SECONDS = 10;
|
||||
private static final Map<String, CompletableFuture<ConsoleResponsePacket>> pendingRequests = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
|
|
@ -125,7 +126,7 @@ public class ConsoleCommand implements Command {
|
|||
|
||||
/**
|
||||
* Executes a Minecraft command on the local server (single_server mode).
|
||||
* Dispatches via CoreEvents.MinecraftCommandExecutionEvent.
|
||||
* Dispatches via CoreEvents.MinecraftCommandExecutionEvent with callback-based completion.
|
||||
*
|
||||
* @param sender The command sender.
|
||||
* @param args args[0] = command (may contain spaces from acceptsExtraArgs).
|
||||
|
|
@ -144,7 +145,17 @@ public class ConsoleCommand implements Command {
|
|||
|
||||
sender.reply(I18nManager.getDmccTranslation("commands.console.executing_local", commandLine));
|
||||
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(sender, commandLine));
|
||||
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
|
||||
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(sender, commandLine, completionFuture));
|
||||
|
||||
// Wait for the command to complete with a timeout
|
||||
try {
|
||||
completionFuture.get(LOCAL_COMMAND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (Exception ignored) {
|
||||
// Timeout or interruption - the command may still be running,
|
||||
// but we've already sent all output that was produced so far via the sender.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import com.xujiayao.discord_mc_chat.utils.events.CoreEvents;
|
|||
import com.xujiayao.discord_mc_chat.utils.events.EventManager;
|
||||
import com.xujiayao.discord_mc_chat.utils.i18n.I18nManager;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Proxy command for managing the server whitelist via DMCC.
|
||||
* <p>
|
||||
|
|
@ -25,6 +28,8 @@ import com.xujiayao.discord_mc_chat.utils.i18n.I18nManager;
|
|||
*/
|
||||
public class WhitelistCommand implements Command {
|
||||
|
||||
private static final int WHITELIST_COMMAND_TIMEOUT_SECONDS = 10;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "whitelist";
|
||||
|
|
@ -76,7 +81,18 @@ public class WhitelistCommand implements Command {
|
|||
}
|
||||
};
|
||||
|
||||
// Delegate to Minecraft's native /whitelist add command
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(elevatedSender, "whitelist add " + player));
|
||||
// Delegate to Minecraft's native /whitelist add command with callback-based completion
|
||||
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
|
||||
|
||||
EventManager.post(new CoreEvents.MinecraftCommandExecutionEvent(elevatedSender, "whitelist add " + player, completionFuture));
|
||||
|
||||
// Wait for the command to complete so the response is available before this method returns.
|
||||
// This is critical for remote execution (execute command) where the response is collected
|
||||
// from the sender's reply buffer after this method returns.
|
||||
try {
|
||||
completionFuture.get(WHITELIST_COMMAND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (Exception ignored) {
|
||||
// Timeout or interruption - output produced so far will still be in the sender's buffer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.xujiayao.discord_mc_chat.utils.events;
|
|||
import com.xujiayao.discord_mc_chat.commands.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Core events for communication between DMCC Core and Minecraft-specific implementations.
|
||||
|
|
@ -19,13 +20,20 @@ public class CoreEvents {
|
|||
* <p>
|
||||
* The handler should construct a virtual CommandSourceStack with the sender's OP level
|
||||
* and dispatch the command to the Minecraft command dispatcher.
|
||||
* <p>
|
||||
* The handler MUST complete the {@code completionFuture} after the command has finished
|
||||
* executing and all output has been sent to the sender. This enables reliable response
|
||||
* timing for remote command execution (console/execute commands).
|
||||
*
|
||||
* @param sender The command sender bridging the execution, used for replying results.
|
||||
* @param commandLine The raw Minecraft command line to be executed (without leading slash).
|
||||
* @param sender The command sender bridging the execution, used for replying results.
|
||||
* @param commandLine The raw Minecraft command line to be executed (without leading slash).
|
||||
* @param completionFuture A future that the handler MUST complete when command execution is done.
|
||||
* Complete with {@code null} on success, or exceptionally on failure.
|
||||
*/
|
||||
public record MinecraftCommandExecutionEvent(
|
||||
CommandSender sender,
|
||||
String commandLine
|
||||
String commandLine,
|
||||
CompletableFuture<Void> completionFuture
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ public class MinecraftEventHandler {
|
|||
EventManager.register(CoreEvents.MinecraftCommandExecutionEvent.class, event -> {
|
||||
if (serverInstance == null) {
|
||||
event.sender().reply("Minecraft server is not ready yet.");
|
||||
event.completionFuture().complete(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -229,8 +230,18 @@ public class MinecraftEventHandler {
|
|||
null
|
||||
);
|
||||
|
||||
// Must be dispatched to the main server thread to avoid concurrent modification
|
||||
serverInstance.execute(() -> serverInstance.getCommands().performPrefixedCommand(source, event.commandLine()));
|
||||
// Must be dispatched to the main server thread to avoid concurrent modification.
|
||||
// The completion future is completed after the command has been executed on the server thread,
|
||||
// ensuring all output has been sent to the sender before the response is collected.
|
||||
serverInstance.execute(() -> {
|
||||
try {
|
||||
serverInstance.getCommands().performPrefixedCommand(source, event.commandLine());
|
||||
} catch (Exception e) {
|
||||
event.sender().reply("Error executing command: " + e.getMessage());
|
||||
} finally {
|
||||
event.completionFuture().complete(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
EventManager.register(CoreEvents.MinecraftCommandAutoCompleteEvent.class, event -> {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue