asobi_world_lobby (asobi v0.75.1)
View SourceSummary
Functions
Create a new world for the given mode (no owner — anonymous create).
Create a new world for the given mode and tag the player as owner.
Find a running world with capacity for the given mode, or create one.
The non-serialized implementation. Only asobi_world_lobby_server
should call this — direct callers race. Exposed because the
serializer holds no state and just delegates back here.
List all running worlds.
List running worlds with optional filters: mode, has_capacity, listed, quick_play.
H3 (2026-05-19): cached variant of list_worlds/1 for request paths that
do not need a fresh enumeration on every call. Each list_worlds/1 call
issues one synchronous gen_server:call per running world (get_info/1);
WS world.list at 60 msg/sec x 1000 worlds = 60k calls/sec/attacker. The
cache (500 ms TTL, backed by the ETS table owned by
asobi_world_lobby_server) absorbs that fan-out without changing the
serialization story for find_or_create_unsafe which stays uncached.
Functions
Create a new world for the given mode (no owner — anonymous create).
Create a new world for the given mode and tag the player as owner.
Refuses with {error, world_capacity_reached} when the global cap
(asobi:world_max, default 1000) is hit, and {error, player_world_limit_reached}
when the player is already at the per-player cap (asobi:world_max_per_player,
default 5). The cap is enforced via a pg group joined on creation so it
naturally clears when the world process dies.
Find a running world with capacity for the given mode, or create one.
Calls go through asobi_world_lobby_server to serialize concurrent
requests. The naive list-then-create sequence has a TOCTOU race:
two callers both see list_worlds = [], both call create_world,
and both clients end up in different worlds despite asking for the
same mode. Serializing closes the window.
The non-serialized implementation. Only asobi_world_lobby_server
should call this — direct callers race. Exposed because the
serializer holds no state and just delegates back here.
-spec list_worlds() -> [map()].
List all running worlds.
List running worlds with optional filters: mode, has_capacity, listed, quick_play.
-spec list_worlds_cached() -> [map()].
H3 (2026-05-19): cached variant of list_worlds/1 for request paths that
do not need a fresh enumeration on every call. Each list_worlds/1 call
issues one synchronous gen_server:call per running world (get_info/1);
WS world.list at 60 msg/sec x 1000 worlds = 60k calls/sec/attacker. The
cache (500 ms TTL, backed by the ETS table owned by
asobi_world_lobby_server) absorbs that fan-out without changing the
serialization story for find_or_create_unsafe which stays uncached.
The cache key is the has_capacity boolean only, never the raw filter
map: mode is attacker-controlled and unbounded, so keying on it would
let a client cycle distinct modes to force a miss on every request
(defeating the cache) and grow the table without bound. Instead the full
enumeration is cached under at most two keys and mode is applied
in-memory on the cached list.
-spec player_owned_world_count(binary()) -> non_neg_integer().