feat: async API with executors, thread pool, mutex + region debug overlay - #3
Merged
Conversation
PyXiion
commented
Aug 1, 2026
Owner
- Coroutine suspension bridge
- Lua coroutines can yield through continuable Java functions, pcall/xpcall, suspend functions, and event handlers.
- Added resume handlers, continuation state, and error propagation.
- Async Lua API
- Replaced mc.task, mc.run, mc.prun, mc.sleep, and mc.fetch with require "async".
- Added tasks, promises, all/allSettled, sleep, HTTP, executor selection, thread pool execution, mutexes, and serialized coroutine resumption.
- Added scheduler thread-safety and server-stop cleanup.
- Region debug overlay (WIP)
- Added server/client region synchronization with interest-radius diffs, add/remove updates, admin gating, cap warnings, and /ignis debug regions.
- Added client source sets and Fabric client entrypoints.
- Wireframe rendering remains a documented no-op pending available Fabric/Yarn renderer APIs.
- Reliability and compatibility
- Fixed region payload ID collisions and development launch issues.
- Added region diff, suspend bridge, Lua yield, and async tests.
- Updated API/design documentation and changelog.
- Add loom.splitEnvironmentSourceSets() and a second 'pxignis' mod entry pointing at sourceSets.client in build.gradle. - Create src/client/java/ and src/version-*/client-kotlin/ for client code. - Wire shadowJar to include both source set outputs and drop minimize() (it stripped PxIgnisClient since the main entrypoint doesn't reference it). - Register client entrypoint ru.pyxiion.ignis.client.PxIgnisClient in fabric.mod.json. - Add PxIgnisClient.kt entrypoint (logs 'loaded') and ClientCompat.kt stubs for 1.21.10 and 1.21.11.
…tests) Server-side (src/main/java): - Add RegionPackets.kt: 5 custom payloads sharing the pxignis:regions id — RegionSyncPayload (full snapshot), RegionUpsertPayload / RegionRemovePayload (incremental diff), RegionCapWarningPayload (one-time toast), and the C2S RegionInterestPayload. Each payload uses a per-class typed CustomPayload.Id<T> and a hand-rolled Box codec (6 doubles). - Region.kt: add REGION_INTEREST_RADIUS_CHUNKS=4 and MAX_REGIONS_PER_PLAYER=256 constants; opt-in state (optInPlayers, lastSnapshots, warnedPlayers) and the per-tick tickClientSync that walks regionsByChunk within the player's 4-chunk radius, diffs against lastSnapshots via the pure diffRegionSnapshots, sends upserts/removes, and fires the cap warning once per opt-in. Pure diffRegionSnapshots treats bound changes as upserts to fix the stale-AABB bug. New setOptedIn / onPlayerLeft / closeAll(server) lifecycle methods. - PxIgnis.kt: register all 4 S2C + 1 C2S payload types, gate the C2S handler with Compat.isAdmin (server-authoritative), call RegionManager.tickClientSync in END_SERVER_TICK, RegionManager.onPlayerLeft in DISCONNECT, and RegionManager.closeAll in SERVER_STOPPED. Client-side (src/client/java): - ClientRegionRegistry: thread-safe ConcurrentHashMap<Int, Box> with upsert/remove/replaceAll/clear; var enabled default false (off on join). - ClientCommands: registers /ignis debug regions via ClientCommandRegistrationCallback; toggles registry.enabled, sends RegionInterestPayload. - PxIgnisClient: registers 4 S2C receivers, hooks WorldRenderEvents.BEFORE_DEBUG_RENDER (runCatching the 1.21.10 stub so the unsupported stub doesn't log-spam every frame), and registers the client command. Tests (src/test/kotlin): - RegionInterestDiffTest: 6 pure-logic JUnit 5 tests covering empty/empty, add, remove, bounds-change-as-upsert, unbounded-when-under-cap, and capped-when-over-limit. No MC runtime. Both ./gradlew build (1.21.11) and ./gradlew build -PtargetVersion=1.21.10 pass; all 93 unit tests pass. Wireframe rendering (drawWireframeBoxes) remains a stub pending Phase 4.
- gradle.properties: bump yarn_mappings from 1.21.11+build.5 to +build.6. Build.5 was missing yarn classes that fabric-recipe-api-v1 8.2.4 references, causing a Mixin 'resource invalid or could not be read' crash on game startup. Build.6 resolves it. - RegionPackets.kt: each of the 5 payloads now gets its own CustomPayload.Id<...> with a unique identifier (pxignis:regions_sync, regions_upsert, regions_remove, regions_cap_warning, regions_interest). The previous version shared a single identifier across all 5 payloads, which collided in PayloadTypeRegistryImpl.register() because the registry is keyed by Identifier, not by Id<T> — it threw 'Packet type Id[id=pxignis:regions] is already registered!' on main entrypoint. Also drops the unused internal REGIONS_ID shim. - PxIgnisClient.kt: pass the WorldRenderContext from WorldRenderEvents.BEFORE_DEBUG_RENDER to the new ClientCompat.drawWireframeBoxes(ctx, boxes) signature. - ClientCompat.kt (1.21.10): signature updated to match; the body is still an UnsupportedOperationException stub for parity with 1.21.11. - ClientCompat.kt (1.21.11): real implementation is blocked because the renderer classes referenced by the fabric docs (com.mojang.blaze3d.vertex.BufferBuilder, ByteBufferBuilder, MeshData; MappableRingBuffer; RenderSystem.device, .dynamicUniforms, .projectionType) are not shipped in yarn 1.21.11+build.6 or the minecraft-client jar at the versions this mod depends on. The merged jar contains them under neoforge profiles, but the loom client source set has only minecraft-client. The fabric-renderer-indigo 5.0.3 access widener does not widen any of these classes. Replaced the body with a one-time-warning no-op that documents the limitation; the network state machine, registry, command, and toggling flag are all live and will start drawing wireframes as soon as a future fabric-api or yarn build exposes the API. Verified: ./gradlew build (1.21.11) green, ./gradlew build -PtargetVersion=1.21.10) green, all 93 unit tests pass, ./gradlew runClient boots singleplayer cleanly with the mod loaded.
Add LuaContinuableFunction — Java functions that let coroutine.yield propagate through their call boundary. When an inner function yields, the caller throws YieldContinuationException; OP_CALL stores the func, callArgs and continuation on the frame and suspends. On resume the continuable function is re-invoked with the continuation state. - pcall/xpcall rework into continuable functions so yields pass through them and are not masked as errors. - LuaThread.ResumeHandler: per-thread callback for async resume, inherited from parent/main thread; lua_resume_sync now sets/restores LuaState.current(). - FrameInterpreter: re-invoke continuable functions on resume, drop the "yield across C-call boundary" error for continuable calls. - LuaClosure/DebugLib: guard against missing current LuaThread.
- pcall/xpcall yield propagation (single and multi-value yields) - pcall errors not masked as yields - closure errors without a current LuaThread no longer NPE
luaSuspendFunction/luaSuspendFunctionNil yield the Lua coroutine, run a Kotlin suspend block on a CoroutineScope, and resume it via the thread's LuaThread.ResumeHandler. Fails with a clear LuaError outside a coroutine, on the main thread, or without a configured handler. - IgnisRuntime owns modScope (SupervisorJob + Dispatchers.Default), cancelled on server stop. - LuaMcApi.suspendFunction helper; main thread gets a resume handler that schedules resumes back on the server thread. - EventBus runs LuaClosure handlers through a LuaThread, so mc.sleep / mc.fetch / suspend functions work inside event handlers; requires a stateProvider (regions use RegionManager.sharedStateProvider). - SuspendBridgeTest covers sync/suspending suspend functions, pcall/xpcall propagation, error handling, and scope usage.
mc.task(fn, ...) runs a Lua function on a background thread and returns a task userdata; mc.task:pwait() yields until it completes, returning ok, result (pcall-like). mc.prun(fn) is a convenience for task(fn):pwait().
- Changelog entry for the async coroutines release. - docs/async-suspend-bridge.md design rationale (problem, thread model, deferred work); docs/index.md index. - Split API surface and testing quirks out of AGENTS.md into agent_docs/api.md and agent_docs/testing.md.
…ises, all/allSettled - mc.task/run/prun/sleep/fetch removed from mc table - async.task runs body as LuaThread (can yield internally) - async.promise() for manual settlement - task:wait(), task:try(), promise:resolve(), promise:error() - async.all() / async.allSettled() combinators - async.sleep() / async.fetch() moved from mc - 23 tests passing
… + docs - AsyncExecutor registry + PxIgnis-async- thread pool - coroutine-safe async.mutex with FIFO queuing - SerializedResumer hardening for one-at-a-time coroutine resumes - LuaThread.executionContext for executor propagation - thread-safe Scheduler and SERVER_STOPPING lifecycle shutdown - rewrite async-api.md reference around executors and I/O vs parallel work
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 448 |
| Duplication | 32 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.