net.diversionmc.async.Promise Maven / Gradle / Ivy
Show all versions of promise Show documentation
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);
}
}