floooh / sokol-zig

Zig bindings for the sokol headers (https://github.com/floooh/sokol)
zlib License
341 stars 46 forks source link

sokol_nuklear zig bindings #73

Open chakany opened 1 month ago

chakany commented 1 month ago

Hello! I was looking to add sokol_nuklear bindings to this repository for usage inside of my project, but I noticed that the bindings were all machine-generated. I couldn't find a tool anywhere that would let me generate these bindings, but I did see your blog post.

Could you shed some light on how I can make these bindings please? Thank you

floooh commented 1 month ago

The important stuff is here:

https://github.com/floooh/sokol/tree/master/bindgen

(I just notice that the readme is incomplete, you'll also need to clone the new D bindings into the bindgen directory: git clone https://github.com/floooh/sokol-d

I think it's best to do roughly the same as for sokol_imgui.h (just search for sokol_imgui and simgui). sokol_imgui.h is a bit special in that there's no example in sokol-zig, because it would require a cimgui+Dear ImGui dependency, instead the example is in a separate repository:

https://github.com/floooh/sokol-zig-imgui-sample

If you get stuck, let me know. Also if you get it working let me also know because then we can add it to the official bindings ;)

floooh commented 1 month ago

Basically:

Back in the bindgen directory, try running python3 gen_all.py and look for errors. If it works, the sokol_nuklear.h header should have been copied to sokol-zig/src/sokol/c/, and a file `sokol-zig/src/sokol/nuklear.zig should have been created.

chakany commented 1 month ago

Hello, I did the following steps, and I got something simpler than I imagined.

// machine generated, do not edit

const builtin = @import("builtin");
const sg = @import("gfx.zig");
const sapp = @import("app.zig");

// helper function to convert a C string to a Zig string slice
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
    return @import("std").mem.span(c_str);
}

So, none of the actual bindings are there. Very strange.

floooh commented 1 month ago

Hmm, I get a different problem which I didn't anticipate:

In file included from sokol-zig/src/sokol/c/sokol_nuklear.c:6:
sokol-zig/src/sokol/c/sokol_nuklear.h:322:2: error: "Please include nuklear.h before sokol_nuklear.h"
#error "Please include nuklear.h before sokol_nuklear.h"
 ^
sokol-zig/src/sokol/c/sokol_nuklear.h:440:24: error: unknown type name 'nk_handle'
SOKOL_NUKLEAR_API_DECL nk_handle snk_nkhandle(snk_image_t img);
                       ^
sokol-zig/src/sokol/c/sokol_nuklear.h:441:60: error: unknown type name 'nk_handle'
SOKOL_NUKLEAR_API_DECL snk_image_t snk_image_from_nkhandle(nk_handle handle);

Problem is that sokol_nuklear.h needs the nuklear.h header even for the declarations, because of the use of nk_handle here:

https://github.com/floooh/sokol/blob/7b20c1936229370277d1c61bde950bce194de584/util/sokol_nuklear.h#L440

...this is actually tricky to solve because requiring the nuklear.h header for bindings generation is a no-go.

I don't have a quick solution to offer, we basically need to get rid somehow of all nuklear.h dependencies (like nk_handle). Maybe with a preprocessor define somewhere...

chakany commented 1 month ago

I don't have a quick solution to offer, we basically need to get rid somehow of all nuklear.h dependencies (like nk_handle). Maybe with a preprocessor define somewhere...

would it be possible to activate the preprocessor ifs from the command line at compile time? and only when building for bindings?

floooh commented 1 month ago

Yep, cleanest solution would be to add a define in the clang call here:

https://github.com/floooh/sokol/blob/7b20c1936229370277d1c61bde950bce194de584/bindgen/gen_ir.py#L102

...maybe this works:

cmd = ['clang', '-Xclang', '-ast-dump=json', '-D', 'SOKOL_BINDGEN', '-c' ]

...and then in the sokol_nuklear.c file we can check for SOKOL_BINDGEN and provide the required Nuklear types directly before sokol_nuklear.h is included, e.g.:

#if defined(SOKOL_BINDGEN)
typedef union {void *ptr; int id;} nk_handle;
#endif

...but hmm, next problem will be that the code generation ignores this typedef because it doesn't start with the right prefix... but let's fix those things step by step...

...the sokol_nuklear.h public API directly using Nuklear types is definitely a code smell...

In sokol_imgui.h this is solved via a 'type-erased' void*:

https://github.com/floooh/sokol/blob/7b20c1936229370277d1c61bde950bce194de584/util/sokol_imgui.h#L533

...but for Nuklear that wouldn't work unfortunately...

There's also this function which needs a solution:

https://github.com/floooh/sokol/blob/7b20c1936229370277d1c61bde950bce194de584/util/sokol_nuklear.h#L435

...if it would just be the snk_nkhandle function we could probably filter that out for now (it's not a critical function unless the user code wants to work with images). But we can't simply kick out the snk_new_frame function.

chakany commented 1 month ago

been working on this for a little while, i have some basic types.

#if defined(IMPL)
#define SOKOL_NUKLEAR_IMPL
#define NK_IMPLEMENTATION
#include "nuklear.h"
#endif
#include "sokol_defines.h"
#include "sokol_app.h"
#include "sokol_gfx.h"
#ifdef SOKOL_BINDGEN
struct nk_context;
typedef union {void *ptr; int id;} nk_handle;
typedef unsigned int nk_flags;
typedef int(*nk_plugin_filter)(const struct nk_context*, nk_handle, int*, int);
#endif
#include "sokol_nuklear.h"

generations go until it reaches a struct nk_context * type. it outputs

Error as_c_arg_type(): struct nk_context *

I believe that the nk_context struct is not being included in the AST dump, and that's why it cannot be found. So, i'm trying to get the nk_ prefix included in the gen_zig.py/gen_ir.py scripts, and I think i'm stuck.

Thanks again.