Skip to content

Repository files navigation

Tempo

One scheduler for every Mides plugin and service: Loom, Facet, Harbor and Jarvis run their coroutines on the same one, and the only thing that changes between Bukkit, Folia, Velocity, a Ktor server or a plain JVM is how each thread is reached.

Module What it is Who uses it JVM
api Threading (the thread contract) and Scheduler (pool + scope + tasks + sync) Loom, Facet, Harbor, Jarvis 8
platform-bukkit-legacy BukkitThreading, from 1.8.8 up; detect() picks Folia when the server is Folia Harbor, Loom bukkit-legacy, Facet 8
platform-folia FoliaThreading: global / region / entity / async. Also Paper 1.20+ Loom folia and modern 21
platform-velocity VelocityThreading Harbor, Loom velocity 17
platform-ktor KtorThreading and Application.tempo(), one scheduler per application Edge and other Ktor services 17
platform-fallback FallbackThreading: a dedicated game thread and a pool, with no platform at all tools, test harnesses, undetected platforms 8

The contract

interface Threading {
    val isGlobal: Boolean
    fun owns(owner: Any): Boolean
    fun global(block: Runnable)
    fun owned(owner: Any, block: Runnable, retired: Runnable = Runnable {})
    fun async(block: Runnable)
}

owned exists because of Folia, where a player or a location lives on the thread of its region. On every other platform it is global. Code written as scheduler.sync(player) { } is correct everywhere and only on Folia does something different. Threading.Inline runs everything on the calling thread, for tests and code with no platform; FallbackThreading is the same idea with real thread separation.

The scheduler

One per plugin. Harbor builds it with the plugin's name and logger and closes it at shutdown; the frameworks running inside the plugin receive that same scheduler.

scheduler.async {
    val profile = backend.load(uuid)                        // on the Tempo-<plugin> pool
    sync(player) { player.sendMessage(profile.greeting) }   // on the thread that owns the player
}
scheduler.repeatTicks(20) { board.refresh() }
withContext(scheduler.global) { world.spawn(...) }          // as a dispatcher

Thread safety

The scheduler is fully thread-safe: launch, cancel, query and close from any thread, concurrently. Every task is registered before it starts and removed when it completes, so no window leaves an orphaned entry. close() is idempotent; whatever is launched afterwards is born cancelled and never runs. A sync whose caller was cancelled while waiting skips its block once the game thread reaches it. Every Threading implementation is safe to call from any thread — that is part of the contract.

Ownership

Whoever creates a scheduler closes it. Harbor closes the plugin's; a Loom registry only closes a scheduler it built itself, never one it was handed. A Threading that owns threads of its own (FallbackThreading) is closed by its creator, not by the scheduler built on it.

About

A plugin's scheduler: one pool, one supervisor and one way back to the game, for everything asynchronous that plugin runs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages