Tracks guild loading progress after READY to distinguish startup loads from runtime joins.
When Discord sends READY, it includes guild stubs (unavailable: true). Full guild data
arrives via subsequent GUILD_CREATE events. This GenServer tracks which guilds are still
loading and fires SHARD_READY / ALL_SHARDS_READY events when loading completes.
Uses an ETS table (:eda_pending_guilds) for O(1) lookups in the hot path
and :persistent_term for the global ready flag.
Summary
Functions
Blocks until all shards have finished loading their guilds.
Returns a specification to start this module under a supervisor.
Marks a guild as loaded (received its GUILD_CREATE).
Returns true if the guild is still loading (pending GUILD_CREATE).
Returns true if every shard is connected and has finished loading its guilds.
Reports that a shard's gateway connection closed.
Registers pending guilds for a shard after receiving READY.
Reports that a shard resumed its session.
Starts the ReadyTracker.
Returns detailed status information about the loading state.
Functions
@spec await_ready(timeout()) :: :ok | {:error, :timeout}
Blocks until all shards have finished loading their guilds.
Returns :ok when the bot is fully ready, or {:error, :timeout} if the
timeout expires. Uses OTP's native GenServer.call suspension — no scheduler
is blocked.
If the bot is already ready, returns :ok immediately.
Examples
EDA.await_ready()
EDA.await_ready(30_000)
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec guild_loaded(String.t()) :: :ok
Marks a guild as loaded (received its GUILD_CREATE).
Called by EDA.Gateway.Events when a startup GUILD_CREATE arrives.
Returns true if the guild is still loading (pending GUILD_CREATE).
Reads directly from ETS — no GenServer call, O(1).
@spec ready?() :: boolean()
Returns true if every shard is connected and has finished loading its guilds.
Goes back to false while any shard's gateway connection is down, and returns to true
once it resumes or completes a fresh READY — so it is safe to use as a readiness check.
REST calls do not depend on the gateway and keep working while this is false; events,
voice and member chunking do not.
Non-blocking — reads from :persistent_term (O(1)).
@spec shard_disconnected(non_neg_integer()) :: :ok
Reports that a shard's gateway connection closed.
Called by EDA.Gateway.Connection on every disconnect. A shard that had finished loading
stops counting as ready until it resumes or completes a fresh READY, so ready?/0 stops
answering true for a bot that has lost its gateway.
@spec shard_ready(non_neg_integer(), [String.t()]) :: :ok
Registers pending guilds for a shard after receiving READY.
Called by EDA.Gateway.Connection when a READY payload arrives.
@spec shard_resumed(non_neg_integer()) :: :ok
Reports that a shard resumed its session.
A resume carries on the same session, so Discord sends RESUMED rather than READY and does
not reload the guilds. A shard that was ready before the disconnect is ready again at
once. SHARD_READY and ALL_SHARDS_READY are not dispatched a second time, because from
the consumer's side nothing restarted.
Starts the ReadyTracker.
@spec status() :: map()
Returns detailed status information about the loading state.
Return value
A map with keys:
:globally_ready— whether all shards are ready:ready_shards— set of shard IDs that finished loading:expected_shards— total number of shards expected:pending_counts— map of shard_id => remaining guild count