Programador

Sponge expone el :javadoc: Scheduler para permitirle designar tareas que se ejecutarán en el futuro. El `` Scheduler`` proporciona un :javadoc: Task.Builder con el que puede especificar propiedades de tareas tales como la demora, el intervalo, el nombre, la (a)sincronicidad y el Runnable` (ver task -properties).

Compilador de tareas

Primero, obtén una instancia del Scheduler, y recupera el Task.Builder:

import org.spongepowered.api.scheduler.Scheduler;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.Sponge;

Scheduler scheduler = Sponge.getScheduler();
Task.Builder taskBuilder = scheduler.createTaskBuilder();

La unica propiedad requerida es el Runnable, el cual puedes especificar usando Task.Builder#execute(Runnable):

taskBuilder.execute(new Runnable() {
    public void run() {
        logger.info("Yay! Schedulers!");
    }
});

o usando la sintaxis de Java 8 con `` Task.Builder#execute(Runnable runnable) ``

taskBuilder.execute(
    () -> {
        logger.info("Yay! Schedulers!");
    }
);

o usando la sintaxis de Java 8 con la tarea `` Task.Builder#execute(Consumer<Task>task) ``

taskBuilder.execute(
    task -> {
        logger.info("Yay! Schedulers! :" + task.getName());
    }
);

Propiedades de tareas

Usando el Task.Builder, puede especificar otras propiedades opcionales, como se describe a continuación.

Propiedad Método Usado Descripción
retraso

delayTicks(retraso largo)

delay(retraso largo,
TimeUnit unidad)

La cantidad de tiempo opcional para pasar antes de que la tarea este lista para ser ejecutada.

El tiempo se especifica como un número de tics con el método delayTicks (), o se puede proporcionar como un número de una unidad de tiempo más conveniente al especificar una TimeUnit con el método de delay().

Cualquiera de los métodos, pero no ambos, puede ser especificado por tarea.

intervalo
intervalTicks(
intervalo largo)
intervalo(intervalo largo,
TimeUnit unidad)

La cantidad de tiempo entre las repeticiones de la tarea. Si no se especifica un intervalo, la tarea no va a ser repetida.

El tiempo se especifica como un número de tics con el método ` intervalTicks() ``, o puede proporcionarse como un número de una unidad de tiempo más conveniente al especificar una TimeUnit con el método interval().

Cualquiera de los métodos, pero no ambos, puede ser especificado por tarea.

sincronización async() Una tarea sincrónica se ejecuta en el ciclo principal del juego en serie con el ciclo de tic. Si se utiliza Task.Builder#async, la tarea se ejecutará de forma asíncrona. Por lo tanto, se ejecutará en su propio hilo, independientemente del ciclo de tic, y no puede usar con seguridad el estado del juego. (Ver Asynchronous Tasks.)
nombre nombre(String nombre) El nombre de la tarea. Por defecto, el nombre de la tarea será PLUGIN_ID «-» ( «A-» | «S-» ) SERIAL_ID. Por ejemplo, el nombre de una tarea por defecto podría ser «FooPlugin-A-12». No pueden haber dos tareas activads con el mismo ID de serial para el mismo tipo de sincronización. Si el nombre de una tarea es especificado, será descriptivo y ayudará al usuario a depurar su plugin.

Por último, envíe la tarea al planificador usando :javadoc: Task.Builder#submit(Object).

¡Y eso es! Para resumir, una tarea programada completamente funcional que se ejecutaría de forma asíncrona cada 5 minutos después de un retraso inicial de 100 milisegundos podría construirse y enviarse utilizando el siguiente código:

import java.util.concurrent.TimeUnit;

Scheduler scheduler = Sponge.getScheduler();
Task.Builder taskBuilder = scheduler.createTaskBuilder();

Task task = taskBuilder.execute(() -> logger.info("Yay! Schedulers!"))
    .async().delay(100, TimeUnit.MILLISECONDS).interval(5, TimeUnit.MINUTES)
    .name("ExamplePlugin - Fetch Stats from Database").submit(plugin);

Para cancelar una tarea, simplemente llame al método Task#cancel():

task.cancel();

Si necesita cancelar la tarea desde el propio ejecutable, puede optar por utilizar ``Consumer<Task> `` `para acceder a la tarea. El siguiente ejemplo programará una tarea que contará hacia atrás desde 60 y se cancelará al llegar a 0.

@Listener
public void onGameInit(GameInitializationEvent event) {
    Scheduler scheduler = Sponge.getScheduler();
    Task.Builder taskBuilder = scheduler.createTaskBuilder();
    Task task = taskBuilder.execute(new CancellingTimerTask())
        .interval(1, TimeUnit.SECONDS)
        .name("Self-Cancelling Timer Task").submit(plugin);
}

private class CancellingTimerTask implements Consumer<Task> {
    private int seconds = 60;
    @Override
    public void accept(Task task) {
        seconds--;
        Sponge.getServer()
            .getBroadcastChannel()
            .send(Text.of("Remaining Time: "+seconds+"s"));
        if (seconds < 1) {
            task.cancel();
        }
    }
}

Tareas asíncronas

Las tareas asincrónicas se deben usar principalmente para código que puede llevar un período de tiempo significativo para ejecutarse, como por ejemlplo solicitudes a otro servidor o base de datos. Si se realiza en el hilo principal, una solicitud a otro servidor podría tener un gran impacto en el rendimiento del juego, ya que el siguiente tic no puede iniciarse hasta que se complete la solicitud.

Dado que Minecraft es en gran medida un hilo único, es poco lo que puede hacer en un hilo asincrónico. Si debe ejecutar un hilo de forma asincrónica, debe ejecutar todo el código que no use SpongeAPI/affect a Minecraft, luego registrar otra tarea sincrónica para manejar el código que necesita la API. Hay algunas partes de Minecraft con las que puedes trabajar de forma asincrónica, que incluyen:

  • Charla
  • Manejo de Permisos integrado de Sponge
  • Pranificador de Sponge

Además, hay algunas otras operaciones que son seguras para hacer asincrónicamente:

  • Solicitudes de red independientes
  • Sistema de archivos I/O (excluidos los archivos utilizados por Sponge)

Compatibilidad con otras bibliotecas

A medida que su complemente crezca en tamaño y alcance, es posible que desee comenzar a utilizar una de las muchas bibliotecas de concurrencia disponibles para Java y JVM. Estas bibliotecas tienden a soportar el ExecutorService “de Java <https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html> _ como un medio para dirigir en qué hilo de la tarea es ejecutado.

Para permitir que estas bibliotecas funcionen con Scheduler de Sponge, se pueden usar los siguientes métodos:

Una cosa a tener en cuenta es que cualquier tarea que interactúe con Sponge fuera de las interacciones listadas en Asynchronous Tasks necesita ser ejecutada en el ExecutorService creado con ` Scheduler#createSyncExecutor(Object) `para ser seguro con los subprocesos.

import org.spongepowered.api.scheduler.SpongeExecutorService;

SpongeExecutorService minecraftExecutor = Sponge.getScheduler().createSyncExecutor(plugin);

minecraftExecutor.submit(() -> { ... });

minecraftExecutor.schedule(() -> { ... }, 10, TimeUnit.SECONDS);

Casi todas las bibliotecas tienen alguna forma de adaptar el ExecutorService para programar tareas de forma nativa. Como ejemplo, los siguientes párrafos explicarán cómo se usa el `` ExecutorService`` en algunas bibliotecas.

CompletableFuture (Java 8)

Con Java 8, el objeto CompletableFuture se agregó a la biblioteca estándar. En comparación con el objeto Future, esto permite que el desarrollador proporcione una devolución de llamada que se llama cuando el futuro se completa en lugar de bloquear el hilo hasta que el futuro eventualmente se completa.

CompletableFuture es una interfaz fluida que generalmente tiene las siguientes tres variaciones para cada una de sus funciones:

  • `` CompletableFuture#<function>Async(…, Executor ex) `` Ejecuta esta función a través de ex
  • `` CompletableFuture#<function>Async(…) `` Ejecuta esta función a través de ``ForkJoinPool.commonPool() ``
  • CompletableFuture#<function>(...) Ejecuta esta función en cualquier hilo en el que se haya completado el CompletableFuture anterior.
