Skip to content

Commit

Permalink
Extract Command API out of the 'Commands' class (breaking change), ad…
Browse files Browse the repository at this point in the history
…d Promise API
  • Loading branch information
lucko committed Sep 25, 2017
1 parent b3c49c0 commit ec9b91a
Show file tree
Hide file tree
Showing 32 changed files with 2,793 additions and 1,195 deletions.
125 changes: 114 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ A utility to reduce boilerplate code in Bukkit plugins. It gets boring writing t
## Feature Overview

* [`Events`](#events) - functional event handling and flexible listener registration
* [`Scheduler`](#scheduler) - scheduler programming with CompletableFutures
* [`Scheduler`](#scheduler) - easy access to the Bukkit scheduler
* [`Promise`](#promise) - a chain of operations (Futures) executing between both sync and async threads
* [`Metadata`](#metadata) - metadata with generic types, automatically expiring values and more
* [`Messenger`](#messenger) - message channel abstraction
* [`Commands`](#commands) - create commands using the builder pattern
Expand Down Expand Up @@ -136,7 +137,7 @@ Scheduler.runTaskRepeatingSync(task -> {
}, 20L, 20L);
```

Completion stages can be cool too.
Completion stages can be used to chain futures together.
```java
Scheduler.callAsync(() -> {

Expand All @@ -153,6 +154,108 @@ Scheduler.callAsync(() -> {
```


### [`Promise`](https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/promise/Promise.java)
A `Promise` is an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is yet incomplete.

The concept very closely resembles the Java [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) and [`CompletableFuture`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) APIs.

The main differences between CompletableFutures and Promises are:

* The ability to switch seamlessly between the main 'Server thread' and asynchronous tasks
* The ability to delay an action by a number of game ticks

Promises are really easy to use. To demonstrate how they work, consider this simple reward system. The task flow looks a bit like this.

1. Announce to players that rewards are going to be given out. (sync)
2. Get a list of usernames who are due a reward (async)
3. Convert these usernames to UUIDs (async)
4. Get a set of Player instances for the given UUIDs (sync)
5. Give out the rewards to the online players (sync)
6. Notify the reward storage that these players have been given a reward

Using the Promise API, this might look something like this...

```java
RewardStorage storage = getRewardStorage();

Promise<List<Player>> rewardPromise = Promise.start()
.thenRunSync(() -> Bukkit.broadcastMessage("Getting ready to reward players in 10 seconds!"))
// wait 10 seconds, then start
.thenRunDelayedSync(() -> Bukkit.broadcastMessage("Starting now!"), 200L)
// get the players which need to be rewarded
.thenApplyAsync(n -> storage.getPlayersToReward())
// convert to uuids
.thenApplyAsync(storage::getUuidsFromUsernames)
// get players from the uuids
.thenApplySync(uuids -> {
List<Player> players = new ArrayList<>();
for (UUID uuid : uuids) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
players.add(player);
}
}
return players;
});

// give out the rewards sync
rewardPromise.thenAcceptSync(players -> {
for (Player player : players) {
storage.giveReward(player);
}
});

// notify
rewardPromise.thenAcceptSync(players -> {
for (Player player : players) {
storage.announceSuccess(player.getUniqueId());
}
});
```

However, then consider how this might look if you were just using nested runnables...

```java
RewardStorage storage = getRewardStorage();

Bukkit.getScheduler().runTask(this, () -> {

Bukkit.broadcastMessage("Getting ready to reward players in 10 seconds!");

Bukkit.getScheduler().runTaskLater(this, () -> {
Bukkit.broadcastMessage("Starting now!");

Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
List<String> playersToReward = storage.getPlayersToReward();
Set<UUID> uuids = storage.getUuidsFromUsernames(playersToReward);

Bukkit.getScheduler().runTask(this, () -> {
List<Player> players = new ArrayList<>();
for (UUID uuid : uuids) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
players.add(player);
}
}

for (Player player : players) {
storage.giveReward(player);
}

Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
for (Player player : players) {
storage.announceSuccess(player.getUniqueId());
}
});
});
});
}, 200L);
});
```

I'll leave it for you to decide which is better. :smile:


### [`Metadata`](https://github.com/lucko/helper/tree/master/helper/src/main/java/me/lucko/helper/metadata)
helper provides an alternate system to the Bukkit Metadata API. The main benefits over Bukkit are the use of generic types and automatically expiring, weak or soft values.

Expand Down Expand Up @@ -285,7 +388,7 @@ public class GlobalMessengerPlugin extends ExtendedJavaPlugin {
You can either integrate messenger into your own existing messaging system (using [`AbstractMessenger`](https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/messaging/AbstractMessenger.java), or, use **helper-redis**, which implements Messenger using Jedis and the Redis PubSub system.


### [`Commands`](https://github.com/lucko/helper/blob/master/helper/src/main/java/me/lucko/helper/Commands.java)
### [`Commands`](https://github.com/lucko/helper/tree/master/helper/src/main/java/me/lucko/helper/command)
helper provides a very simple command abstraction, designed to reduce some of the boilerplate needed when writing simple commands.

Specifically:
Expand Down Expand Up @@ -760,7 +863,7 @@ Then, you can add dependencies for each helper module.
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper</artifactId>
<version>2.1.6</version>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand All @@ -769,7 +872,7 @@ Then, you can add dependencies for each helper module.
#### Gradle
```gradle
dependencies {
compile ("me.lucko:helper:2.1.6")
compile ("me.lucko:helper:3.0.0")
}
```

Expand All @@ -780,7 +883,7 @@ dependencies {
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper-sql</artifactId>
<version>1.0.3</version>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand All @@ -789,7 +892,7 @@ dependencies {
#### Gradle
```gradle
dependencies {
compile ("me.lucko:helper-sql:1.0.3")
compile ("me.lucko:helper-sql:1.0.4")
}
```

Expand All @@ -800,7 +903,7 @@ dependencies {
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper-redis</artifactId>
<version>1.0.4</version>
<version>1.0.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand All @@ -809,7 +912,7 @@ dependencies {
#### Gradle
```gradle
dependencies {
compile ("me.lucko:helper-redis:1.0.4")
compile ("me.lucko:helper-redis:1.0.5")
}
```

Expand All @@ -820,7 +923,7 @@ dependencies {
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper-mongo</artifactId>
<version>1.0.1</version>
<version>1.0.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand All @@ -829,6 +932,6 @@ dependencies {
#### Gradle
```gradle
dependencies {
compile ("me.lucko:helper-mongo:1.0.1")
compile ("me.lucko:helper-mongo:1.0.2")
}
```
107 changes: 105 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
## Feature Overview

* [`Events`](#events) - functional event handling and flexible listener registration
* [`Scheduler`](#scheduler) - scheduler programming with CompletableFutures
* [`Scheduler`](#scheduler) - easy access to the Bukkit scheduler
* [`Promise`](#promise) - a chain of operations (Futures) executing between both sync and async threads
* [`Metadata`](#metadata) - metadata with generic types, automatically expiring values and more
* [`Messenger`](#messenger) - message channel abstraction
* [`Commands`](#commands) - create commands using the builder pattern
Expand Down Expand Up @@ -134,7 +135,7 @@ Scheduler.runTaskRepeatingSync(task -> {
}, 20L, 20L);
```

Completion stages can be cool too.
Completion stages can be used to chain futures together.
```java
Scheduler.callAsync(() -> {

Expand All @@ -151,6 +152,108 @@ Scheduler.callAsync(() -> {
```


### Promise
A `Promise` is an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is yet incomplete.

The concept very closely resembles the Java [`CompletionStage`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html) and [`CompletableFuture`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) APIs.

The main differences between CompletableFutures and Promises are:

* The ability to switch seamlessly between the main 'Server thread' and asynchronous tasks
* The ability to delay an action by a number of game ticks

Promises are really easy to use. To demonstrate how they work, consider this simple reward system. The task flow looks a bit like this.

1. Announce to players that rewards are going to be given out. (sync)
2. Get a list of usernames who are due a reward (async)
3. Convert these usernames to UUIDs (async)
4. Get a set of Player instances for the given UUIDs (sync)
5. Give out the rewards to the online players (sync)
6. Notify the reward storage that these players have been given a reward

Using the Promise API, this might look something like this...

```java
RewardStorage storage = getRewardStorage();

Promise<List<Player>> rewardPromise = Promise.start()
.thenRunSync(() -> Bukkit.broadcastMessage("Getting ready to reward players in 10 seconds!"))
// wait 10 seconds, then start
.thenRunDelayedSync(() -> Bukkit.broadcastMessage("Starting now!"), 200L)
// get the players which need to be rewarded
.thenApplyAsync(n -> storage.getPlayersToReward())
// convert to uuids
.thenApplyAsync(storage::getUuidsFromUsernames)
// get players from the uuids
.thenApplySync(uuids -> {
List<Player> players = new ArrayList<>();
for (UUID uuid : uuids) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
players.add(player);
}
}
return players;
});

// give out the rewards sync
rewardPromise.thenAcceptSync(players -> {
for (Player player : players) {
storage.giveReward(player);
}
});

// notify
rewardPromise.thenAcceptSync(players -> {
for (Player player : players) {
storage.announceSuccess(player.getUniqueId());
}
});
```

However, then consider how this might look if you were just using nested runnables...

```java
RewardStorage storage = getRewardStorage();

Bukkit.getScheduler().runTask(this, () -> {

Bukkit.broadcastMessage("Getting ready to reward players in 10 seconds!");

Bukkit.getScheduler().runTaskLater(this, () -> {
Bukkit.broadcastMessage("Starting now!");

Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
List<String> playersToReward = storage.getPlayersToReward();
Set<UUID> uuids = storage.getUuidsFromUsernames(playersToReward);

Bukkit.getScheduler().runTask(this, () -> {
List<Player> players = new ArrayList<>();
for (UUID uuid : uuids) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
players.add(player);
}
}

for (Player player : players) {
storage.giveReward(player);
}

Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
for (Player player : players) {
storage.announceSuccess(player.getUniqueId());
}
});
});
});
}, 200L);
});
```

I'll leave it for you to decide which is better. :smile:


### Metadata
helper provides an alternate system to the Bukkit Metadata API. The main benefits over Bukkit are the use of generic types and automatically expiring, weak or soft values.

Expand Down
4 changes: 2 additions & 2 deletions helper-lilypad/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<artifactId>helper-lilypad</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<version>1.0.1</version>

<name>helper-lilypad</name>
<description>Implements the helper Messaging system using LilyPad.</description>
Expand Down Expand Up @@ -56,7 +56,7 @@
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper</artifactId>
<version>2.1.0</version>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
4 changes: 2 additions & 2 deletions helper-mongo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<artifactId>helper-mongo</artifactId>
<packaging>jar</packaging>
<version>1.0.1</version>
<version>1.0.2</version>

<name>helper-mongo</name>
<description>Provides MongoDB datasources.</description>
Expand Down Expand Up @@ -138,7 +138,7 @@
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper</artifactId>
<version>2.1.0</version>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
4 changes: 2 additions & 2 deletions helper-redis/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<artifactId>helper-redis</artifactId>
<packaging>jar</packaging>
<version>1.0.4</version>
<version>1.0.5</version>

<name>helper-redis</name>
<description>Provides Redis clients and implements the helper Messaging system using Jedis.</description>
Expand Down Expand Up @@ -134,7 +134,7 @@
<dependency>
<groupId>me.lucko</groupId>
<artifactId>helper</artifactId>
<version>2.1.0</version>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Loading

0 comments on commit ec9b91a

Please sign in to comment.