Documentación de MagmaLib v2.0

La solución profesional para migrar plugins de Paper a Folia con una API moderna, type-safe composition y máximo rendimiento.

Nuevo en v2.0

Composición type-safe: thenApply(), thenAccept(), exceptionally()Context propagation: named() para debugging • 100% backward compatible con v1.x

Instalación

Requisitos

Paper 1.18+ o Folia (recomendado 1.20+). Java 17 o superior.

Próximamente

MagmaLib v2.0 estará disponible en JitPack muy pronto. Mientras tanto, puedes usar el código fuente directamente copiando MagmaLib.java en tu proyecto.

Maven

<!-- Próximamente en JitPack -->

Gradle (Kotlin DSL)

// Próximamente en JitPack

Gradle (Groovy)

// Próximamente en JitPack

Inicio Rápido

Integra MagmaLib en 3 pasos simples:

  1. Añade la dependencia o copia MagmaLib.java en tu proyecto
  2. Inicializa en onEnable() con MagmaLib.init(this)
  3. Reemplaza Bukkit.getScheduler() por MagmaLib.task()
// En tu JavaPlugin principal
@Override
public void onEnable() {
    // 1. Inicializar MagmaLib
    MagmaLib.init(this);

    // 2. Usar Task Builder (API estándar - segura)
    MagmaLib.task(() -> {
        player.sendMessage("¡Hola desde MagmaLib!");
    })
    .at(player.getLocation())
    .afterTicks(20)
    .handleException(e -> getLogger().warning("Error: " + e.getMessage()))
    .run();

    // 3. O composición type-safe (nuevo en v2.0)
    MagmaLib.<Integer>task(() -> calculateScore(player))
        .thenApply(score -> score > 100 ? "¡Excelente!" : "Sigue")
        .thenAccept(msg -> player.sendMessage(msg))
        .run();
}

Inicialización

init() Estándar

void init(Plugin plugin)

Inicializa la librería. Llamar en onEnable(). Lanza IllegalStateException si plugin es null.

@Override
public void onEnable() {
    MagmaLib.init(this);
}

isFolia()

boolean isFolia()

Detecta automáticamente si el servidor es Folia usando reflexión con caché. Lanza excepción si no se llamó init().

if (MagmaLib.isFolia()) {
    // Código optimizado para Folia
} else {
    // Fallback para Paper/Spigot
}

Task Builder (API Estándar)

API segura y legible para la mayoría de casos de uso. Incluye validaciones automáticas y manejo de errores. 100% compatible con v1.x.

Backward Compatible

Todo tu código de MagmaLib v1.x funciona sin cambios en v2.0. Las nuevas features de composición son opcionales.

task(Runnable) v1.x

TaskBuilder task(Runnable runnable)

Punto de entrada para tareas sin retorno. Compatible con versiones anteriores.

task(Supplier) v2.0

<T> TaskBuilder<T> task(Supplier<T> supplier)

Punto de entrada para composición type-safe con thenApply/thenAccept.

at()

TaskBuilder<T> at(Location location)

Ejecuta en la región del chunk (Folia) o hilo principal (Paper).

with()

TaskBuilder<T> with(Entity entity)

Vincula la tarea al scheduler de la entidad en Folia.

afterTicks()

TaskBuilder<T> afterTicks(long ticks)

Retraso directo en ticks de Minecraft (1 tick = 50ms).

everyTicks()

TaskBuilder<T> everyTicks(long ticks)

Intervalo repetitivo en ticks para tareas periódicas.

handleException()

TaskBuilder<T> handleException(Consumer<Exception>)

Manejador personalizado de errores para la tarea.

named() v2.0

TaskBuilder<T> named(String name)

Asigna nombre para debugging y logs con contexto rico.

unsafe() Directo

TaskBuilder<T> unsafe()

⚠️ Desactiva validaciones. Solo en hot paths con parámetros garantizados.

run()

Task run()

Ejecuta la tarea y devuelve objeto Task para cancelación manual.

Importante

Por defecto, .at() y .with() validan que el chunk esté cargado y la entidad sea válida. Usa .cancelIfUnloaded(false) para desactivar.

Composición Type-Safe v2.0

Nueva API funcional estilo CompletableFuture con type-safety completo. Transforma, consume y maneja errores de forma declarativa.

¿Por qué usar composición?
  • ✅ Type-safety en cada paso del pipeline (sin casts)
  • ✅ Código más legible y mantenible
  • ✅ Manejo de errores integrado
  • ✅ Thread-aware: ejecuta en el scheduler correcto automáticamente

Flujo de Composición

task(() -> calcular()) thenApply(x -> transformar) thenAccept(x -> consumir) exceptionally(e -> fallback) run()

thenApply() Transformar

<R> TaskBuilder<R> thenApply(Function<T, R> mapper)

Transforma el resultado aplicando una función. Cambia el tipo del builder para encadenamiento type-safe.

// Integer → String
MagmaLib.<Integer>task(() -> player.getLevel())
    .thenApply(level -> "Nivel: " + level)
    .thenAccept(msg -> player.sendMessage(msg))
    .run();

