Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C API #1640

Open
slime73 opened this issue Oct 17, 2020 · 13 comments
Open

C API #1640

slime73 opened this issue Oct 17, 2020 · 13 comments
Labels
feature New feature or request help wanted Extra attention is needed
Milestone

Comments

@slime73
Copy link
Member

slime73 commented Oct 17, 2020

A plain C external API for LÖVE would be useful. It would allow any number of other languages to be used to write LÖVE code, and it would make it easier to improve performance of LÖVE code in Lua via LuaJIT's FFI.

That being said, it would also be a lot to maintain. Ideally all of the existing Lua wrapper code would call the new C APIs, to keep consistency.

There are a number of problems to solve and questions to answer when implementing a C API. A few I can think of off the top of my head:

  • Errors are currently propagated to the Lua wrapper code via C++ exceptions. C APIs would need to return an error status (perhaps also with a thread-local GetLastErrorMessage API) or some other mechanism, instead. Ideally the only exceptions in any of LÖVE's code would be inside constructors and they would be caught and converted as early as possible, but that's more for future convenience rather than necessary for a C wrapper.

  • Some of the argument and return value types used in the C++ API are not directly C compatible, for example std::vector and std::string. Those should be wrapped cleanly, somehow.

  • LÖVE's enums are usually declared inside class code, or inside namespaces if not classes. They'll either need to be duplicated for the C wrapper, or moved to separate C files included by both the C++ code and C wrapper.

  • How will sub/superclass method flexibility interact with type safety? For example how will the equivalent of Object:release be defined (would it accept a base type, or would a separate Release function be declared for each type? If the former, is there anything preventing the wrong object type from being passed to other functions?)

  • C has no namespaces, so there should be a well-defined naming convention. Should the equivalent of Texture:getWidth contain the Graphics namespace name (e.g. loveGraphicsTextureGetWidth(Texture *tex);), or not (e.g. loveTextureGetWidth(Texture *tex);)?

  • Many LÖVE APIs have several function variants. C has no function overloading. Different variants will generally need uniquely named functions, but I'd also like to cut back on 'convenience' variants in favour of keeping the API smaller, for the C API. It might make sense to create extra convenience functions in some cases for common variant functionality.

  • All LÖVE objects use reference counting. This will need to be exposed directly in the C API, while still keeping it automatic in Lua bindings.

@slime73 slime73 added feature New feature or request help wanted Extra attention is needed labels Oct 17, 2020
@radgeRayden
Copy link

This post has a few useful tips on the section "Binding-Friendly C-APIs".

@endlesstravel
Copy link

I build the C# version of LÖVE, this is fully file about C binding to C#
it covering lots of APIs and work on windows/debain/macos
just two file:
https://github.com/endlesstravel/Love2dCS/blob/master/c_api_src/wrap_love_dll.cpp
https://github.com/endlesstravel/Love2dCS/blob/master/c_api_src/wrap_love_dll.h

In dealing with these problems, I used some crude methods for reference only:

  1. Errors: all the c function return int(bool) value to indicate is there an error:
    function that can throw error will wrap an function to :
    char errStrBuffer[MaxBuffer]; 
    bool4 exported_function_xxx(.....) {
        try {
            // xxxxxxxxx
            sprint(errStrBuffer, "xxxxx") // set error message
            return 1; // no error
        } catch (e) {
            return 0; // have error
        }
    }

To simplify the structure, there is a special function to do this
wrap_catchexcept at https://github.com/endlesstravel/Love2dCS/blob/master/c_api_src/wrap_love_dll.h#L195

use wrap_love_dll_last_error to get error message later.

#define int bool4 // return type
char errStrBuffer[MaxBuffer]; // https://github.com/endlesstravel/Love2dCS/blob/master/c_api_src/wrap_love_dll.cpp#L118

   bool4 wrap_love_dll_filesystem_newFileData_content(const void *contents, int contents_length, const char *filename, FileData** out_filedata)
    {
        return wrap_catchexcept([&]() {
            FileData *t = fsInstance->newFileData(contents, contents_length, filename);
            *out_filedata = t;
        });
    }
  1. For array arguments:

  2. For enums : just use as int values

  3. For sub/superclass : It's not accuracy available in C, This is explained in high-level languages like C#

     for `Rasterizer` build to `Font`: 
     first `wrap_love_dll_font_newRasterizer` `wrap_love_dll_font_newTrueTypeRasterizer` `wrap_love_dll_font_newImageRasterizer` to get `Rasterizer` object from different source:
    
     then: use `wrap_love_dll_graphics_newFont(Rasterizer *rasterizer, love::graphics::Font** out_font)` to get font 
    
  4. namespaces: i just simple use wrap_love_dll_{Module/Object type}_{name} as name

  5. C has no function overloading : You are right. Can achieve the effect by providing as few overloading as possible.
    for graphics.newImage it can be a lot of overloading -- but it has chain:
    string(file name) --> File --> FileData --> ImageData --> Image
    for other "new object function" have same chain, for example graphics.newFont:
    string(file name) --> File --> FileData --> Rasterizer --> Font
    Common convert links can be merged:
    then C api have about all 60 function to new(create) object:

     https://github.com/endlesstravel/Love2dCS/blob/master/c_api_src/wrap_love_dll.h#L276
    

image

  1. reference counting(Resource Manage):
    I followed the rules:

    • for C part, it response to retain object for all new action or get action
      wrap_love_dll_graphics_newCanvas
      wrap_love_dll_graphics_getCanvas
    • for C# part, it response to release object in Dispose stage
  2. Other: There are many other tasks, such as comparing whether the acquired object and the newly created object are the same object
    graphics.newCanvas & graphics.getCanvas
    wrap_love_dll_graphics_newCanvas & wrap_love_dll_graphics_getCanvas
    ....
    There are too many details, so I won’t be long-winded 😋

Excuse me for any possible mistake. 😄

Build log:
https://github.com/endlesstravel/Love2dCS/blob/master/develop.md

@thegrb93
Copy link
Contributor

thegrb93 commented Oct 21, 2020

Can anyone enlighten me to what a c api specifically would allow? Isn't the c++ already established a sufficient api? Or do scripting languages have trouble with c++ apis?

@slime73
Copy link
Member Author

slime73 commented Oct 21, 2020

C++'s ABI makes it practically impossible for any other language to use functions and methods directly from a C++ library. On the other hand, C's ABI is simple and consistent and almost every language supports some form of calling C code from the language (for example LuaJIT's FFI).

C wrappers of love's functions are necessary for any use of love outside of its existing hand-written Lua wrapper code.

@thegrb93
Copy link
Contributor

thegrb93 commented Oct 21, 2020

I see, so we just need functions that can wrap the classes used in love since runtime ABI interfacers like FFI doesn't support classes.

@radgeRayden
Copy link

radgeRayden commented Feb 8, 2021

How will sub/superclass method flexibility interact with type safety? For example how will the equivalent of Object:release be defined (would it accept a base type, or would a separate Release function be declared for each type? If the former, is there anything preventing the wrong object type from being passed to other functions?)

Is there any intention of making love objects available as anything but opaque handles / void*? That seems like the most straightforward solution, in which case all typechecking would be done internally anyway. A function such as love_Object_release(void* object) could then take care of any kind of love object, and eg. draw or a method like love_Texture_getFormat would check some identity flag in the object and emit an error if necessary (not unlike how it works for the lua API internally).

@slime73
Copy link
Member Author

slime73 commented Feb 8, 2021

Yeah I suppose you're right. I've been thinking in terms of having those checks be at the language binding level because that's how it's done right now and different languages have different type safety guarantees, but that's probably the best approach even if it'll add error checks to pretty much every API.

@Ruin0x11
Copy link

Ruin0x11 commented Apr 27, 2021

I made an attempt at porting love.filesystem to be exposed entirely through C. It seems to work okay at a superficial level, but my C(++) is rusty.

https://github.com/Ruin0x11/love/tree/c-api
https://github.com/Ruin0x11/love/blob/c-api/src/modules/filesystem/c_Filesystem.h
https://github.com/Ruin0x11/love/blob/c-api/src/modules/filesystem/c_Filesystem.cpp
https://github.com/Ruin0x11/love/blob/c-api/src/test/love_c.c

My methodology was to take the code in the wrap_*.cpp files and rewrite it to have no dependencies on Lua. I wrapped the functions that can throw exceptions with a boolean return value/pointer to error string (same as LLVM's C bindings). The API makes no assumptions about object ownership, but the retain/release functions are also exposed through C.

I think having LÖVE available for languages like C#, Ruby or Rust would be very interesting. I was always frustrated by "game engine" frameworks in other languages that are really just low-level bindings to SDL/OpenGL/etc., and ones like MonoGame with unwieldy asset pipelines that overcomplicate things.

@spindlebink
Copy link

A lot of C wrapper APIs don't actually do a ton of type checking and pass things around as opaque pointers. I feel like it's safe to assume that if you're savvy enough to be using bindings for LÖVE from a separate language it's acceptable to get a segfault/panic if you try to canvas-clear an audio source. A lot of C APIs end up getting wrapped over for a given language rather than used directly (e.g. especially in OOP languages like Crystal or C# people tend to write elaborate class wrappers over the imperative C stuff), so type checking, if it's provided, can probably be done in a per-language way rather than with a lot of guarding code LÖVE-side.

@johnpayne-dev
Copy link

Is this being worked on currently? I'm interested in this feature so I think I might take a stab at it

@slime73
Copy link
Member Author

slime73 commented Sep 20, 2022

I don't think anyone's actively working on it right now - @Ruin0x11's stuff (above) might be one place to start from, although I'd base it on the 12.0-development branch instead of main.

@expikr
Copy link

expikr commented Apr 21, 2024

What is currently exposed in love.dll? A first step could be to document an unstable "as-is" ABI for those looking to "hack" functionality anyways willing to work around quirks.

@slime73
Copy link
Member Author

slime73 commented Apr 22, 2024

The main C API that's exposed is luaopen_love for use with Lua's require function (which works if people use a standalone Lua executable to load love, rather than love.exe).

As for C++, love's C++ headers change almost every week and aren't exactly authored with external use in mind (they even get stripped from the library's exported symbols I believe), it doesn't seem very beneficial for C API work to document a particular iteration of them - and anyone who wants to try using them with a matching C++ compiler will likely need to look at / be familiar with the source code regardless, because of those earlier points.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

8 participants