tecs.platform.gamepadbackend

The observation contract between Tecs input policy and a gamepad source.

Tecs owns the policy: the layer stack, frame and fixed-step edges, deadzones, and the positional vocabulary a game names a control by. A backend owns device enumeration, hot plug and the platform's own event stream, and reports what it saw as one array of plain observations per poll. Nothing crosses the seam except that array, the capability and identity answers a device is asked for directly, and the one outbound command, which is rumble.

Every field is a plain function rather than a method, so a test double is a record of closures. That is what makes the whole of tecs.input testable with no gamepad attached: none is the device-free backend a headless build gets, and a test that wants to drive a device supplies its own.

local backend <const> = tecs.platform.gamepadbackend.none()
local buffer <const> = {tecs.platform.gamepadbackend.newEvent()}
print(backend.drain(buffer))

Module contents

Constructors

ConstructorDescription
newEventReturns one zeroed observation record.

Types

TypeKindDescription
BackendrecordReads one platform's gamepads.
EventrecordOne observation a backend produces.
InforecordWhat one device reports about itself.
PowerStatetypeNames what a device says about its power source.

Functions

FunctionKindDescription
missingfunctionReturns the info a device that is not there reports.
nonefunctionReturns a backend with no devices and no platform source.

Values

ValueKindDescription
KIND_ADDEDvariableReports that a device appeared.
KIND_AXISvariableReports one axis's new value.
KIND_BUTTON_DOWNvariableReports a button transition to held.
KIND_BUTTON_UPvariableReports a button transition to released.
KIND_REMOVEDvariableReports that a device went away.

Constructors#

newEventconstructor#

function newEvent(): Event

Returns one zeroed observation record.

A caller preallocates a drain buffer of these once and reuses it, because the seam is crossed every frame and allocating per observation on a path that exists to avoid per-device calls gives the saving straight back.

Returns

TypeDescription
Event

a fresh caller-owned observation record

Types#

Backendrecord#

record Backend
    available: boolean
    poll: function(): integer
    drain: function(out: {Event}): integer
    attached: function(): {integer}
    info: function(device: integer): Info
    hasButton: function(device: integer, code: integer): boolean
    hasAxis: function(device: integer, code: integer): boolean
    name: function(device: integer): string
    guid: function(device: integer): string
    rumble: function(device: integer, low: number, high: number, seconds: number): boolean
    close: function(): nil
end

Reads one platform's gamepads.

Methods

poll#
poll: function(): integer

Read-only. Reads everything the platform produced since the previous call and returns how many observations are waiting. Runs on the frame thread, because that is the only thread allowed to move platform work into Tecs.

Returns
TypeDescription
integer
drain#
drain: function(out: {Event}): integer

Read-only. Fills out with waiting observations in order and returns how many it wrote. Writes at most #out, and the rest wait for the next call rather than being lost.

Arguments
NameTypeDescription
out{Event}
Returns
TypeDescription
integer
attached#
attached: function(): {integer}

Read-only. Returns a fresh list of every attached device's identifier.

Returns
TypeDescription
{integer}
info#
info: function(device: integer): Info

Read-only. Returns what one device says about itself. A device that is gone answers with connected false.

Arguments
NameTypeDescription
deviceinteger
Returns
TypeDescription
Info
hasButton#
hasButton: function(device: integer, code: integer): boolean

Read-only. Reports whether one device has the button a code names.

Arguments
NameTypeDescription
deviceinteger
codeinteger
Returns
TypeDescription
boolean
hasAxis#
hasAxis: function(device: integer, code: integer): boolean

Read-only. Reports whether one device has the axis a code names.

Arguments
NameTypeDescription
deviceinteger
codeinteger
Returns
TypeDescription
boolean
name#
name: function(device: integer): string

Read-only. Returns the device's display name, and an empty string when the platform reports none.

Arguments
NameTypeDescription
deviceinteger
Returns
TypeDescription
string
guid#
guid: function(device: integer): string

Read-only. Returns the stable identity a saved binding matches on, and an empty string when the platform reports none.

Arguments
NameTypeDescription
deviceinteger
Returns
TypeDescription
string
rumble#
rumble: function(device: integer, low: number, high: number, seconds: number): boolean

Read-only. Plays one rumble effect for seconds, replacing whatever the device was playing, and reports whether it started. A zero or negative duration stops the device instead.

Arguments
NameTypeDescription
deviceinteger
lownumber
highnumber
secondsnumber
Returns
TypeDescription
boolean
close#
close: function(): nil

Read-only. Releases the platform source. Calling anything afterwards is a mistake in the caller.

Returns
TypeDescription
nil

Fields

available#
available: boolean

Read-only. Reports whether a real device source opened. A false here still answers every call; no device ever appears.

Eventrecord#

record Event
    kind: integer
    device: integer
    code: integer
    value: number
end

One observation a backend produces.

One record covers every kind, because the array crossing the seam has to be one contiguous run of identical elements. A field a kind does not name carries whatever the previous use of that record left behind, so a reader reads only the fields its kind claims.

Fields

kind#
kind: integer

Read-only. Selects the observation from this module's kind constants.

device#
device: integer

Read-only. Names the device the observation is about.

code#
code: integer

Read-only. Carries the button or axis code, and zero for a device appearing or going away.

value#
value: number

Read-only. Carries the axis value from -1 to 1, and zero for every other kind.

Inforecord#

record Info
    connected: boolean
    forceFeedback: boolean
    power: PowerState
    percent: integer
end

What one device reports about itself.

Fields

connected#
connected: boolean

Read-only. Reports whether the device is still attached.

forceFeedback#
forceFeedback: boolean

Read-only. Reports whether the device accepts rumble on this platform.

power#
power: PowerState

Read-only. Reports what the device says about its power source.

percent#
percent: integer

Read-only. Reports the battery percentage, and -1 when the device will not say.

PowerStatetype#

type PowerState = "unknown" | "noBattery" | "onBattery" | "charging" | "charged"

Names what a device says about its power source.

Compatibility surface: these strings are stable for saved bindings and game branches. "error" is not among them because no source distinguishes a failed read from an unknown one.

Functions#

missingfunction#

function missing(): Info

Returns the info a device that is not there reports.

Returns

TypeDescription
Info

a fresh caller-owned info record describing nothing

nonefunction#

function none(): Backend

Returns a backend with no devices and no platform source.

This is what a headless build and a device-free test get. Every call answers, nothing is ever attached, and rumble reports that it did not run.

Returns

TypeDescription
Backend

a fresh backend that observes nothing

Values#

KIND_ADDEDvariable#

const KIND_ADDED: integer

Reports that a device appeared.

KIND_AXISvariable#

const KIND_AXIS: integer

Reports one axis's new value.

KIND_BUTTON_DOWNvariable#

const KIND_BUTTON_DOWN: integer

Reports a button transition to held.

KIND_BUTTON_UPvariable#

const KIND_BUTTON_UP: integer

Reports a button transition to released.

KIND_REMOVEDvariable#

const KIND_REMOVED: integer

Reports that a device went away.