Skip to content

Commit a1adce6

Browse files
committed
fix(kether): 修复异步上下文完成与线程切换
确保动作异常与取消能够结束上下文并回收运行任务,同时序列化远程读取并将计分板更新切回平台线程。
1 parent 4bced1a commit a1adce6

6 files changed

Lines changed: 507 additions & 38 deletions

File tree

‎module/minecraft/minecraft-kether/build.gradle.kts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
44

55
dependencies {
66
compileOnly(project(":common"))
7+
testImplementation(project(":common"))
78
compileOnly(project(":common-env"))
89
compileOnly(project(":common-util"))
10+
testImplementation(project(":common-util"))
911
compileOnly(project(":common-legacy-api"))
1012
compileOnly(project(":common-platform-api"))
1113
compileOnly(project(":module:minecraft:minecraft-chat"))

‎module/minecraft/minecraft-kether/src/main/java/taboolib/library/kether/AbstractQuestContext.java‎

Lines changed: 129 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.jetbrains.annotations.NotNull;
55

66
import java.util.*;
7+
import java.util.concurrent.CancellationException;
78
import java.util.concurrent.CompletableFuture;
89
import java.util.concurrent.CompletionException;
910
import java.util.concurrent.Executor;
@@ -15,8 +16,8 @@ public abstract class AbstractQuestContext<T extends AbstractQuestContext<T>> im
1516
protected final Frame rootFrame;
1617
protected final Quest quest;
1718
protected final QuestExecutor executor;
18-
protected ExitStatus exitStatus;
19-
protected CompletableFuture<Object> future;
19+
protected volatile ExitStatus exitStatus;
20+
protected volatile CompletableFuture<Object> future;
2021

2122
protected AbstractQuestContext(QuestService<T> service, Quest quest, String playerIdentifier) {
2223
this.service = service;
@@ -61,25 +62,50 @@ public Frame rootFrame() {
6162
}
6263

6364
@Override
64-
public CompletableFuture<Object> runActions() {
65+
public synchronized CompletableFuture<Object> runActions() {
6566
Preconditions.checkState(future == null, "already running");
66-
return future = rootFrame.run().thenApply(o -> {
67-
if (this.exitStatus == null) {
68-
this.exitStatus = ExitStatus.success();
67+
CompletableFuture<Object> frameFuture = rootFrame.run();
68+
CompletableFuture<Object> contextFuture = new CompletableFuture<>();
69+
frameFuture.whenComplete((result, ex) -> {
70+
if (ex != null) {
71+
completeFailure(contextFuture, ex);
72+
} else {
73+
if (this.exitStatus == null) {
74+
this.exitStatus = ExitStatus.success();
75+
}
76+
contextFuture.complete(result);
77+
}
78+
});
79+
contextFuture.whenComplete((result, ex) -> {
80+
if (contextFuture.isCancelled()) {
81+
frameFuture.cancel(false);
6982
}
70-
return o;
7183
});
84+
this.future = contextFuture;
85+
return contextFuture;
7286
}
7387

7488
@Override
75-
public void terminate() {
89+
public synchronized void terminate() {
7690
this.rootFrame.close();
7791
if (future != null) {
7892
future.completeExceptionally(new QuestCloseException());
7993
future = null;
8094
}
8195
}
8296

97+
private static void completeFailure(CompletableFuture<?> future, Throwable throwable) {
98+
Throwable cause = throwable;
99+
while (cause instanceof CompletionException && cause.getCause() != null) {
100+
cause = cause.getCause();
101+
}
102+
if (cause instanceof CancellationException) {
103+
future.cancel(false);
104+
} else {
105+
future.completeExceptionally(cause);
106+
}
107+
}
108+
83109
public static class QuestExecutor implements Executor {
84110

85111
private final AbstractQuestContext<?> questContext;
@@ -104,7 +130,7 @@ public static abstract class AbstractFrame implements Frame {
104130
protected final List<Frame> frames;
105131
protected final VarTable varTable;
106132
protected final QuestContext questContext;
107-
protected CompletableFuture<?> future;
133+
protected volatile CompletableFuture<?> future;
108134
protected final Deque<AutoCloseable> closeables = new LinkedBlockingDeque<>();
109135

110136
public AbstractFrame(Frame parent, List<Frame> frames, VarTable varTable, QuestContext questContext) {
@@ -162,12 +188,14 @@ public <T extends AutoCloseable> T addClosable(T closeable) {
162188

163189
@Override
164190
public void close() {
165-
if (this.future == null) return;
191+
CompletableFuture<?> runningFuture = this.future;
192+
if (runningFuture == null) return;
193+
this.future = null;
166194
for (Frame frame : this.frames) {
167195
frame.close();
168196
}
169197
this.cleanup();
170-
this.future = null;
198+
runningFuture.completeExceptionally(new QuestCloseException());
171199
}
172200

173201
@Override
@@ -191,6 +219,7 @@ public static class SimpleNamedFrame extends AbstractFrame {
191219
private final String name;
192220
private Quest.Block block, next;
193221
private int sp = -1, np = -1;
222+
private volatile CompletableFuture<?> runningAction;
194223

195224
public SimpleNamedFrame(Frame parent, List<Frame> frames, VarTable varTable, String name, QuestContext questContext) {
196225
super(parent, frames, varTable, questContext);
@@ -235,36 +264,100 @@ public void setNext(@NotNull Quest.Block block) {
235264
np = 0;
236265
}
237266

267+
@Override
268+
public synchronized void close() {
269+
CompletableFuture<?> actionFuture = this.runningAction;
270+
this.runningAction = null;
271+
super.close();
272+
if (actionFuture != null) {
273+
actionFuture.cancel(false);
274+
}
275+
}
276+
238277
@Override
239278
@SuppressWarnings("unchecked")
240-
public <T> CompletableFuture<T> run() {
279+
public synchronized <T> CompletableFuture<T> run() {
241280
Preconditions.checkState(this.future == null, "already running");
242281
varTable.initialize(this);
243282
future = new CompletableFuture<>();
244-
process(future);
245-
return (CompletableFuture<T>) future;
283+
CompletableFuture<?> resultFuture = future;
284+
resultFuture.whenComplete((result, ex) -> {
285+
if (resultFuture.isCancelled()) {
286+
this.close();
287+
}
288+
});
289+
process(null);
290+
return (CompletableFuture<T>) resultFuture;
246291
}
247292

248-
@SuppressWarnings("unchecked")
249-
private void process(CompletableFuture<?> future) {
293+
private synchronized void process(CompletableFuture<?> previousFuture) {
294+
CompletableFuture<?> resultFuture = this.future;
295+
if (resultFuture == null || resultFuture.isDone()) {
296+
return;
297+
}
250298
while (!context().getExitStatus().isPresent()) {
251299
this.cleanup();
252300
this.frames.removeIf(Frame::isDone);
253301
Optional<? extends ParsedAction<?>> optional = nextAction();
254-
if (optional.isPresent()) {
255-
ParsedAction<?> action = optional.get();
256-
CompletableFuture<?> newFuture = action.process(this);
257-
if (!newFuture.isDone()) {
258-
newFuture.thenRun(() -> this.process(newFuture));
259-
return;
260-
} else {
261-
future = newFuture;
262-
}
263-
} else {
264-
((CompletableFuture<Object>) this.future).complete(future != null && future.isDone() ? future.join() : null);
302+
if (!optional.isPresent()) {
303+
completeResult(resultFuture, previousFuture);
304+
return;
305+
}
306+
ParsedAction<?> action = optional.get();
307+
CompletableFuture<?> actionFuture;
308+
try {
309+
actionFuture = Objects.requireNonNull(action.process(this), "Quest action returned null future: " + action);
310+
} catch (Throwable ex) {
311+
fail(resultFuture, ex);
265312
return;
266313
}
314+
this.runningAction = actionFuture;
315+
if (!actionFuture.isDone()) {
316+
actionFuture.whenComplete((result, ex) -> resume(resultFuture, actionFuture, ex));
317+
return;
318+
}
319+
this.runningAction = null;
320+
if (actionFuture.isCancelled()) {
321+
resultFuture.cancel(false);
322+
return;
323+
}
324+
try {
325+
actionFuture.join();
326+
} catch (Throwable ex) {
327+
fail(resultFuture, ex);
328+
return;
329+
}
330+
previousFuture = actionFuture;
267331
}
332+
this.cleanup();
333+
this.frames.removeIf(Frame::isDone);
334+
completeResult(resultFuture, previousFuture);
335+
}
336+
337+
private synchronized void resume(CompletableFuture<?> resultFuture, CompletableFuture<?> actionFuture, Throwable throwable) {
338+
if (this.runningAction == actionFuture) {
339+
this.runningAction = null;
340+
}
341+
if (this.future != resultFuture || resultFuture.isDone()) {
342+
return;
343+
}
344+
if (throwable != null) {
345+
fail(resultFuture, throwable);
346+
} else {
347+
process(actionFuture);
348+
}
349+
}
350+
351+
private void fail(CompletableFuture<?> resultFuture, Throwable throwable) {
352+
this.cleanup();
353+
this.frames.removeIf(Frame::isDone);
354+
completeFailure(resultFuture, throwable);
355+
}
356+
357+
@SuppressWarnings("unchecked")
358+
private void completeResult(CompletableFuture<?> resultFuture, CompletableFuture<?> previousFuture) {
359+
Object result = previousFuture != null ? previousFuture.getNow(null) : null;
360+
((CompletableFuture<Object>) resultFuture).complete(result);
268361
}
269362

270363
private Optional<? extends ParsedAction<?>> nextAction() {
@@ -309,10 +402,17 @@ public void setNext(@NotNull Quest.Block block) {
309402

310403
@Override
311404
@SuppressWarnings("unchecked")
312-
public <T> CompletableFuture<T> run() {
405+
public synchronized <T> CompletableFuture<T> run() {
313406
Preconditions.checkState(this.future == null, "already running");
314407
this.varTable.initialize(this);
315-
return (CompletableFuture<T>) (this.future = this.action.process(this));
408+
try {
409+
this.future = Objects.requireNonNull(this.action.process(this), "Quest action returned null future: " + action);
410+
} catch (Throwable ex) {
411+
CompletableFuture<Object> failed = new CompletableFuture<>();
412+
completeFailure(failed, ex);
413+
this.future = failed;
414+
}
415+
return (CompletableFuture<T>) this.future;
316416
}
317417
}
318418

‎module/minecraft/minecraft-kether/src/main/kotlin/taboolib/module/kether/RemoteQuestReader.kt‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,44 +12,54 @@ import taboolib.library.kether.QuestReader
1212
@Suppress("UNCHECKED_CAST")
1313
class RemoteQuestReader(val remote: OpenContainer, val source: Any) : QuestReader {
1414

15+
@Synchronized
1516
override fun peek(): Char {
1617
return source.invokeMethod("peek", remap = false)!!
1718
}
1819

20+
@Synchronized
1921
override fun peek(n: Int): Char {
2022
return peekIntMethod[source].invoke(source, n) as Char
2123
}
2224

25+
@Synchronized
2326
override fun getIndex(): Int {
2427
return source.invokeMethod("getIndex", remap = false)!!
2528
}
2629

30+
@Synchronized
2731
override fun getMark(): Int {
2832
return source.invokeMethod("getMark", remap = false)!!
2933
}
3034

35+
@Synchronized
3136
override fun hasNext(): Boolean {
3237
return source.invokeMethod("hasNext", remap = false)!!
3338
}
3439

40+
@Synchronized
3541
override fun nextToken(): String {
3642
return source.invokeMethod("nextToken", remap = false)!!
3743
}
3844

45+
@Synchronized
3946
override fun mark() {
4047
source.invokeMethod<Void>("mark", remap = false)
4148
}
4249

50+
@Synchronized
4351
override fun reset() {
4452
source.invokeMethod<Void>("reset", remap = false)
4553
}
4654

55+
@Synchronized
4756
override fun <T> nextAction(): ParsedAction<T> {
4857
val action = source.invokeMethod<T>("nextAction", remap = false)!!
4958
val questAction = RemoteQuestAction<T>(remote, action.getProperty<Any>("action", remap = false)!!)
5059
return ParsedAction(questAction, action.getProperty<Map<String, Any>>("properties", remap = false)!!)
5160
}
5261

62+
@Synchronized
5363
override fun <T : Any?> nextAction(namespace: String?): ParsedAction<T> {
5464
return try {
5565
val action = nextActionStringMethod[source].invoke(source, namespace)!!
@@ -60,6 +70,7 @@ class RemoteQuestReader(val remote: OpenContainer, val source: Any) : QuestReade
6070
}
6171
}
6272

73+
@Synchronized
6374
override fun expect(value: String) {
6475
expectMethod[source].invoke(source, value)
6576
}

0 commit comments

Comments
 (0)