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, obtenga una instancia del `` Task.Builder``:

import org.spongepowered.api.scheduler.Task;

Task.Builder taskBuilder = Task.builder();

La única propiedad requerida es el Runnable <https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html> _, que puede especificar usando :javadoc: `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, un nombre de tarea predeterminado podría verse como «fooplugin-A-12». No habrá dos tareas activas que tengan la misma identificación de serie para el mismo tipo de sincronización. Si se especifica un nombre de tarea, debe ser descriptivo y ayudar a los usuarios a depurar su complemento.

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;

Task task = Task.builder().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();

If you need to cancel the task from within the runnable itself, you can instead opt to use a Consumer<Task> in order to access the task. The below example will schedule a task that will count down from 60 and cancel itself upon reaching 0.

@Listener
public void onGameInit(GameInitializationEvent event) {
    Task task = Task.builder().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();
        }
    }
}

Advertencia

Any scheduled tasks should either watch the current state of the Game or should be unregistered if they are no longer needed (e.g. during a GameStoppingServerEvent). This is of particular importance for the client because it can start and stop the server multiple times.

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.

Since Minecraft is largely single-threaded, there is little you can do in an asynchronous thread. If you must run a thread asynchronously, you should execute all of the code that does not use SpongeAPI/affect Minecraft, then register another synchronous task to handle the code that needs the API. There are a few parts of Minecraft that you can work with asynchronously, including:

  • 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)

Advertencia

El acceso a objetos del juego fuera del hilo principal puede provocar bloqueos, inconsistencias y otros problemas, y debe evitarse. Si esto se hace mal, puede obtener una ConcurrentModificationException con o sin una caída del servidor en el mejor de los casos y un player/world/server dañado en el peor.

Advertencia

Any scheduled tasks should either watch the current state of the Game or should be unregistered if they are no longer needed (e.g. during a GameStoppingServerEvent). This is of particular importance for the client because it can start and stop the server multiple times.

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<https://www.scala-lang.org/api/current/#scala.concurrent.Future>`_ incorporado que es reflejado por diseño en la estructura de scala. La mayoría de los métodos de Future aceptan un `ExecutionContext <https://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext;crwdn:ht:1:ht:crwdn;gt;`_ que determina dónde se ejecuta esa parte de la operación. Esto es diferente de CompletableFuture o RxJava, ya que se ejecutan de manera predeterminada en el mismo hilo en el que finalizó la operación previa.

The fact that all these operations try to implicitly find an ExecutionContext means that you can easily use the default ExecutionContext.global and specifically run the parts that need to be thread-safe on the Sponge server thread.

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
}