Skip to content

aowlspt/abi

Source: aowl/src/aowlspt/abi.nim — 305 lines.

The raw ABI, transliterated from abi/aowlspt_abi.h, for nimony/aowl.

Deliberately mechanical: the same structs, in the same order, with the same calling convention, and nothing else. Ergonomics live in aowlspt.nim, so a new field in the header has exactly one place to be mirrored.

Two nimony specifics that are load-bearing here:

  • Null pointers are written as cast[T](0). nimony's ptr T is non-nil by default and rejects a bare nil literal, which is a feature — it means every null in this file is one somebody chose.
  • {.bycopy.} objects match C layout. AowlSlice is 16 bytes on x86-64 (8-byte pointer, 4-byte length, 4 bytes tail padding); {.packed.} would break the match rather than tighten it. sizeof is asserted against the C header by tests/abi_layout.nim.

Types

Status

nim
  Status* = int32

aowl/src/aowlspt/abi.nim:57

LogLevel

nim
  LogLevel* = enum
    llTrace = 0'i32
    llDebug = 1
    llInfo = 2
    llSuccess = 3
    llWarn = 4
    llError = 5

aowl/src/aowlspt/abi.nim:91

Encoding

nim
  Encoding* = enum
    encJson = 0'i32
    encCbor = 1
    encRaw = 2
    encNif = 3

aowl/src/aowlspt/abi.nim:99

Side

nim
  Side* = enum
    sideServer = 1'i32
    sideClient = 2
    sideSim = 3

aowl/src/aowlspt/abi.nim:105

PatchKind

nim
  PatchKind* = enum
    pkPrefix = 0'i32
    pkPostfix = 1
    pkFinalizer = 2

aowl/src/aowlspt/abi.nim:110

RouteKind

nim
  RouteKind* = enum
    rkStatic = 0'i32
    rkDynamic = 1

aowl/src/aowlspt/abi.nim:115

ModFlag

nim
  ModFlag* = enum
    mfHotReloadable  ## bit 0 — implements stateSave/stateLoad
    mfThreadSafe     ## bit 1 — callbacks are safe off the main thread

aowl/src/aowlspt/abi.nim:119

ModFlags

nim
  ModFlags* = set[ModFlag]

aowl/src/aowlspt/abi.nim:123

Handle

nim
  Handle* = uint64

aowl/src/aowlspt/abi.nim:125

Bytes

nim
  Bytes* = ptr UncheckedArray[uint8]

aowl/src/aowlspt/abi.nim:127

AowlSlice

nim
  AowlSlice* {.bycopy.} = object
    data*: Bytes
    len*: int32

Borrowed bytes, valid only for the duration of the call that produced it.

aowl/src/aowlspt/abi.nim:129

AowlBuffer

nim
  AowlBuffer* {.bycopy.} = object
    data*: Bytes
    len*: int32

Owned bytes, allocated with the host allocator and freed by the receiver.

aowl/src/aowlspt/abi.nim:134

HostInfo

nim
  HostInfo* {.bycopy.} = object
    size*: int32
    abiVersion*: uint32
    abiRevision*: uint32
    side*: int32
    hostName*: AowlSlice
    hostVersion*: AowlSlice
    sptVersion*: AowlSlice
    gameVersion*: AowlSlice
    encodings*: uint32
    modDir*: AowlSlice
    dataDir*: AowlSlice

aowl/src/aowlspt/abi.nim:139

CallbackFn

nim
  CallbackFn* = proc (user: pointer; payload: AowlSlice;
                      outBuf: ptr AowlBuffer): Status {.cdecl.}

aowl/src/aowlspt/abi.nim:154

RouteFn

nim
  RouteFn* = proc (user: pointer; url, body, session: AowlSlice;
                   outBuf: ptr AowlBuffer): Status {.cdecl.}

aowl/src/aowlspt/abi.nim:156

PatchFn

nim
  PatchFn* = proc (user: pointer; target, args: AowlSlice;
                   outBuf: ptr AowlBuffer): Status {.cdecl.}

aowl/src/aowlspt/abi.nim:158

PatchFramePtr

nim
  PatchFramePtr* = pointer

