Programador
Advertencia
These docs were written for SpongeAPI 7 and are likely out of date. If you feel like you can help update them, please submit a PR!
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;
PluginContainer plugin = ...;
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(Component.text("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:
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;
PluginContainer plugin = ...;
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;
PluginContainer plugin = ...;
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;
PluginContainer plugin = ...;
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(Component.text("Computer says no"));
});
Scala
Scala comes with a built-in Future object which a lot of scala framework mirror in design. Most methods of the Future accept an ExecutionContext which determined where that part of the operation is executed. This is different from the CompletableFuture or RxJava since they default to executing on the same thread on which the previous operation ended.
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
}