simvx.core.ai.async_slot

AsyncSlot: a non-blocking bridge from the synchronous frame loop to one awaitable.

The engine frame loop is purely synchronous (SceneTree.tick drives generator coroutines with gen.send(dt)); there is no asyncio event loop on the frame thread, and a blocking await there would freeze the game. AsyncSlot owns one daemon thread running one long-lived asyncio loop. The frame thread only ever calls :meth:submit (returns immediately via run_coroutine_threadsafe) and

meth:

poll (non-blocking; reads a finished result or None).

This mirrors two precedents the engine already ships: AssetServer.flush (snapshot-under-lock then dispatch) and GitStatusProvider (daemon thread + lock-guarded poll + self-disable-and-log on failure). The slot is LLM-agnostic: it bridges any awaitable, so the hybrid commander and node-gen capstone reuse it unchanged.

Policy:

  • Coalesce: at most one in-flight future. :meth:submit is a no-op while a call is pending (the dropped coroutine factory is never invoked, so there is no “coroutine was never awaited” warning).

  • Graceful degrade: :meth:poll logs the specific failure and returns None so the caller keeps its last good value, never a bare except that hides bugs.

  • Teardown: :meth:close stops the loop and joins the daemon thread; it is idempotent. The thread is a daemon so a crashed game still exits.

Module Contents

Classes

AsyncSlot

One daemon asyncio loop bridging a single in-flight awaitable to the frame.

Data

log

API

simvx.core.ai.async_slot.log

‘getLogger(…)’

class simvx.core.ai.async_slot.AsyncSlot(*, thread_name: str = 'simvx-async-slot')[source]

One daemon asyncio loop bridging a single in-flight awaitable to the frame.

Initialization

property pending: bool[source]

True if a submitted call is still running (used by callers to gate cadence).

submit(factory: simvx.core.ai.async_slot.CoroFactory[simvx.core.ai.async_slot.T]) bool[source]

Submit a coroutine factory to the loop, coalescing to one in-flight call.

Returns True if accepted (no call was pending), False if dropped because a call is already in flight or the slot is closed. The dropped factory is never invoked, so no coroutine is created and abandoned.

poll() simvx.core.ai.async_slot.T | None[source]

Return a freshly completed result, or None if nothing new / it failed.

Non-blocking: never awaits. On success the slot is cleared and the value returned. On exception the failure is logged and None is returned so the caller retains its last good value. While a call is still pending, or nothing has been submitted, returns None.

cancel() None[source]

Cancel any in-flight call without tearing down the loop.

close() None[source]

Cancel in-flight work, stop the loop, and join the daemon thread (idempotent).