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