|
| 1 | +## Per-context registry of live `{.ffiHandle.}` objects. The object stays here, |
| 2 | +## in `FFIContext.handles`; only its `uint64` id crosses the boundary. Ids are |
| 3 | +## monotonic and never recycled (0 = null), so a stale/forged id misses cleanly. |
| 4 | +## FFI-thread-only access, so no locking. |
| 5 | + |
| 6 | +import std/tables |
| 7 | +import results |
| 8 | +import ./cbor_serial |
| 9 | + |
| 10 | +type |
| 11 | + FFIHandleRoot* = ref object of RootObj |
| 12 | + ## Base every `{.ffiHandle.}` type inherits from, so handle refs are storable |
| 13 | + ## under one static type. |
| 14 | + |
| 15 | + FFIHandleEntry = object |
| 16 | + obj: FFIHandleRoot |
| 17 | + typeName: string |
| 18 | + |
| 19 | + FFIHandleRegistry* = object |
| 20 | + nextId*: uint64 |
| 21 | + byHandle*: Table[uint64, FFIHandleEntry] |
| 22 | + |
| 23 | +proc initHandleRegistry*(reg: var FFIHandleRegistry) = |
| 24 | + reg.nextId = 0'u64 |
| 25 | + reg.byHandle = initTable[uint64, FFIHandleEntry]() |
| 26 | + |
| 27 | +proc deinitHandleRegistry*(reg: var FFIHandleRegistry) = |
| 28 | + reg.byHandle = default(Table[uint64, FFIHandleEntry]) |
| 29 | + reg.nextId = 0'u64 |
| 30 | + |
| 31 | +proc register*( |
| 32 | + reg: var FFIHandleRegistry, obj: FFIHandleRoot, typeName: string |
| 33 | +): uint64 = |
| 34 | + ## Stores `obj`, returns its fresh handle id (>0). |
| 35 | + reg.nextId.inc() |
| 36 | + reg.byHandle[reg.nextId] = FFIHandleEntry(obj: obj, typeName: typeName) |
| 37 | + reg.nextId |
| 38 | + |
| 39 | +proc lookup*( |
| 40 | + reg: var FFIHandleRegistry, handle: uint64, typeName: string |
| 41 | +): Result[FFIHandleRoot, string] = |
| 42 | + ## Live ref for `handle`; err if absent or registered under another type. |
| 43 | + let entry = reg.byHandle.getOrDefault(handle) |
| 44 | + if entry.obj.isNil(): |
| 45 | + return err("no ffiHandle with id " & $handle) |
| 46 | + if entry.typeName != typeName: |
| 47 | + return err( |
| 48 | + "ffiHandle " & $handle & " has type '" & entry.typeName & "', expected '" & |
| 49 | + typeName & "'" |
| 50 | + ) |
| 51 | + ok(entry.obj) |
| 52 | + |
| 53 | +proc release*(reg: var FFIHandleRegistry, handle: uint64): bool {.discardable.} = |
| 54 | + ## Drops the entry; true iff it existed. |
| 55 | + if not reg.byHandle.hasKey(handle): |
| 56 | + return false |
| 57 | + reg.byHandle.del(handle) |
| 58 | + return true |
| 59 | + |
| 60 | +proc releaseAll*(reg: var FFIHandleRegistry) = |
| 61 | + ## Drops every entry. Must run on the FFI thread that allocated the refs. |
| 62 | + reg.byHandle.clear() |
| 63 | + |
| 64 | +proc encodeHandle*(id: uint64): seq[byte] = |
| 65 | + ## Wire-form bytes for a handle id. Single ABI seam for future format changes. |
| 66 | + cborEncode(id) |
0 commit comments