All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.diversionmc.async.Promise Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
package net.diversionmc.async;

import net.diversionmc.async.schedule.LoopState;
import net.diversionmc.async.schedule.Scheduler;
import net.diversionmc.async.schedule.ThreadPoolScheduler;
import net.diversionmc.async.schedule.ThreadPoolScheduler.DummyTask;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static java.util.Arrays.stream;
import static java.util.Map.entry;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static net.diversionmc.async.schedule.ThreadPoolScheduler.SCHEDULER;
import static net.diversionmc.async.schedule.ThreadPoolScheduler.allowedAwait;

/**
 * 

* Promise is an async result supplier. The type of result is either defined or ? (always null). * *

* This API is inspired by Java's {@link Optional Optional API} and {@link Stream Stream API} and ECMAScript's Promises API. * Most of the features are made to be on parity with Optionals and Streams (and the method names are similar), * and the main idea is same as in ECMAScript's version (but the method names are different). * *

* Promises return the result some time later after its creation. * A currently evaluating promise is {@link #pending() pending}, and after it is done it is {@link #settled() settled}. * *

* Promises can be chained. * The awaited promise is called a parent, and following promises use the result from the parent. * A promise that has no parent is independent. * Chaining does not necessarily mean order of operations (although it is often the case), * but rather that making a step is allowed only after the parent promise is settled. * *

* There are 5 types of methods promises supply: *

    *
  • Start methods - generate new independent promises.
  • *
  • Modify methods - make promises that use the result from the parent.
  • *
  • Then (new start) methods - generate new start promises with parents.
  • *
  • Flat modify methods - generate new promises in a function and use their result.
  • *
  • Flat then/start methods - generate new start promises in a function.
  • *
* Methods also divide into passthrough and new result. * Passthrough methods are guaranteed to return the result of parent chain. * *

* The start method types are as follows: *

    *
  • Passthrough (when chained):
      *
    • {@link #run(Runnable) run} - Starts a new promise with no result.
    • *
    • {@link #after(Promise[]) after} - Awaits for supplied promises group.
    • *
    • {@link #repeat(long, TimeUnit, Consumer) repeat} - Creates a repeating schedule promise.
    • *
  • *
  • New result:
      *
    • {@link #get(Supplier) get} - Starts a new promise with a result supplied.
    • *
    • {@link #reference() reference} (no thenReference) - Creates a promise that you may settle from anywhere else.
    • *
    • {@link #batch(Promise[]) batch} - Awaits for supplied promises group results of a common type.
    • *
  • *
* Promise groups are evaluated in parallel. * *

* The modify method types are as follows: *

    *
  • New result:
      *
    • {@link #map(Function) map} - Gives a new result promise using parent result.
    • *
  • *
  • Passthrough:
      *
    • {@link #peek(Consumer) peek} - Performs an action using the parent result and passes that result over.
    • *
  • *
* *

* Use net.diversionmc:result for proper error handling. Promises simply print out exceptions instead of stopping. * * @param Resulting object type, or ? if promise supplies no result. */ public final class Promise { public static final int MINECRAFT_TICKS_TO_MILLIS = 50; private R result; // // Task and waiting // private final Future task; private Promise(Scheduler scheduler, Optional> parent, BiConsumer, Promise> asyncAction) { Runnable threadPoolRunnable = () -> { parent.ifPresent(Promise::await); // wait for parent to settle (if not settled already) - never throws because always in thread pool if (scheduler instanceof ThreadPoolScheduler) // both current thread and new task (when it would be scheduled) would be in thread pool asyncAction.accept(parent.orElse(null), this); else try { scheduler .schedule(() -> asyncAction.accept(parent.orElse(null), this)) .get(); // and wait for asyncAction to finish for current submit to count as completed } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }; if (allowedAwait()) { var task = new DummyTask(); this.task = task; threadPoolRunnable.run(); task.quietlyComplete(); } else task = SCHEDULER.schedule(threadPoolRunnable); } private Promise(Consumer> complete) { var task = new DummyTask(); this.task = task; complete.accept(result -> { synchronized (Promise.this) { if (settled()) throw new PromiseException(new IllegalStateException("Attempt to resolve a reference promise more than once")); this.result = result; task.quietlyComplete(); } }); } private Promise(R result) { var task = new DummyTask(); this.task = task; this.result = result; task.quietlyComplete(); } /** * Await for promise to finish by blocking this thread. * Works only in {@link ThreadPoolScheduler} threads. * * @return Promise result. * @throws PromiseException Awaiting when not allowed or on underlying execution exception. */ public R await() throws PromiseException { if (!allowedAwait()) throw new PromiseException(new IllegalStateException("Await called from a non-pool thread")); try { task.get(); } catch (InterruptedException ignored) { } catch (ExecutionException e) { throw new PromiseException(e.getCause()); } return result; } /** * Try to get the result if this promise has settled. * * @return Optional of the result of the promise. */ public Optional result() { // pending check for passthrough promises that have not completed yet but set result already return pending() ? empty() : ofNullable(result); } /** * Check if promise is not done evaluating the result yet. * * @return Pending state. */ public boolean pending() { return !task.isDone(); } /** * Check if promise is no longer {@link #pending() pending}. * * @return Settled state. */ public boolean settled() { return !pending(); } // // Start // // // run() /** * Start a new promise without results. * * @param handle Action to perform. */ public static Promise run(Runnable handle) { return run(SCHEDULER, handle); } /** * Start a new promise without results. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. */ public static Promise run(Scheduler scheduler, Runnable handle) { return new Promise<>( scheduler, empty(), (from, to) -> handle.run()); } // after() /** * Start a new promise waiting for a promise group to finish. * * @param promises Promises to wait for. */ public static Promise after(Promise... promises) { return after(stream(promises)); } /** * Start a new promise waiting for a promise group to finish. * * @param promises Promises to wait for. */ public static Promise after(Collection> promises) { return after(promises.stream()); } /** * Start a new promise waiting for a promise group to finish. * * @param promises Promises to wait for. */ public static Promise after(Stream> promises) { //noinspection ResultOfMethodCallIgnored return new Promise<>( SCHEDULER, empty(), (from, to) -> promises.parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList()); // by calling toList and hence terminating the stream } // repeat() /** * Start a new repeating schedule promise. * * @param ticksPeriod Period between iterations, in Minecraft ticks (1/20 second). * @param handle Action to perform. */ public static Promise repeat(long ticksPeriod, Consumer handle) { return repeat(ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS, handle); } /** * Start a new repeating schedule promise. * * @param period Period between iterations. * @param unit Period unit. * @param handle Action to perform. */ public static Promise repeat(long period, TimeUnit unit, Consumer handle) { return repeat(SCHEDULER, period, unit, handle); } /** * Start a new repeating schedule promise. * * @param scheduler Thread supplier to run on. * @param ticksPeriod Period between iterations, in Minecraft ticks (1/20 second). * @param handle Action to perform. */ public static Promise repeat(Scheduler scheduler, long ticksPeriod, Consumer handle) { return repeat(scheduler, ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS, handle); } /** * Start a new repeating schedule promise. * * @param scheduler Thread supplier to run on. * @param period Period between iterations. * @param unit Period unit. * @param handle Action to perform. */ public static Promise repeat(Scheduler scheduler, long period, TimeUnit unit, Consumer handle) { return new Promise<>( SCHEDULER, empty(), (from, to) -> { try { scheduler .scheduleRepeating(period, unit, handle) .get(); // wait for scheduleRepeating to finish for current promise to count as completed } catch (InterruptedException ignored) { // fine to ignore since repeat gives no result } catch (ExecutionException e) { throw new PromiseException(e.getCause()); } }); } // delay() /** * Promise that awaits for a certain amount of time. * * @param ticksPeriod Period to await for, in Minecraft ticks (1/20 second). */ public static Promise delay(long ticksPeriod) { return delay(ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS); } /** * Promise that awaits for a certain amount of time. * * @param period Period to await for. * @param unit Period unit. */ public static Promise delay(long period, TimeUnit unit) { return new Promise<>( SCHEDULER, empty(), (from, to) -> { try { var lock = SCHEDULER.runLock(); if (lock.tryLock(period, unit)) lock.unlock(); } catch (InterruptedException ignored) { } }); } // // of() /** * Skip starting anything and just get a settled promise of given value. * * @param value Value to put inside. * @param New result type. */ public static Promise of(N value) { return new Promise<>(value); } // get() /** * Start a new promise giving a result. * * @param handle Action to perform. * @param New result type. */ public static Promise get(Supplier handle) { return get(SCHEDULER, handle); } /** * Start a new promise giving a result. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. * @param New result type. */ public static Promise get(Scheduler scheduler, Supplier handle) { return new Promise<>( scheduler, empty(), (from, to) -> to.result = handle.get()); } // reference() /** * Create a promise that will be resolved by any thread that calls the setter. * * @param The reference type. * @return A pair of a promise and a function that will settle that promise. */ public static Entry, Consumer> reference() { var atomic = new AtomicReference>(); var promise = new Promise<>(atomic::set); return entry(promise, atomic.get()); } // batch() /** * Start a promise waiting for results from a promise group of a common type. * * @param promises Promises to wait for. * @param New result type. */ @SafeVarargs public static Promise> batch(Promise... promises) { return batch(stream(promises)); } /** * Start a promise waiting for results from a promise group of a common type. * * @param promises Promises to wait for. * @param New result type. */ public static Promise> batch(Collection> promises) { return batch(promises.stream()); } /** * Start a promise waiting for results from a promise group of a common type. * * @param promises Promises to wait for. * @param New result type. */ public static Promise> batch(Stream> promises) { return new Promise<>( SCHEDULER, empty(), (from, to) -> to.result = promises.parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList().stream()); // by calling toList and hence terminating the stream } // // Modify result // // // map() /** * Transform a value. * * @param handle Action to perform. * @param New result type. */ public Promise map(Function handle) { return map(SCHEDULER, handle); } /** * Transform a value. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. * @param New result type. */ public Promise map(Scheduler scheduler, Function handle) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> to.result = handle.apply(from.result)); } // // peek() /** * Perform an action using result of this promise and pass the result over. * * @param handle Action to perform. */ public Promise peek(Consumer handle) { return peek(SCHEDULER, handle); } /** * Perform an action using result of this promise and pass the result over. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. */ public Promise peek(Scheduler scheduler, Consumer handle) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> handle.accept(to.result = from.result)); } // // Discard result // // // thenRun() /** * Start a new promise without results after this promise is {@link #settled() settled}. * * @param handle Action to perform. */ public Promise thenRun(Runnable handle) { return thenRun(SCHEDULER, handle); } /** * Start a new promise without results after this promise is {@link #settled() settled}. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. */ public Promise thenRun(Scheduler scheduler, Runnable handle) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> { to.result = from.result; handle.run(); }); } // thenAfter() /** * Start a new promise waiting for a promise group to finish after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. */ public Promise thenAfter(Promise... promises) { return thenAfter(stream(promises)); } /** * Start a new promise waiting for a promise group to finish after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. */ public Promise thenAfter(Collection> promises) { return thenAfter(promises.stream()); } /** * Start a new promise waiting for a promise group to finish after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. */ public Promise thenAfter(Stream> promises) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> { to.result = from.result; //noinspection ResultOfMethodCallIgnored promises.parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList(); // by calling toList and hence terminating the stream }); } // thenRepeat() /** * Start a new repeating schedule promise after this promise is {@link #settled() settled}. * * @param ticksPeriod Period between iterations, in Minecraft ticks (1/20 second). * @param handle Action to perform. */ public Promise thenRepeat(long ticksPeriod, Consumer handle) { return thenRepeat(ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS, handle); } /** * Start a new repeating schedule promise after this promise is {@link #settled() settled}. * * @param period Period between iterations. * @param unit Period unit. * @param handle Action to perform. */ public Promise thenRepeat(long period, TimeUnit unit, Consumer handle) { return thenRepeat(SCHEDULER, period, unit, handle); } /** * Start a new repeating schedule promise after this promise is {@link #settled() settled}. * * @param scheduler Thread supplier to run on. * @param ticksPeriod Period between iterations, in Minecraft ticks (1/20 second). * @param handle Action to perform. */ public Promise thenRepeat(Scheduler scheduler, long ticksPeriod, Consumer handle) { return thenRepeat(scheduler, ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS, handle); } /** * Start a new repeating schedule promise after this promise is {@link #settled() settled}. * * @param scheduler Thread supplier to run on. * @param period Period between iterations. * @param unit Period unit. * @param handle Action to perform. */ public Promise thenRepeat(Scheduler scheduler, long period, TimeUnit unit, Consumer handle) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> { to.result = from.result; try { scheduler .scheduleRepeating(period, unit, handle) .get(); // and wait for scheduleRepeating to finish for current submit to count as completed } catch (InterruptedException ignored) { // fine to ignore since repeat gives no result } catch (ExecutionException e) { e.printStackTrace(); } }); } // thenDelay() /** * Promise that awaits for a certain amount of time after this promise is {@link #settled() settled}. * * @param ticksPeriod Period to await for, in Minecraft ticks (1/20 second). */ public Promise thenDelay(long ticksPeriod) { return thenDelay(ticksPeriod * MINECRAFT_TICKS_TO_MILLIS, MILLISECONDS); } /** * Promise that awaits for a certain amount of time after this promise is {@link #settled() settled}. * * @param period Period to await for. * @param unit Period unit. */ public Promise thenDelay(long period, TimeUnit unit) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> { to.result = from.result; try { var lock = SCHEDULER.runLock(); if (lock.tryLock(period, unit)) lock.unlock(); } catch (InterruptedException ignored) { } }); } // // thenOf() /** * Start a new promise giving a result after this promise is {@link #settled() settled}. * * @param value Value to put inside. * @param New result type. */ public Promise thenOf(N value) { return thenOf(SCHEDULER, value); } /** * Start a new promise giving a result after this promise is {@link #settled() settled}. * * @param scheduler Thread supplier to run on. * @param value Value to put inside. * @param New result type. */ public Promise thenOf(Scheduler scheduler, N value) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> to.result = value); } // thenGet() /** * Start a new promise giving a result after this promise is {@link #settled() settled}. * * @param handle Action to perform. * @param New result type. */ public Promise thenGet(Supplier handle) { return thenGet(SCHEDULER, handle); } /** * Start a new promise giving a result after this promise is {@link #settled() settled}. * * @param scheduler Thread supplier to run on. * @param handle Action to perform. * @param New result type. */ public Promise thenGet(Scheduler scheduler, Supplier handle) { return new Promise<>( scheduler, Optional.of(this), (from, to) -> to.result = handle.get()); } // thenBatch() /** * Start a promise waiting for results from a promise group of a common type after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. * @param New result type. */ @SafeVarargs // SafeVarargs requires method to be final, even though the class itself is final public final Promise> thenBatch(Promise... promises) { return thenBatch(stream(promises)); } /** * Start a promise waiting for results from a promise group of a common type after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. * @param New result type. */ public Promise> thenBatch(Collection> promises) { return thenBatch(promises.stream()); } /** * Start a promise waiting for results from a promise group of a common type after this promise is {@link #settled() settled}. * * @param promises Promises to wait for. * @param New result type. */ public Promise> thenBatch(Stream> promises) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> to.result = promises.parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList().stream()); // by calling toList and hence terminating the stream } // // Flat modify result // // // flatMap() /** * Transform a value into a promise and get the resulting value. * * @param handle Action to perform. * @param New result type. */ public Promise flatMap(Function> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> to.result = handle .apply(from.result) .await()); } // // flatPeek() /** * Start a new promise that uses result of this promise and passes result over. * * @param handle Action to perform. */ public Promise flatPeek(Function> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> { to.result = from.result; handle .apply(from.result) .await(); // result ignored }); } // // Flat discard result // // // flatRun() /** * Start a new promise waiting for this promise and the supplied promise. * * @param handle Action to perform. */ public Promise flatRun(Supplier> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> { to.result = from.result; handle.get().await(); // result ignored }); } // flatAfter() /** * Start a new promise waiting for this promise and the supplied stream of promises. * * @param handle Action to perform. */ public Promise flatAfter(Supplier>> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> { to.result = from.result; //noinspection ResultOfMethodCallIgnored handle.get().parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList(); // by calling toList and hence terminating the stream }); } // // flatGet() /** * Start a new promise supplying a new result after this promise and the supplied promise. * * @param handle Action to perform. * @param New result type. */ public Promise flatGet(Supplier> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> to.result = handle.get().await()); } // flatBatch() /** * Start a new promise waiting for the results from the supplied stream of promises after this promise is {@link #settled() settled}. * * @param handle Action to perform. * @param New result type. */ public Promise> flatBatch(Supplier>> handle) { return new Promise<>( SCHEDULER, Optional.of(this), (from, to) -> to.result = handle.get().parallel() .map(Promise::await) // ensure await() happens inside the asyncAction block .toList().stream()); // by calling toList and hence terminating the stream } // // Stream collectors // /** * Collect all promises to await upon into a new promise. * * @return Collector to an awaiting promise. */ public static Collector, ArrayList>, Promise> toAfter() { return Collector.of( ArrayList::new, ArrayList::add, (p1, p2) -> { p1.addAll(p2); return p1; }, Promise::after); } /** * Collect all results of the promise stream into a single result stream promise. * * @param Result type. * @return Collector to a result stream promise. */ public static Collector, ArrayList>, Promise>> toBatch() { return Collector.of( ArrayList::new, ArrayList::add, (p1, p2) -> { p1.addAll(p2); return p1; }, Promise::batch); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy