electronstudio / raylib-python-cffi

Python CFFI bindings for Raylib
http://electronstudio.github.io/raylib-python-cffi
Eclipse Public License 2.0
142 stars 29 forks source link

Solution to "Deal with conversions to pointers in PyRay automatically" #8

Closed Pebaz closed 3 years ago

Pebaz commented 4 years ago

I think I may have a solution to the problem of how to selectively convert pointers in PyRay. In the very least it could be used as a stepping stone forward.

In each PyRay function that needs to dereference a pointer, you could call this function on the object. What it does is deref the pointer if it is a pointer, and return the object if it is not.

def unwrap(ptr):
    if isinstance(ptr, ffi.CData):
        ctype = ffi.typeof(ptr)
        if ctype.kind == 'pointer':
            return ctype.item
    return ptr

# Shorter (less clear) version:
def unwrap(ptr):
    if isinstance(ptr, ffi.CData) and ffi.typeof(ptr).kind == 'pointer':
        return ptr[0]
    return ptr

Example usage could entail:

cam = ffi.new("struct Camera3D *", [...])

def render_using_camera(camera):
    camera = unwrap(camera)

# With `unwrap`
render_using_camera(cam)

# Before `unwrap`
render_using_camera(cam[0])

I hope this helps!

Pebaz commented 4 years ago

I forgot to mention that one of the main benefits of this approach is that it won't matter if the function is called with an already-dereffed pointer or a pointer:

def foo(cam):
    cam = unwrap(cam)

# Call with both pointer and dereferenced pointer (both work)
foo(cam)
foo(cam[0])
electronstudio commented 4 years ago

The issue is that some Raylib functions expect pointers and some don't. So if we just dererence every pointer I think that would break functions that expect them. But maybe I'm just not understanding your examples - if I saw how test_pyray.py would look with your changes it might be clearer.

I think a solution would be to parse raylib.h and note which function arguments are pointer types and which are not.

Pebaz commented 4 years ago

I think I have found a great solution to the problem of knowing when and where to dereference pointers when using Raylib.

It turns out that you can get the type signatures of all arguments of a C function and then selectively dereference them only if the function requires it.

Solution:

def callc(func, *args):
    unwraped = []

    for (expect, actual) in zip(ffi.typeof(func).args, args):
        if not isinstance(actual, ffi.CData):
            unwraped.append(actual); continue

        actual_type = ffi.typeof(actual)

        if actual_type == expect:
            unwraped.append(actual)

        elif actual_type.kind == 'pointer' and actual_type.item == expect:
            unwraped.append(actual[0])

        else:
            raise TypeError(
                f'Arg type "{ffi.typeof(arg)}" does not match call signature: '
                f'{func_type}'
            )

    return func(*unwraped)

Now, as far as I can tell in test_pyray.py, you are creating a special wrapper function for each and every Raylib function that you want to support. So if you decide to use the above code, it will not have any impact on users of Pyray.

However, for the library implementor (you), having a way to not care about pointers when working with Raylib functions would be very convenient.

Below is an example usage of the above solution:

from raylib.dynamic import raylib as rl, ffi

def pyray_begin_mode_3d(camera):
    """
    The function expects a 'struct Camera3D', but can be passed as
    a 'struct Camera3D *' seamlessly!
    """
    callc(rl.BeginMode3D, camera)

cam = ffi.new('struct Camera *', [...])
pyray_begin_mode_3d(cam)
pyray_begin_mode_3d(cam[0])

I have not been able to come up with a way to do this automatically for every function in Raylib, but if there was a way to add a decorator to every Raylib function, this would take care of all pointer troubles.

electronstudio commented 3 years ago

Thanks, I have taken your code and done this: https://github.com/electronstudio/raylib-python-cffi/commit/26e9f9cbf2f0aa09f90710bd083e26705d5f8bc8

Not sure if this is sufficient.

Pebaz commented 3 years ago

Nicely done!