thenAccept() Consumir

TaskBuilder<T> thenAccept(Consumer<T> action)

Consume el resultado sin retornar valor. Ideal para efectos secundarios como enviar mensajes.

// Consumir resultado final
MagmaLib.task(() -> fetchUserData(player))
    .thenApply(data -> data.username)
    .thenAccept(username -> {
        player.sendMessage("Bienvenido, " + username);
    })
    .run();

exceptionally() Fallback

TaskBuilder<T> exceptionally(Function<Throwable, T> fallback)

Proporciona un valor de recuperación cuando ocurre una excepción. Similar a try-catch pero funcional.

// Fallback ante error
MagmaLib.task(() -> database.query(player.getUniqueId()))
    .thenApply(result -> result.score)
    .exceptionally(e -> {
        getLogger().warning("Query falló: " + e.getMessage());
        return 0; // Valor por defecto
    })
    .thenAccept(score -> updateUI(score))
    .run();

named() Debugging

TaskBuilder<T> named(String name)

Asigna nombre para logs con contexto. Incluye task name, ubicación y entidad en mensajes de error.

// Logs más útiles en producción
MagmaLib.task(() -> processTransaction(player, amount))
    .named("EconomyDeduction")
    .at(player.getLocation())
    .handleException(e -> {
        // Log: "Error en tarea MagmaLib ['EconomyDeduction'] location=world@123,45,67..."
        getLogger().severe("Transacción fallida: " + e.getMessage());
    })
    .run();

Ejemplo Completo: Pipeline de Datos

// Pipeline type-safe completo
MagmaLib.<List<ItemStack>>task(() -> player.getInventory().getContents())
    .named("InventoryProcessor")
    .at(player.getLocation())

    // Filtrar items no null (List ItemStack → List ItemStack)
    .thenApply(items -> items.stream()
        .filter(Objects::nonNull)
        .toList())

    // Contar items (List ItemStack → Integer)
    .thenApply(items -> items.size())

    // Formatear mensaje (Integer → String)
    .thenApply(count -> "Tienes " + count + " items")

    // Enviar mensaje al jugador (efecto secundario)
    .thenAccept(message -> player.sendMessage(message))

    // Manejar errores con fallback
    .exceptionally(e -> {
        getLogger().warning("Error procesando inventario: " + e.getMessage());
        return "Error al cargar items";
    })

    // Ejecutar con routing automático para Folia
    .run();
                    
Nota sobre thenApply

thenApply() requiere usar MagmaLib.<T>task(Supplier<T>). Si usas task(Runnable), lanzará IllegalStateException. Los métodos thenAccept() y exceptionally() también requieren modo Supplier.

API Directa (Alto Rendimiento)

Métodos optimizados para hot paths. Sin builder, sin validaciones, máximo throughput.

Advertencia de Seguridad

Estos métodos omiten validaciones. Úsalos SOLO cuando garantices manualmente: location.getWorld() != null, chunk cargado, y entity.isValid().

runDirect()

void runDirect(Runnable runnable)

Ejecución inmediata en hilo global. ~25% más rápido que task().run().

runDirectAt()

void runDirectAt(Location, Runnable)

Ejecución en región de chunk. Ideal para procesamiento masivo de bloques.

runDirectWith()

void runDirectWith(Entity, Runnable)

Ejecución en scheduler de entidad. Para bucles de actualización masiva. Fix v2.0: delay 0L para ejecución inmediata.

runDirectLater()

void runDirectLater(Runnable, long delayTicks)

Retraso directo en ticks. Sin conversión de TimeUnit.

runDirectTimer()

void runDirectTimer(Runnable, long periodTicks)

Tarea periódica directa. Sin builder ni allocations extra.

runTimerUntilFast() Nuevo

Task runTimerUntilFast(Runnable, long, BooleanSupplier)

Versión optimizada sin AtomicReference. ~15% más rápido.

Async & CompletableFuture

runAsync()

CompletableFuture<Void> runAsync(Runnable)

Ejecuta asíncronamente y retorna futuro para composición.

MagmaLib.runAsync(() -> {
    return heavyComputation();
}).thenAccept(result -> {
    MagmaLib.runSync(() -> player.sendMessage(result));
});

callSync()

<T> CompletableFuture<T> callSync(Supplier<T>)

Ejecuta en hilo principal y retorna valor vía CompletableFuture.

MagmaLib.callSync(() -> {
    return player.getInventory().getContents();
}).thenAccept(items -> {
    // Procesar en hilo asíncrono
});

Utilidades Avanzadas

runWithRetry() Nuevo

void runWithRetry(Runnable, int, long, TimeUnit)

Ejecuta con reintentos automáticos. Compatible con Folia.

runTimerUntil() Nuevo

Task runTimerUntil(Runnable, long, BooleanSupplier)

Ejecuta periódicamente hasta que se cumpla una condición.

forAllPlayers()

void forAllPlayers(Consumer<Player>)

Itera jugadores con manejo seguro de errores. Ejecuta en hilo principal.

