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:
parent
1027e21eee
commit
a9e121380b
3 changed files with 78 additions and 34 deletions
|
|
@ -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 ← {
|
||||
host‿port 𝕊 w‿h:
|
||||
"Total pixel count must be divisible by 8" ! 0=8|w×h
|
||||
host‿port‿w‿h:
|
||||
"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 ⇐ h‿w
|
||||
empty ⇐ shape⥊0
|
||||
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
|
||||
s‿r ← (1⊸⋈∘𝔾)⎊(0⊸⋈∘(•CurrentError⎊("_defer_: Unknown Error occurred"˙))) 𝕩 ⋄ 𝔽 𝕩 ⋄ r⊣r!s;
|
||||
(𝕨⊸𝔽) _𝕣_ (𝕨⊸𝔾) 𝕩
|
||||
}
|
||||
|
||||
WithFlipdot ← {{𝕩.Close @} _defer_ 𝕏 MakeFlipdot 𝕨}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env BQN
|
||||
|
||||
⟨MakeFlipdot⟩ ⇐ •Import "../lib/"⊸∾⍟("bin" (⊣≡(-∘≠⊸↑)) •path) "blipqn.bqn"
|
||||
⟨WithFlipdot⟩ ⇐ •Import "../lib/"⊸∾⍟("bin" (⊣≡(-∘≠⊸↑)) •path) "blipqn.bqn"
|
||||
|
||||
examples ← ⍉>⟨
|
||||
"random"‿{𝕩.Send 𝕩.shape •rand.Range 2},
|
||||
|
|
@ -19,11 +19,9 @@ opts ← {
|
|||
host‿port‿width‿height‿example ← •args
|
||||
|
||||
example ⇐
|
||||
addr ⇐ host⋈•ParseFloat port
|
||||
dim ⇐ •ParseFloat¨ width‿height
|
||||
cfg ⇐ ⟨host⟩∾•ParseFloat¨ port‿width‿height
|
||||
}
|
||||
|
||||
action ← examples (⊏⊸(⊑∘⊐) ⊑ ⊏˜⟜1)⟜< opts.example
|
||||
|
||||
flip ← opts.addr MakeFlipdot opts.dim
|
||||
Action flip
|
||||
opts.cfg WithFlipdot action
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#define _DEFAULT_SOURCE // see getnameinfo(3), for NI_MAX*
|
||||
#define _POSIX_C_SOURCE 200112L // see getaddrinfo(3)
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -19,29 +19,53 @@ int resolve_addr(char *host, char *port, struct addrinfo **addrs) {
|
|||
return getaddrinfo(host, port, &hints, addrs);
|
||||
}
|
||||
|
||||
// Send given bytes[len] to the host:port via UDP, returns 1 if all bytes
|
||||
// where sent and no locally detectable errors occurred.
|
||||
int8_t send_to_flipdot(char *host, in_port_t port_number, uint8_t *bitmap,
|
||||
size_t bitmap_len) {
|
||||
struct flipdot {
|
||||
int sockfd;
|
||||
struct addrinfo *addrs;
|
||||
};
|
||||
|
||||
// Assumes all pointers in struct flipdot are not NULL which should be the case
|
||||
// for any struct returned by flipdot_open().
|
||||
void flipdot_close(struct flipdot *flipdot) {
|
||||
freeaddrinfo(flipdot->addrs);
|
||||
close(flipdot->sockfd);
|
||||
free(flipdot);
|
||||
}
|
||||
|
||||
// Returns NULL if some error occurred. Note that errno isn't necessarily set.
|
||||
struct flipdot *flipdot_open(char *host, in_port_t port_number) {
|
||||
char port[NI_MAXSERV];
|
||||
int sockfd = -1;
|
||||
ssize_t sent = 0;
|
||||
struct addrinfo *addrs = NULL;
|
||||
struct flipdot *flipdot = malloc(sizeof(struct flipdot));
|
||||
if (flipdot == NULL) goto error;
|
||||
|
||||
if (snprintf(port, sizeof port, "%d", port_number) < 0) goto error;
|
||||
memset(flipdot, 0, sizeof(struct flipdot));
|
||||
flipdot->sockfd = -1;
|
||||
flipdot->addrs = NULL;
|
||||
|
||||
if (resolve_addr(host, port, &addrs) != 0) goto error;
|
||||
if (snprintf(port, sizeof(port), "%d", port_number) < 0) goto error;
|
||||
if (resolve_addr(host, port, &flipdot->addrs) != 0) goto error;
|
||||
|
||||
sockfd = socket(addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sockfd < 0) goto error;
|
||||
flipdot->sockfd = socket(flipdot->addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (flipdot->sockfd < 0) goto error;
|
||||
|
||||
sent =
|
||||
sendto(sockfd, bitmap, bitmap_len, 0, addrs->ai_addr, addrs->ai_addrlen);
|
||||
|
||||
if (sent != (ssize_t)bitmap_len) goto error;
|
||||
return flipdot;
|
||||
|
||||
error:
|
||||
if (addrs != NULL) freeaddrinfo(addrs);
|
||||
if (sockfd >= 0) close(sockfd);
|
||||
if (flipdot != NULL) {
|
||||
if (flipdot->sockfd >= 0) close(flipdot->sockfd);
|
||||
if (flipdot->addrs != NULL) freeaddrinfo(flipdot->addrs);
|
||||
free(flipdot);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Send given bytes[len] to the given flipdot, returns 1 if all bytes were sent
|
||||
// and no locally detectable errors occurred, 0 otherwise.
|
||||
int8_t flipdot_send(struct flipdot *flipdot, uint8_t *bitmap,
|
||||
size_t bitmap_len) {
|
||||
ssize_t sent = sendto(flipdot->sockfd, bitmap, bitmap_len, 0,
|
||||
flipdot->addrs->ai_addr, flipdot->addrs->ai_addrlen);
|
||||
|
||||
return (sent == (ssize_t)bitmap_len);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue