Files
kayjaydee a10294c01f feat(core): dispatchers, scopes, and exceptions
HytaleDispatchers (World/HytaleIO/HytaleScheduled), per-key scope
registries (PlayerScopes, WorldScopes, PluginScopes) keyed by UUID
or plugin identity, and the sealed AsyncException hierarchy.
Includes unit tests against in-memory stubs.
2026-04-28 16:29:55 +02:00

1.8 KiB

:core

Dispatchers and coroutine scopes. No knowledge of the Hytale SDK — the only runtime deps are kotlinx-coroutines and SLF4J.

Dispatchers

AsyncDispatchers.World(world)         // confines to a specific world's main thread
AsyncDispatchers.HytaleIO             // bounded pool for blocking I/O
AsyncDispatchers.HytaleScheduled      // backs delay() and withTimeout()

AsyncDispatchers.configureIo(parallelism = 16)
AsyncDispatchers.configureScheduled(myExecutor)

World takes a WorldExecutor interface, not the Hytale World class directly. The :binding module wires the real World in via World.asExecutor(). Tests substitute single-thread executors keyed by UUID.

Scopes

Three registries, all SupervisorJob-backed (a child failure doesn't kill siblings) and all defaulting to HytaleIO:

PlayerScopes.of(uuid)        // get-or-create
PlayerScopes.cancel(uuid)    // idempotent
PlayerScopes.cancelAll()
PlayerScopes.activeCount()

WorldScopes.of(worldUuid)    // same shape
PluginScopes.of(plugin)      // identity-keyed (IdentityHashMap), not equality

Async.shutdown() cancels all three registries and is idempotent — call it from your plugin's shutdown().

Exceptions

A sealed hierarchy under AsyncException so consumers can catch one type if they want a uniform error path:

  • WorldClosedException — dispatching to a dead world
  • NoWorldInContextExceptionMainHere invoked off-context
  • ComponentNotFoundException — strict read/modify on a missing component
  • ComponentTypeNotRegisteredException — DSL used on an unregistered class

Tests

SmokeTest and ScopesTest run against stub WorldExecutor implementations backed by Executors.newSingleThreadExecutor. No Hytale jar required.