forAllLoadedChunks()

void forAllLoadedChunks(Consumer<Chunk>)

Procesa chunks cargados (ejecuta asíncrono por defecto).

ticksToMs() / msToTicks()

long ticksToMs(long) | long msToTicks(long)

Conversión de tiempo optimizada (multiplicación por 50L).

executeIfLoaded()

void executeIfLoaded(Location, Runnable)

Ejecuta solo si el chunk de la ubicación está cargado.

Manejo de Entidades

Mejor Práctica

Siempre usa .with(entity) para operaciones con entidades en Folia. Garantiza ejecución en el scheduler correcto y previene race conditions.

Ejemplo: Actualización Segura

MagmaLib.task(() -> {
    if (!entity.isValid() || entity.isDead()) return;

    // Operaciones seguras con la entidad
    entity.setCustomName("§aProcesado");
    entity.setGlowing(true);
})
.with(entity)  //  Routing correcto en Folia
.cancelIf(() -> !entity.isValid())  //  Cancelación automática
.handleException(e -> getLogger().warning("Error: " + e.getMessage()))
.run();

Modo Alto Rendimiento (⚠️)

// Solo si garantizas entity.isValid() == true
for (Entity entity : entities) {
    MagmaLib.task(() -> {
        entity.setCustomName("Updated");
    })
    .with(entity)
    .unsafe()  // ⚠️ Omite isValid() check
    .run();
}

Compatibilidad

CaracterísticaPaper/SpigotFolia
task().run()✅ Bukkit Scheduler✅ GlobalRegionScheduler
task().at(location)✅ Main Thread✅ RegionScheduler
task().with(entity)✅ Main Thread✅ EntityScheduler
task().async()✅ Async Scheduler✅ AsyncScheduler
thenApply/thenAccept v2.0✅ Funcional✅ Funcional
runDirectAt()✅ Main Thread✅ RegionScheduler
runDirectWith()✅ Main Thread✅ EntityScheduler
cancelIfUnloaded✅ Verificación✅ Verificación
handleException✅ Try-catch✅ Try-catch
named() v2.0✅ Logs con contexto✅ Logs con contexto

Nota: MagmaLib detecta automáticamente el tipo de servidor y usa el scheduler apropiado. Tu código funciona en ambos sin modificaciones.

Rendimiento Comparado

Benchmark: 10,000 iteraciones de tareas simples en servidor de prueba (Paper 1.21.4, Java 21).

EscenarioFoliaLibMagmaLib v1.2MagmaLib v2.0Mejora
Tarea global simple1.2 ms1.0 ms0.9 ms+25%
Tarea en región1.5 ms1.2 ms1.1 ms+27%
Tarea con composiciónN/A2.8 ms*2.1 ms+25%
Bucle con validaciones3.8 ms3.2 ms2.1 ms+47%
Hot path (runDirect*)1.3 ms0.9 ms0.8 ms+38%

*v1.2 con CompletableFuture manual; v2.0 con composición nativa integrada

Conclusión: MagmaLib v2.0 es ~25-47% más rápido que alternativas en hot paths, con composición type-safe integrada y cero overhead adicional.

Preguntas Frecuentes

¿Mi código de v1.x funcionará en v2.0?

Sí, 100% compatible. Todas las APIs de v1.x funcionan sin cambios. Las nuevas features de composición son opcionales y se activan usando task(Supplier<T>) en lugar de task(Runnable).

¿Cuándo usar thenApply vs thenAccept?

thenApply: Cuando necesitas transformar el valor y continuar el pipeline (retorna nuevo tipo). thenAccept: Cuando quieres consumir el valor final para un efecto secundario (no retorna valor, finaliza el pipeline).

¿Es seguro usar .unsafe()?

Sí, si sigues estas reglas:

  • ✅ Valida manualmente antes de llamar
  • ✅ Úsalo solo en código que controlas totalmente
  • ✅ Documenta por qué es seguro en ese contexto
  • ❌ Nunca lo uses con inputs de usuario o datos externos

¿Funciona en Paper y Folia?

✅ Sí, 100% compatible. MagmaLib detecta automáticamente el tipo de servidor y usa el scheduler apropiado:

  • Folia: RegionScheduler, EntityScheduler, GlobalRegionScheduler
  • Paper/Spigot: Bukkit Scheduler tradicional

Tu código no necesita cambios para funcionar en ambos.

¿Cómo debuggear tareas con named()?

Los logs de error incluirán automáticamente:

  • 🏷️ Nombre de la tarea: ['MiTarea']
  • 📍 Ubicación: location=world@x,y,z
  • 👤 Entidad: entity=Player[Notch]
  • 📋 Stack trace de creación (si habilitas debug)

¿Cómo instalar v2.0 ahora?

Mientras JitPack no esté disponible:

  1. Descarga MagmaLib.java del repositorio
  2. Cópialo en tu plugin: src/main/java/tu/paquete/MagmaLib.java
  3. ¡Listo! Sin dependencias externas

Próximamente: jitpack.io/#MagmaEnginers/MagmaLib