import java.util.concurrent.CompletableFuture;

SpongeExecutorService minecraftExecutor = Sponge.getScheduler().createSyncExecutor(plugin);

CompletableFuture.supplyAsync(() -> {
    // ASYNC: ForkJoinPool.commonPool()
    return 42;
}).thenAcceptAsync((awesomeValue) -> {
    // SYNC: minecraftExecutor
}, minecraftExecutor).thenRun(() -> {
    // SYNC: minecraftExecutor
});

RxJava

RxJava <https://github.com/ReactiveX/RxJava> _ es una implementación del concepto Reactive Extensions para JVM.

El subprocesamiento múltiple en Rx se gestiona a través de varios `Schedulers<http://reactivex.io/documentation/scheduler.html>`_. Utilizando la función Schedulers#from(Executor executor) ``, el ``Executor proporcionado por Sponge se puede convertir en un Scheduler.

Al igual que CompletableFuture por defecto, las acciones son ejecutadas en el mismo hilo que completó la parte anterior de la cadena. Use ``Observable#observeOn(Scheduler scheduler) `` para moverse entre hilos.

Una cosa importante a tener en cuenta es que la raíz Observable se invoca en cualquier hilo en que Observable#subscribe() haya sido invocado. Si la raíz observable interactúa con Sponge, se debe forzar que se ejecute sincrónicamente usando ``Observable#subscribeOn(Scheduler scheduler) ``.

import rx.Observable;
import rx.Scheduler;
import rx.schedulers.Schedulers;

SpongeExecutorService executor = Sponge.getScheduler().createSyncExecutor(plugin);
Scheduler minecraftScheduler = Schedulers.from(executor);

Observable.defer(() -> Observable.from(Sponge.getServer().getOnlinePlayers()))
          .subscribeOn(minecraftScheduler) // defer -> SYNC: minecraftScheduler
          .observeOn(Schedulers.io()) // -> ASYNC: Schedulers.io()
          .filter(player -> {
              // ASYNC: Schedulers.io()
              return "Flards".equals(player.getName());
          })
          .observeOn(minecraftScheduler) // -> SYNC: minecraftScheduler
          .subscribe(player -> {
              // SYNC: minecraftScheduler
              player.kick(Text.of("Computer says no"));
          });

Scala

Scala viene con un objeto Future incorporado que se refleja por diseño en la estructura de scala. La mayoría de los métodos de el Future aceptan una `ExecutionContext <http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext;crwdn:ht:1:ht:crwdn;gt;`_ la cual es determinada dónde esa parte de la operacion es ejecutada. Esto es diferente de el CompletableFuture o RxJava ya que estos ejecutan en el mismo hilo en el cual la operación anterior terminó por defecto.

El hecho de que todas estas operaciones intenten encontrar implícitamente un `` ExecutionContext`` significa que usted puede usar fácilmente el ExecutionContext.global predeterminado y ejecutar específicamente las partes que necesitan ser seguras para hilos en el hilo del servidor de Sponge.

Para evitar pranificar accidentalmente el trabajo a través del ExecutorContext de Sponge, se debe definir implícitamente otro contexto para que actúe como la opción predeterminada. Para mantener la seguridad del hilo, solo las funciones que realmente interactúan con Sponge necesitarán tener especificado el ejecutor de Sponge.

import scala.concurrent.ExecutionContext

val executor = Sponge.getScheduler().createSyncExecutor(plugin)

import ExecutionContext.Implicits.global
val ec = ExecutionContext.fromExecutorService(executor)

val future = Future {
    // ASYNC: ExecutionContext.Implicits.global
}

future foreach {
    case value => // SYNC: ec
}(ec)

future map {
    case value => 42 // SYNC: ec
}(ec).foreach {
    case value => println(value) // ASYNC: ExecutionContext.Implicits.global
}