44import org .jetbrains .annotations .NotNull ;
55
66import java .util .*;
7+ import java .util .concurrent .CancellationException ;
78import java .util .concurrent .CompletableFuture ;
89import java .util .concurrent .CompletionException ;
910import 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
0 commit comments