fabmax / physx-jni

Java JNI bindings for Nvidia PhysX
MIT License
85 stars 8 forks source link

No "garbage-free" way to interact with the library #68

Open dustContributor opened 2 months ago

dustContributor commented 2 months ago

Hi! I noticed something while using these bindings.

Having LWJGL as a reference, it provides usually a couple of overloads for every function. In particular it provides both wrapped and unwrapped versions of a native binding. It looks like this:

// I believe LWJGL prefixes all of these "direct" calls with n
public static native long ndoSomethingOnNative(long address);

public static ByteBuffer doSomethingOnNative(ByteBuffer data) {
  var result = ndoSomethingOnNative(addressOf(data)); // unwrap ByteBuffer argument
  return wrap(result); // wrap result in ByteBuffer
}

This way, if you do not want to go through object manipulation, because you're handling the long addresses directly, you have the chance to do so.

Now lets look at PxActor user data functions:

    public NativeObject getUserData() {
        checkNotNull();
        return NativeObject.wrapPointer(_getUserData(address));
    }
    private static native long _getUserData(long address);
    public void setUserData(NativeObject value) {
        checkNotNull();
        _setUserData(address, value.getAddress());
    }
    private static native void _setUserData(long address, long value);

While I can get the object's address field just fine with getAddress(), I am unable to directly call either _getUserData or _setUserData due their visibility.

Moreover, I am forced to create an object on every single call of these methods. getUserData may very well be a high frequency method to call.

Now, this limitation is propagated in the design of the whole library, you can find many cases such as this. Where you could, if it were visible, have a single PxSomething instance created, and just wrap/unwrap direct pointers mutating that lone instance's address field, instead of having to recreate those PxSomething objects on every single access.

In this particular case I'd use the native _setUserData function for something more pragmatic. I do not want to actually set an object reference there, I just want to set an int32 index. So I'd use it like:

 PxActor o = // etc.
 int entityId = // some entity id
 PxActor._setUserData(o.getAddress(), entityId);
 // and to use it, no objects created in either case
 int entityId = (int)PxActor._getUserData(o.getAddress());

I do not believe this could apply on every single instance where a native call is issued, but I can imagine plenty of cases where object creation could be avoided if you could just access the native functions "directly" using long back and forth, and just manually mutating the address field of a single PxSomething object to manipulate the memory from Java.

Also I do know that physx-jni is very careful on how it uses objects and they never cross to the native side, so in theory escape analysis should be able to work its magic. However, it popped out to me since in particular the userData ID seemed a "simple" use case I couldn't emulate with just primitive usage with the API as it is.

My experience with PhysX is really barebones (and my project is nowhere near a serious one, just hobby things) so I cant comment on better ways of doing exactly what I wanted to do, but I hope it illustrates the kind of use case I'm thinking of.