bun icon indicating copy to clipboard operation
bun copied to clipboard

FFI fixed-length buffer type

Open konsumer opened this issue 3 years ago • 1 comments

What is the problem this feature will solve?

Not sure if this is related to #235 but I feel like they could go together.

I am interested in fixed-length buffer value types.

I work on the node-raylib NAPI wrapper, and would like to make a ffi-only wrapper for bun, so only the official raylib DLL is needed. Raylib uses struct values a lot, instead of pointers, and some (that fit into the value of another type, like 4 bytes for i32) can be used by setting the type to something that has the same length, and converting it. An example:

// Color, 4 components, R8G8B8A8 (32bit)
typedef struct Color {
    unsigned char r;        // Color red value
    unsigned char g;        // Color green value
    unsigned char b;        // Color blue value
    unsigned char a;        // Color alpha value
} Color;

So in bun, I can set the type to i32 and jam it in there with this:

const toColor = (r, g, b, a) => (r & 0xFF) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24)

What I would prefer instead is a fixed-length buffer that looks like this:

Uint8Array.from([r,  g,  b,  a])

Or any other way to represent "4 bytes".

Other structs that are passed as values, in raylib, are bigger like this:

typedef struct Texture {
    unsigned int id;        // OpenGL texture id
    int width;              // Texture base width
    int height;             // Texture base height
    int mipmaps;            // Mipmap levels, 1 by default
    int format;             // Data format (PixelFormat type)
} Texture;

since there is no 20byte number-type built-in, there is no way to pass this as a value (although I could probably abuse a larger type like f64 for an 8-byte type.)

What is the feature you are proposing to solve the problem?

If we had a new fixed-length buffer ffi-type, like buffer[20] we could massage the value into the right format, in js-space.

const int32ToBytes = (num) => {
  const arr = new ArrayBuffer(4)
  const view = new DataView(arr)
  view.setUint32(0, num, false)
  return arr
}

const TextureToBytes({id, width, height, mipmaps, format}) => Uint8Array.from([
  ...int32ToBytes(id),
  ...int32ToBytes(width),
  ...int32ToBytes(height),
  ...int32ToBytes(mipmaps),
  ...int32ToBytes(format)
])

This probably needs more tuning, but that is the basic idea.

There are a few existing libs that do this sort of thing in node, like ref-struct-di where you tell it the shape of the struct, and it makes a buffer to back the memory, and it manages the bytes, and it can be sent over the (ffi, napi, etc) wire as bytes or a pointer.

I'm not very familiar with zig, and just ok with C, but I'd be happy to help in any way I can, like as support, or I could get more familiar with zig, if needed.

What alternatives have you considered?

One way to accomplish this would be to FFI a thin wrapper DLL, that just flattens values like this:

void thingThatUseTextureWrapper(uint8_t id1, uint8_t id2, uint8_t id3, uint8_t id4, uint8_t width1, uint8_t width2, uint8_t width3, uint8_t width4, uint8_t height1, uint8_t height2, uint8_t height3, uint8_t height4, uint8_t height1, uint8_t height2, uint8_t height3, uint8_t height4, uint8_t mipmaps1, uint8_t mipmaps2, uint8_t mipmaps3, uint8_t mipmaps4, uint8_t format1, , uint8_t format2, uint8_t format3, uint8_t format4)

and then build the args, on JS-side, from input (splitting them into bytes, each as an arg.) This is sort of what we do currently, on node-raylib, and it works, but it's a lot of extra steps, and requires us to maintain a C lib to do all the wrapping.

konsumer avatar Jul 15 '22 23:07 konsumer

I'm not sure where to start with this, but I'd be willing to give it a shot, if you could point me in the right direction.

On the js-side, I found struct-buffer, shared-structs and c-struct for allocating and using struct-buffers, which might make things simpler. There are quite a few other libraries, too, so between them, maybe a nice bun lib could also be built-in for working with struct values and pointers.

konsumer avatar Sep 18 '22 02:09 konsumer

One idea might be to do what emscripten & clang do, and convert struct values to pointers, in FFI layer.

You can see examples of passing stuct-by-value as pointers here. This is a tester for WAMR native wasm host, but the idea is similar.

I made this simple lib to work with pointers over wasm, and it's pretty useful, similar could definitely be used here.

konsumer avatar Nov 20 '23 17:11 konsumer