A borrowed const AowlPatchFrame*. Opaque here: the accessors live in aowlspt.nim, over the static readers in abi/aowlspt_frame.h, so that this file stays a transliteration of the struct layout and nothing else.

aowl/src/aowlspt/abi.nim:161

TypedPatchFn

nim
  TypedPatchFn* = proc (user: pointer; frame: PatchFramePtr): Status {.cdecl.}

Revision 4's patch handler: the same two return statuses, and a borrowed view of the saved registers instead of a JSON payload.

aowl/src/aowlspt/abi.nim:167

ArgKind

nim
  ArgKind* = enum
    akNone = 0'i32
    akInt = 1
    akFloat = 2
    akDouble = 3
    akObject = 4
    akValue = 5
    akBigValue = 6
    akVoid = 7
    akStack = 8
    akUnknown = 9

What one slot of a typed frame is, decided once at registration. Mirrors AowlArgKind; the numbers are wire values.

aowl/src/aowlspt/abi.nim:171

HostApi

nim
  HostApi* {.bycopy.} = object
    size*: int32
    ctx*: pointer
    info*: ptr HostInfo

    alloc*: proc (ctx: pointer; bytes: int32): pointer {.cdecl.}
    free*: proc (ctx: pointer; p: pointer) {.cdecl.}

    log*: proc (ctx: pointer; level: int32; message: AowlSlice) {.cdecl.}
    lastError*: proc (ctx: pointer; outSlice: ptr AowlSlice) {.cdecl.}

    configGet*: proc (ctx: pointer; key: AowlSlice; outBuf: ptr AowlBuffer): Status {.cdecl.}
    configSet*: proc (ctx: pointer; key, valueJson: AowlSlice): Status {.cdecl.}

    dbGet*: proc (ctx: pointer; path: AowlSlice; outBuf: ptr AowlBuffer): Status {.cdecl.}
    dbPatch*: proc (ctx: pointer; path, patchJson: AowlSlice): Status {.cdecl.}

    routeRegister*: proc (ctx: pointer; url: AowlSlice; kind: int32;
                          handler: RouteFn; user: pointer): Status {.cdecl.}

    eventSubscribe*: proc (ctx: pointer; name: AowlSlice;
                           handler: CallbackFn; user: pointer): Status {.cdecl.}
    eventEmit*: proc (ctx: pointer; name, payload: AowlSlice): Status {.cdecl.}

    call*: proc (ctx: pointer; target, args: AowlSlice; outBuf: ptr AowlBuffer): Status {.cdecl.}
    resolve*: proc (ctx: pointer; typeName: AowlSlice; outHandle: ptr Handle): Status {.cdecl.}
    handleRelease*: proc (ctx: pointer; handle: Handle) {.cdecl.}

    patch*: proc (ctx: pointer; target: AowlSlice; kind: int32;
                  handler: PatchFn; user: pointer): Status {.cdecl.}

    schedule*: proc (ctx: pointer; delayMs: int32;
                     cb: CallbackFn; user: pointer): Status {.cdecl.}
    invokeMain*: proc (ctx: pointer; cb: CallbackFn; user: pointer): Status {.cdecl.}

    nowMs*: proc (ctx: pointer): int64 {.cdecl.}

    # -- revision 2 ---------------------------------------------------------
    # Appended, never inserted: a mod built against revision 1 checks `size`
    # and simply does not see these.
    storeGet*: proc (ctx: pointer; key: AowlSlice; outBuf: ptr AowlBuffer): Status {.cdecl.}
    storeSet*: proc (ctx: pointer; key, value: AowlSlice): Status {.cdecl.}
    storeList*: proc (ctx: pointer; prefix: AowlSlice; outBuf: ptr AowlBuffer): Status {.cdecl.}

    # -- revision 3 ---------------------------------------------------------
    # The address behind a handle, for a mod that wants to reach an object on
    # its own fast path rather than through `call`. Present only on a host with
    # a managed heap, which is why `size` may still say revision 2 on a host
    # built from this same header -- see `livePointersReady`.
    handlePointer*: proc (ctx: pointer; handle: Handle;
                          outAddress: ptr uint64): Status {.cdecl.}
    handlePin*: proc (ctx: pointer; handle: Handle;
                      outPinned: ptr Handle): Status {.cdecl.}

    # -- revision 4 ---------------------------------------------------------
    # The same detour, with the arguments left in the registers the thunk saved
    # rather than described as JSON. See `patchTyped` in `aowlspt.nim` for what
    # that is worth and `abi/aowlspt_frame.h` for how the frame is read.
    patchTyped*: proc (ctx: pointer; target: AowlSlice; kind: int32;
                       handler: TypedPatchFn; user: pointer): Status {.cdecl.}

    # -- revision 5 ---------------------------------------------------------
    # Hand a notification to whatever the host has that reaches this player --
    # a websocket, on the backend. `ErrNotFound` means "no connection for that
    # session", which is a normal answer and the mod's cue to fall back rather
    # than a failure.
    notifyPush*: proc (ctx: pointer; session, payload: AowlSlice): Status {.cdecl.}

    # -- revision 6 ---------------------------------------------------------
    # Run `cb` on Unity's thread DURING rendering, where immediate-mode
    # `UnityEngine.GL` drawing rasterizes -- for native ESP and GL HUDs, which
    # `invokeMain` (the update phase) is on the right thread but the wrong point
    # in the frame to draw from. `ErrUnsupported` means no render-phase drain is
    # bound on this host/build/scene; a mod gates on
    # `call("aowlspt.host::render_thread")` -> `bound:true` and draws only then.
    invokeRender*: proc (ctx: pointer; cb: CallbackFn; user: pointer): Status {.cdecl.}

aowl/src/aowlspt/abi.nim:185

ModInfo

nim
  ModInfo* {.bycopy.} = object
    size*: int32
    abiVersion*: uint32
    abiRevision*: uint32
    guid*: AowlSlice
    name*: AowlSlice
    author*: AowlSlice
    version*: AowlSlice
    sptRange*: AowlSlice
    sides*: uint32
    flags*: uint32

aowl/src/aowlspt/abi.nim:262

ModApi

nim
  ModApi* {.bycopy.} = object
    size*: int32
    self*: pointer
    onLoad*: proc (self: pointer): Status {.cdecl.}
    onUpdate*: proc (self: pointer; elapsedMs: int64): Status {.cdecl.}
    onUnload*: proc (self: pointer): Status {.cdecl.}
    stateSave*: proc (self: pointer; outBuf: ptr AowlBuffer): Status {.cdecl.}
    stateLoad*: proc (self: pointer; state: AowlSlice): Status {.cdecl.}

aowl/src/aowlspt/abi.nim:274

Constants

AbiVersion

nim
  AbiVersion* = 1'u32

aowl/src/aowlspt/abi.nim:18

AbiRevision

nim
  AbiRevision* = 6'u32

aowl/src/aowlspt/abi.nim:19

HostApiSizeRev1

nim
  HostApiSizeRev1* = 168'i32

aowl/src/aowlspt/abi.nim:21

HostApiSizeRev2

nim
  HostApiSizeRev2* = 192'i32

aowl/src/aowlspt/abi.nim:22

HostApiSizeRev3

nim
  HostApiSizeRev3* = 208'i32

aowl/src/aowlspt/abi.nim:23

HostApiSizeRev4

nim
  HostApiSizeRev4* = 216'i32

aowl/src/aowlspt/abi.nim:24

HostApiSizeRev5

nim
  HostApiSizeRev5* = 224'i32

aowl/src/aowlspt/abi.nim:25

HostApiSizeRev6

nim
  HostApiSizeRev6* = 232'i32

How large HostApi was at each revision, as literals.

A capability test has to be against the boundary the capability appeared at, not against sizeof(HostApi). The obvious form -- "the host's size is at least the size I know about" -- is right for exactly one revision: revision 3 grows sizeof to 208, so a mod asking only for the revision-2 store would start refusing a revision-2 host that has it.

Literals rather than arithmetic over the struct because the whole point is that they must not move when the struct does. tests/abi_layout.nim and tests/abi_layout.c pin all four to the real offsets, from the two sides.

Revision 3 is handlePointer/handlePin, revision 4 is patchTyped and revision 5 is notifyPush. They are separate boundaries rather than one because a host may have any of them without the others: the addresses need a managed heap, the typed patch needs a working detour engine, the push needs a listening socket, and size says how much was filled rather than which header the host saw.

Revision 5 is where the watermark in that sentence stopped being free. Only the client host can fill 3 and 4; only the backend can fill 5; one integer cannot say "the fifth and not the third". A host in that position fills the entries under its watermark with the refusal it already owes -- abi/aowlspt_notify.h does exactly that for the backend -- so a size test keeps meaning "there is a function here that will answer", which is all it ever established. It cannot mean more than that here in any case: nimony will not cast a proc field to a pointer to compare it against null.

aowl/src/aowlspt/abi.nim:26

Ok

nim
  Ok* = 0'i32

aowl/src/aowlspt/abi.nim:60

ErrGeneric

nim
  ErrGeneric* = -1'i32

aowl/src/aowlspt/abi.nim:61

ErrAbi

nim
  ErrAbi* = -2'i32

aowl/src/aowlspt/abi.nim:62

ErrNotFound

nim
  ErrNotFound* = -3'i32

aowl/src/aowlspt/abi.nim:63

ErrBadArg

nim
  ErrBadArg* = -4'i32

aowl/src/aowlspt/abi.nim:64

ErrDecode

nim
  ErrDecode* = -5'i32

aowl/src/aowlspt/abi.nim:65

ErrUnsupported

nim
  ErrUnsupported* = -6'i32

aowl/src/aowlspt/abi.nim:66

ErrWrongThread

nim
  ErrWrongThread* = -7'i32

aowl/src/aowlspt/abi.nim:67

ErrDisposed

nim
  ErrDisposed* = -8'i32

aowl/src/aowlspt/abi.nim:68

ErrModFault

nim
  ErrModFault* = -9'i32

aowl/src/aowlspt/abi.nim:69

ErrConfigParse

nim
  ErrConfigParse* = -10'i32

config.json exists and is not readable JSON, so no key in it can be answered. Distinct from ErrNotFound, which is the ordinary "no such setting, use your default".

They were the same status, and that is how a BOM on the mod manager's config made it read activeLists as empty, resolve nothing, and write a selection naming only itself -- one mod out of ten loaded on the next start, with no error anywhere. A mod that falls back to defaults on any failure, which is most of them, cannot tell "absent" from "the file is broken" without this.

lastError() after one of these names the file and the fault -- the offset and what was found there -- not the key. The key is not what is missing.

Returned by a prefix patch to suppress the original method.

aowl/src/aowlspt/abi.nim:71

PatchSkip

nim
  PatchSkip* = 1'i32

aowl/src/aowlspt/abi.nim:88

SymAbiVersion

nim
  SymAbiVersion* = "aowlspt_abi_version"

aowl/src/aowlspt/abi.nim:284

SymDescribe

nim
  SymDescribe* = "aowlspt_describe"

aowl/src/aowlspt/abi.nim:285

SymInit

nim
  SymInit* = "aowlspt_init"

aowl/src/aowlspt/abi.nim:286

Routines

nilBytes

nim
func nilBytes*(): Bytes {.inline.}

aowl/src/aowlspt/abi.nim:288

isNilPtr

nim
func isNilPtr*(p: pointer): bool {.inline.}

aowl/src/aowlspt/abi.nim:289

isNilBytes

nim
func isNilBytes*(p: Bytes): bool {.inline.}

aowl/src/aowlspt/abi.nim:290

emptySlice

nim
func emptySlice*(): AowlSlice {.inline.}

aowl/src/aowlspt/abi.nim:292

emptyBuffer

nim
func emptyBuffer*(): AowlBuffer {.inline.}

aowl/src/aowlspt/abi.nim:295

sideBit

nim
func sideBit*(s: Side): uint32 {.inline.}

The sides bitmask is 1 << AowlSide, matching the header.

aowl/src/aowlspt/abi.nim:298

flagBits

nim
func flagBits*(flags: ModFlags): uint32

aowl/src/aowlspt/abi.nim:301

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.