erincatto / box2c

Library for 2D Game Physics
MIT License
396 stars 30 forks source link

FFI and passing C structs by value #163

Open BurningWitness opened 1 month ago

BurningWitness commented 1 month ago

In some languages (in my case Haskell; I don't know the true scope of this issue) passing C structs by value is not supported at FFI level, leading to a need for shims. This is annoying, but generally not much of an issue: Vulkan Core 1.0 only has one such function, FreeType has about three in total at the edges of the API, a lot of small libraries have none.

Chipmunk2D (and Box2D v2 based on the headers) chooses to pass a few small structs this way, making quite a number of functions incompatible, including half the iterators. This iteration of Box2D seems to apply this strategy to pretty much every single function it exposes.

This means that if I wish to use this library in Haskell, I either have to make an intermediate FFI library that is effectively a box of shims or settle for custom C functions in every project.

Could the API be altered as to exclude passing structs by value or is this too much of a hassle to bother?

erincatto commented 1 month ago

Passing small objects by value can leave them in the register. This can improve performance. I have not measured this lately, but conventional wisdom is that a struct of 16 bytes or less (SIMD register) should be passed by value.

This is one reason I pass small objects by value. This mainly comes up for the ids (handles). The whole API is designed around passing these small opaque objects by value, as if they are a built-in type.

OpenGL has these sorts of ids with no type checking. Things like this:

glBindBuffer(GL_ARRAY_BUFFER, someInteger);

I wonder how often people end up passing the wrong value to these kinds of functions due to a lack of type safety.

I have considered that, where for example:

typedef uint64_t b2BodyId;

This would work just fine and probably solve your problem for ids at least. But then I lose a lot of type safety.

Still remaining are b2Vec2, b2Rot, and b2Transform. It is very natural to pass these small objects by value. Forcing these to be passed by address means you couldn't write code like:

bool isOverlapped = b2Shape_TestPoint(myShapeId, {x, y});
erincatto commented 1 month ago

It looks like there are Haskell bindings for raylib which is a C library that also uses pass by value, even more than Box2D. https://github.com/Anut-py/h-raylib

BurningWitness commented 1 month ago

Yes, with enough C wrappers you can get anything working, the questions are regarding performance and convenience. (and also most Haskell "bindings" libraries are very far from the 1:1 C API ideal, but that's a whole separate topic)

The questions I then have are:

erincatto commented 1 month ago

I think you can make the wrappers cheap. You would just be copying small structs. Can you pass in pointers from the Haskell side to avoid small allocations?

BurningWitness commented 1 month ago

Yes, pointers are supported, and they're the default way of wrapping unmarshallable functions. I would however assume that anything opaque that fits into a uint64_t should be packed into that instead.

erincatto commented 1 month ago

I think that is a good idea. Let me know if a macro or helper functions on the Box2D side would help.