Skip to content

Instantly share code, notes, and snippets.

@Kiskae
Created July 11, 2015 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kiskae/5c44ea146857ca173170 to your computer and use it in GitHub Desktop.
Save Kiskae/5c44ea146857ca173170 to your computer and use it in GitHub Desktop.
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.api.Game;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.service.scheduler.SchedulerService;
import org.spongepowered.api.service.scheduler.Task;
import org.spongepowered.api.service.scheduler.TaskBuilder;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.concurrent.*;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A delegating ExecutionService that schedules all its tasks on Sponge's {@link SchedulerService}.
* Any tasks scheduled through this service will run synchronously on the main Server Thread and will thus be able to
* safely modify game data.
* <br />
* When using this ExecutorService with other asynchronous libraries, be careful of systems that inherit the
* ExecutorService from their parents, since any child tasks will also run on Sponge's main thread.
* <br />
* In terms of contracts this ExecutorService cannot be shut down since it does not directly control the execution
* environment.
*
* @author Kiskae <kiskae@serverpeon.net>
*/
public class SpongeExecutorService extends AbstractExecutorService implements ScheduledExecutorService {
private final static Logger log = LoggerFactory.getLogger(SpongeExecutorService.class);
private final SchedulerService scheduler;
private final PluginContainer plugin;
private final Supplier<String> taskNamer;
private SpongeExecutorService(
@Nonnull SchedulerService scheduler,
@Nonnull PluginContainer plugin,
@Nullable Supplier<String> taskNamer
) {
this.scheduler = scheduler;
this.plugin = plugin;
this.taskNamer = taskNamer;
}
/**
* Utility method to create a ExecutorService using the given {@link SchedulerService} with named tasks tied to
* the given {@link PluginContainer}.
*
* @param service Service on which the tasks will be scheduled.
* @param pc The plugin to which the tasks scheduled by this executor will be tied.
* @param taskNamer Supplier of custom names for the tasks that are scheduled, if null it uses the default naming
* scheme defined by {@link TaskBuilder#name(String)}
* @return An executor that will schedule all executions on the given {@link SchedulerService}
*/
@Nonnull
public static SpongeExecutorService create(
@Nonnull SchedulerService service,
@Nonnull PluginContainer pc,
@Nullable Supplier<String> taskNamer
) {
return new SpongeExecutorService(
checkNotNull(service, "service == NULL"),
checkNotNull(pc, "pc == NULL"),
taskNamer
);
}
/**
* Utility method to create a ExecutorService using the given {@link SchedulerService} with tasks tied to
* the given {@link PluginContainer}.
*
* @param service Service on which the tasks will be scheduled.
* @param pc The plugin to which the tasks scheduled by this executor will be tied.
* @return An executor that will schedule all executions on the given {@link SchedulerService}
*/
@Nonnull
public static SpongeExecutorService create(
@Nonnull SchedulerService service,
@Nonnull PluginContainer pc
) {
return create(service, pc, null);
}
/**
* Utility method to create a ExecutorService using Sponge's default {@link SchedulerService} with tasks tied to
* the given {@link PluginContainer}.
*
* @param game Instance of the current Sponge {@link Game}
* @param pc The plugin to which the tasks scheduled by this executor will be tied.
* @return An executor that will schedule all executions on Sponge's {@link SchedulerService}
*/
@Nonnull
public static SpongeExecutorService create(
@Nonnull Game game,
@Nonnull PluginContainer pc
) {
return create(checkNotNull(game, "game == NULL").getScheduler(), pc);
}
@Override
public void shutdown() {
//NOOP
}
@Override
public List<Runnable> shutdownNow() {
return ImmutableList.of();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void execute(Runnable command) {
this.setupTask(command)
.submit(this.plugin.getInstance());
}
@Override
public SpongeFuture<?> schedule(final Runnable command, long delay, TimeUnit unit) {
final ListenableFutureTask<?> runnable = ListenableFutureTask.create(command, null);
final Task task = this.setupTask(runnable)
.delay(delay, unit)
.submit(this.plugin.getInstance());
return new SpongeFuture<>(runnable, task);
}
@Override
public <V> SpongeFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
final ListenableFutureTask<V> runnable = ListenableFutureTask.create(callable);
final Task task = this.setupTask(runnable)
.delay(delay, unit)
.submit(this.plugin.getInstance());
return new SpongeFuture<>(runnable, task);
}
@Override
public SpongeFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
final ListenableFutureTask<?> runnable = ListenableFutureTask.create(command, null);
final Task task = this.setupTask(runnable)
.delay(initialDelay, unit)
.interval(period, unit)
.submit(this.plugin.getInstance());
return new SpongeFuture<>(runnable, task);
}
/**
* Behaves exactly like {@link #scheduleAtFixedRate(Runnable, long, long, TimeUnit)} since the backing scheduling
* is not customizable enough to make the distinction.
* <br />
* {@inheritDoc}
*/
@Override
public SpongeFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
//Since we dont have full control over the execution, the contract needs to be a little broken
return this.scheduleAtFixedRate(command, initialDelay, delay, unit);
}
private TaskBuilder setupTask(Runnable run) {
log.debug("Scheduling runnable for {}: {}", plugin.getName(), run);
final TaskBuilder builder = this.scheduler.getTaskBuilder()
.execute(run);
if (taskNamer != null) {
return builder.name(this.taskNamer.get());
} else {
return builder;
}
}
/**
* A {@link ScheduledFuture} that is backed by Sponge's {@link Task}
*/
public static class SpongeFuture<V> implements ScheduledFuture<V> {
private final ListenableFuture<V> runnable;
private final Task task;
private SpongeFuture(ListenableFuture<V> runnable, Task task) {
this.runnable = runnable;
this.task = task;
}
/**
* @return The underlying task that was submitted to {@link SchedulerService}
*/
@Nonnull
public Task getTask() {
return this.task;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.task.getDelay(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return this.runnable.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return this.runnable.isCancelled();
}
@Override
public boolean isDone() {
return this.runnable.isDone();
}
@Override
public V get() throws InterruptedException, ExecutionException {
return this.runnable.get();
}
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return this.runnable.get(timeout, unit);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment