BGE/Controller
Alias: BGE.Controller
Enums
BGE.Controller.ConnectionMode
Properties
http(default: "http")websocket(default: "websocket")
BGE.Controller.ControllerServerEventType
Properties
onOpen(default: "onOpen")onMessage(default: "onMessage")onClose(default: "onClose")
Other
ActionBinding
One bindAction() binding - internal bookkeeping, not part of the public API.
Properties
remoteButton(dynamic) — string, or invalid if this action has no remote bindingcontrollerButton(dynamic) — string, or invalid if this action has no remote bindingplayerIndex(integer) — string, or invalid if this action has no controller bindinglabel(dynamic) — string, or invalid if none given
AxisBinding
One bindAxis() binding - internal bookkeeping, not part of the public API.
Properties
stick(string)playerIndex(integer)label(dynamic) — string, or invalid if none given
ActionState
One action's live press/held/release/duration state - internal bookkeeping, not part of the public API (isActionPressed()/isActionHeld()/isActionReleased()/getActionHeldTimeMs() are how a game reads this).
Properties
press(boolean)held(boolean)release(boolean)heldTimeMs(integer)
ControlMap
Unified mapping from remote-button and controller input to named actions/axes - the one class a game needs to learn to support both input sources. Exposed as Game.controls; see Game.enableControllerInput().
Parameters
registry(BGE.Controller.ControllerRegistry)
Returns
BGE.Controller.ControlMap
ControllerConnectionState
Internal per-connection controller state (buttons/sticks/held-timers/custom payload) plus the remote's own d-pad fallback state. Not part of the public API - a game uses BGE.Controller.ControlMap (game.controls) instead.
Properties
buttons(dynamic) — name -> booleanheldTimers(dynamic) — name -> booleansticks(dynamic) — name -> roTimespan, present only while heldcustom(dynamic) — name -> BGE.Math.Vector
ControllerRegistry
CONTROLLER_HTTP_BUFFER_SIZE
Default: 10240
CONTROLLER_WS_READ_SIZE
Default: 10240
CONTROLLER_WS_BUFFER_MAX_SIZE
Cap on connection.wsBuffer's accumulated size, mirroring CONTROLLER_HTTP_BUFFER_SIZE's guard on httpBuffer - without this, a client declaring a huge 64-bit extended WebSocket frame payload length and then trickling/withholding bytes could grow wsBuffer unbounded while decodeFrames() keeps waiting for the rest of the frame to arrive.
Default: 65536
ControllerConnection
Properties
socket(roStreamSocket)mode(string)wsBuffer(roByteArray)httpBuffer(roByteArray) — Accumulates raw bytes across possibly-multiple recv()/poll() calls ' until a full HTTP request (terminated by a blank line) has arrived - ' see onHttpReadable(). A single recv() is not guaranteed to return an ' entire request in one call, especially a real browser's larger ' WebSocket-upgrade request (many headers) split across TCP segments.staleReadPolls(integer) — Consecutive poll() cycles where isReadable() reported true but ' receive() returned a negative "not ready yet" result with no bytes ' actually delivered - see onHttpReadable()/onWebSocketReadable(). Reset ' to 0 on every poll that actually receives bytes. Guards against a dead ' connection (peer vanished without a clean FIN) spinning forever now ' that a negative receive() no longer closes the connection immediately. ' ' IMPORTANT, confirmed on a real 2024 Roku Ultra with an on-device debug ' capture: isReadable() is NOT a reliable "there might be data" signal ' for a websocket socket on this hardware - once a connection has ever ' been read from, isReadable() reports true on essentially every single ' subsequent poll() even while the peer is sitting perfectly idle and ' sends nothing, and receive() returns -1 every one of those times, with ' no gaps and no distinguishing pattern versus an actually-dead peer. ' Concretely: a real browser client left untouched (no stick/button ' movement) hit exactly this and got closed after ~300 consecutive ' negative-receive polls (~5s) - not a hypothetical, this is what a ' silently-idle player looks like on this hardware. There is no signal ' available here to tell "healthy but idle" apart from "actually dead" - ' both look identical. The real fix is the browser client ' (controller-web/index.html) resending its current state once a ' second even when nothing changed, so a healthy connection always has ' real bytes arriving well inside this threshold; staleReadPolls stays ' as a backstop that still reaps a connection whose heartbeats have ' genuinely stopped (tab closed, network gone), just no longer trusted ' to distinguish idle-vs-dead on its own.pendingSend(roByteArray) — Bytes still waiting to be flushed to the socket - see ' ControllerServer.queueSend()/flushPendingSend().invalidwhen ' nothing is queued. roStreamSocket.send() is non-blocking and can ' return 0 ("would block, try again") under ordinary backpressure - ' looping on that in place would busy-spin the whole render loop ' (Roku's socket has no blocking wait), so instead any unsent ' remainder is parked here and retried once per poll() cycle until it ' fully flushes (or a real send error occurs).pendingSendOffset(integer) — How far into pendingSend the previous partial send(s) already got.pendingSendAction(string) — What to do once pendingSend fully flushes - "" (nothing further), ' "close" (close the connection - e.g. after an HTTP response), or ' "upgrade" (flip into websocket mode and emit onOpen - deferred until ' the handshake response has actually reached the client, so a flush ' failure never leaves a "ghost" upgraded connection).
CONTROLLER_MAX_STALE_READ_POLLS
Consecutive negative-receive polls before a connection is assumed dead and closed - see ControllerConnection.staleReadPolls. At ~60 polls/sec this is a multi-second grace period. The browser client now resends its state once a second (see controller-web/index.html), so this threshold only needs to comfortably clear that 1s heartbeat cadence (it does, by ~5x) - it no longer needs to double as "long enough for the flakiest possible isReadable() behavior on idle, untouched input," since that case no longer exists once the client is always sending.
Default: 300
ControllerServerEvent
Properties
type(string) — BGE.Controller.ControllerServerEventTypeconnectionId(string) — BGE.Controller.ControllerServerEventTypemessage(string) — only set for onMessage
HttpRequest
A parseHttpRequest() result - the request line plus headers, both used verbatim by onHttpReadable()'s routing. headers stays a plain dictionary (its keys are whatever header names the client happened to send, not a fixed set).
Properties
command(string)path(string)protocol(string)headers(object)
ControllerServer
Minimal HTTP + WebSocket (RFC 6455) server for streaming controller input from a browser page, without SceneGraph. Adapted and trimmed from https://github.com/markwpearce/roku-gamepad - serves GET only (static files under pkg:/source/controller-web), no upload/directory listing.
Parameters
port(integer)
Properties
port(integer)
Returns
BGE.Controller.ControllerServer