session
Single-session state and room fanout.
Single-session state and room fanout.
The container hosts exactly one play session at a time, so session and room state are module globals.
attributelog= logging.getLogger(__name__)attributeSESSIONOptional[dict[str, Any]]= NoneThe active play session, or None.
Shape:
\{
"id", "active", "created_at",
"user": \{...\}, "emulator": "pcsx2", "rom": \{...\}, "rom_file": str,
"save": \{...\} | None, "callback": \{...\} | None, "multiplayer": bool,
"controller_token",
"viewers": [\{"token","slot","mk_control","username","permission"\}...],
"controller_slot", "mk_owner_token", "designated_speaker",
"save_baseline": float, "emulator_obj": Emulator,
\}attributeLAST_EXITOptional[dict[str, Any]]= NoneWhat is left of the session that just exited: {"id", "rom", "emulator_obj"}.
RomM files the exit state in its library after the teardown has answered, so the emulator that captured it has to outlive the session for the read routes to find the file. Dropped at the next activate.
attributeROOMdict[str, Any]= {'controller': None, 'viewers': {}, 'cooldowns': {}}Live websocket connections for the room.
controller is the controller's connection info dict (or None while offline),
viewers maps each online viewer's token to its connection info dict, and
cooldowns tracks per-token rate limits.
func_session_id(raw) -> strReduce a caller-supplied id to what is safe in the export filename.
The exit archive is named after the session, and the export routes reject anything with path structure in it, so an id carrying a slash or a dot would produce an archive the parent could never fetch back.
paramrawobjectThe session_id from the activate payload, of any type or None.
Returns
strraw stringified and stripped to [A-Za-z0-9_-], at most 64
funcnew_session(payload, emulator_obj, rom_file) -> dict[str, Any]Replace the module-level session with a fresh one built from the activate payload.
The controller starts with gamepad 1 and mouse/keyboard control; the viewer list starts empty and the save baseline is the moment of creation.
parampayloaddict[str, Any]The activate request body; emulator is required, the rest
(session_id, user, rom, save, callback, multiplayer) is
optional.
paramemulator_objEmulatorThe emulator instance driving this session.
paramrom_filestrThe resolved path of the ROM file being played.
Returns
dictThe new session dict, which is also stored in SESSION.
funcretire_session() -> NoneEnd the session, keeping what the state routes still have to answer with.
Exit captures a state and then tears the session down, but RomM only asks for that state once the teardown has replied, so clearing outright is what made every post-exit read a 409. Only the emulator is kept, and only the read routes consult it: nothing here can be played, written to or resumed.
Returns
Nonefuncfind_viewer(token) -> Optional[dict[str, Any]]Look up a viewer entry in the active session by its token.
paramtokenstrThe viewer's streaming token.
Returns
typing.OptionalThe viewer dict, or None when there is no session or no viewer holds
funcadd_viewer(permission, user=None) -> dict[str, Any]Mint a viewer token and add the viewer to the active session.
A re-join by the same user (matched by id, else username) replaces the old entry and invalidates its token.
parampermissionstrThe viewer's permission level, e.g. readonly.
paramuserOptional[dict[str, Any]]= NoneThe joining user's details (id, display_name, username), or
None for an anonymous viewer who gets a generated username.
Returns
dictThe new viewer dict: `{"token", "user_id", "slot", "mk_control",
funcbroadcast_to_room(payload) -> NoneSend a JSON message to every connected room member.
Sends run concurrently; a failure on one socket is logged and does not stop delivery to the others.
parampayloaddict[str, Any]The JSON-serializable message, normally carrying a type key.
Returns
Nonefuncbroadcast_binary_to_room(payload, sender_ws) -> NoneRelay a binary media frame to every connected room member except its sender.
parampayloadbytesThe raw frame in the room's binary wire format.
paramsender_wsWebSocketThe websocket the frame arrived on; it is skipped.
Returns
Nonefuncbroadcast_state() -> NoneBroadcast a state_update describing every room member to the room.
The controller is listed first, then each viewer with its online status, mouse/keyboard ownership and, while connected, its public id. Does nothing when there is no session.
Returns
Nonefunchandle_assign_slot(viewer_token, slot) -> NoneAssign a gamepad slot to a room member, or take theirs away.
A slot can only be held by one member, so whoever held it before is
unassigned first. The new token map is pushed to selkies and a
gamepad_change notification is broadcast for every change made, followed
by a state update. Unknown tokens are logged and ignored.
paramviewer_tokenstrThe token of the member to change; the controller's own token targets the controller.
paramslotOptional[int]The gamepad slot to assign, or None to unassign.
Returns
Nonefunchandle_assign_mk(target_token) -> NoneHand mouse and keyboard control to a room member.
The controller's own token and None both mean control returns to the
controller. A no-op when the target already holds it; otherwise the token
map is pushed to selkies and an mk_change notification and a state update
are broadcast.
paramtarget_tokenOptional[str]The token of the viewer to receive control, or None (or the controller's token) to give it back to the controller.
Returns
Nonefuncnotify_session_ended() -> NoneTell the room the session has ended and forget every connection.
Returns
None