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)
|
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 Cualquiera de los métodos, pero no ambos, puede ser especificado por tarea. |
intervalo |
|
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 |
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();
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) {
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();
}
}
}
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)
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.
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:
Scheduler#createSyncExecutor(Object) crea un SpongeExecutorService que ejecuta tareas a través del pranificador sincrónico de Sponge.
Scheduler#createAsyncExecutor(Object) crea un
SpongeExecutorService
que ejecuta tareas a través del programador asincrónico de Sponge. Las tareas están sujetas a las restricciones mencionadas en Asynchronous Tasks.
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 elCompletableFuture
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.
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
}