feat(sterni/blipqn): reuse address and socket for multiple Sends

This is achieved by storing the resources we need to acquire for
interacting with the flipdot (socket fd and addrinfo struct) in a
`struct flipdot` that is dynamically allocated and treated as an opaque
pointer object via the BQN FFI.

To make sure these resources are released correctly, we only provide a
lisp style WithFlipdot to the user which takes care of acquiring and
releasing the `struct flipdot`. This works even if an error occurs in
the function the user provides thanks to _defer_. I'm not sure if
calling it _defer_ is right since Go's error handling works differently,
so defer really is deferred execution in a sense which doesn't really
fit what we're doing here. The closest is probably Haskell's bracket,
but that name references it's triadic nature which doesn't fit our
implementation.

Change-Id: Iff65d277a448dbe0c6ac93e816ece5ab6fa10190
Reviewed-on: https://cl.tvl.fyi/c/depot/+/13011
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
This commit is contained in:
sterni 2025-01-15 20:46:32 +01:00
parent 1027e21eee
commit a9e121380b
3 changed files with 78 additions and 34 deletions

View file

@ -1,20 +1,42 @@
MakeFlipdot⇐
WithFlipdot⇐
# - host (char*) argument:
# - Assume char is i8 (safe even if it's actually u8 if
# ASCII). We can't let •FFI check this for us since CBQN doesn't support
# specifying c7. We do this in MakeFlipdot
# - Needs to be zero-terminated, we also do this in MakeFlipdot.
# - bitmap_len (size_t) argument: assume size_t is at least u32.
ffi_send_to_flipdot "libflipdot.so" •FFI "i8""send_to_flipdot""*i8:c8""u16""*u8""u32"
t {
bool "i8"
# needs bound checking in wrapper (no c7 in CBQN) and zero byte appended
charp "*i8:c8"
# assume size_t is at least 32bit
size "u32"
# used for struct flipdot *
hdl "*"
}
ffi_flipdot_open "libflipdot.so" •FFI t.hdl"flipdot_open"t.charp"u16"
ffi_flipdot_close "libflipdot.so" •FFI """flipdot_close"t.hdl
ffi_flipdot_send "libflipdot.so" •FFI t.bool"flipdot_send"t.hdl"*u8"t.size
Compact +´( × 2)˘(8)
MakeFlipdot {
hostport 𝕊 wh:
"Total pixel count must be divisible by 8" ! 0=8|w×h
hostportwh:
"Hostname must be ASCII" ! ´(@+127)host
"Total pixel count must be divisible by 8" ! 0=8|w×h
hdl FFI_flipdot_open (host@)port
Close {𝕤 FFI_flipdot_close hdl}
shape hw
empty shape0
Send {"sendto(2) error"!FFI_send_to_flipdot (host@)port() Compact 𝕩}
Send {FFI_flipdot_send hdl() Compact 𝕩}
}
# Inspired by Go's defer. Will execute 𝔽 after 𝔾 (passing 𝕨 and 𝕩 to each)
# even if an error occurs. If an error occurred, _defer_ will cause an
# assertion failure with the error message after executing 𝔾. This looses
# the original error location, though.
_defer_ {
# At least in CBQN, •CurrentError may fail for namespace related reasons
sr (1𝔾)(0(•CurrentError("_defer_: Unknown Error occurred"˙))) 𝕩 𝔽 𝕩 rr!s;
(𝕨𝔽) _𝕣_ (𝕨𝔾) 𝕩
}
WithFlipdot {{𝕩.Close @} _defer_ 𝕏 MakeFlipdot 𝕨}