-
Notifications
You must be signed in to change notification settings - Fork 367
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
[Help Wanted] Zig & Odin #3
Comments
For Odin:
instead of
an example from the source code how such a function is defined:
now, I see that these macros don't actually have return values, but you could certainly leverage this behavior anyway, to make fairly nice to use Odin bindings (which would be great, someone just posted this library in the Odin Discord, and it looks really useful!) |
Just for posterity the folks over at the Odin discord have been very helpful, and I'm going to have a crack at writing the Odin bindings today. |
I'm not an expert, but here's a Zig idea I toyed with: const std = @import("std");
const LayoutConfig = struct {};
// Would be the real imported Clay
const clay_ns = @This();
pub fn clay(layout: anytype) void {
const fields = std.meta.fields(@TypeOf(layout));
inline for (fields) |el| {
comptime var name: [el.name.len]u8 = undefined;
@memcpy(&name, el.name);
// Crudely capitalize the field name.
name[0] = comptime std.ascii.toUpper(name[0]);
const openFn = @field(clay_ns, "Clay__Open" ++ name ++ "Element");
const closeFn = @field(clay_ns, "Clay__Close" ++ name ++ "Element");
// The Clay element functions have different arities,
// so more code would be needed here.
const args = @field(layout, el.name);
const id_arg = args[0];
const config_arg = args[1];
openFn(id_arg, config_arg);
defer closeFn();
if (3 <= args.len) {
clay(args[2]);
}
}
}
const id = 123;
pub fn main() void {
clay(.{
.container = .{
id,
LayoutConfig{},
.{
.text = .{
id,
"text goes here",
},
.rectangle = .{
id,
LayoutConfig{},
},
},
},
});
}
fn Clay__OpenContainerElement(id_: usize, layout_config: LayoutConfig) void {
std.log.debug("open container element {d} {any}", .{ id_, layout_config });
}
fn Clay__CloseContainerElement() void {
std.log.debug("close container element", .{});
}
fn Clay__OpenTextElement(id_: usize, text: []const u8) void {
std.log.debug("open text element {d} {s}", .{ id_, text });
}
fn Clay__CloseTextElement() void {
std.log.debug("close text element", .{});
}
fn Clay__OpenRectangleElement(id_: usize, layout_config: LayoutConfig) void {
std.log.debug("open rectangle element {d} {any}", .{ id_, layout_config });
}
fn Clay__CloseRectangleElement() void {
std.log.debug("close rectangle element", .{});
} Running this program prints:
So you get the sandwiching, but it would need more work, and I don't know if it's a good direction. |
@illfygli That is super cool, thanks so much for the code samples! I will do some investigation when I have time, would be great to get it working from Zig. |
I've made some great progress with the Odin bindings today: #5 |
I'd love to see Nim bindings too, the macro system will allow for the functional form :) Edit: Missed the part where you explain how the macros work, should be simple to map to other DSL forms! I might take a crack if there are no takers |
@crystalthoughts Please feel free, I've never used nim myself but it looks cool 😁 |
Now that the odin bindings are out the door and I've got a reasonable idea of how long it takes to write them for another language, I'm probably going to delay writing the zig bindings until after I've finished some feature work. Still high on the priority list, though! |
Hey @nicbarker, I just watched your YT video and clay looks great! I have a Zig project that would benefit from a nice UI and I'd love to pitch in if you need any help with the bindings when you come around to it |
Since I imagine @nicbarker is busy with other tasks I have started work on my own clay bindings for zig at https://codeberg.org/Zettexe/clay-zig. Keep in mind that they are currently very unfinished so it may still be a while until they are usable. |
Funnily enough, I have also started on a set of bindings for zig after watching that video. They are at https://github.com/raugl/clay-zig. You can use it today, though it still needs examples and more decoupling from raylib. It is only a few commits behind master and it needs zig 0.14 for the more compact api, but can be easily made to work on 0.13 too. I have ditched the more imperative api that is used internally by the macros, and which is replicated by most of the other bindings, for a more declarative one with a single |
Here is my idea for the zig binding. I went for something close to @illfygli, I wanted to keep the automatic close of the element. There is a bit of boiler plate comptime code to capture function and generic arguments. Example code pub fn main() !void {
var value: i32 = 0;
const sub_scoped_object = Closure.closure(.{@as(*i32, &value)}, struct {
pub fn inline_fn(single_arg: *i32) void {
std.log.info("Inside captured generic code", .{});
single_arg.* = 10;
clay.clay(clay.child(&.{}, &.{})); // More ui code in a
}
}.inline_fn);
clay.clay(
clay.child(&.{
clay.RectangleElement{},
CClay.Clay_RectangleElementConfig{},
}, &.{
clay.child(
&.{@as(u32, 1)},
&.{sub_scoped_object},
),
}),
);
std.log.info("Edited value: {}", .{value});
} Here is the output from the code above:
See this Gits for the full impl of Closure |
Its taken a little bit but my bindings are now functional, the only completed renderer is the raylib one but ill add more soon and using it is completely optional. I found a way to kind of abuse the way Zig handles the execution order of blocks to create a syntax like this: clay.ui()(.{
.id = clay.id("SideBar"),
.layout = .{
.direction = .TopToBottom,
.alignment = .{ .x = .Center, .y = .Top },
.sizing = .{ .width = clay.Sizing.Axis.fixed(300), .height = clay.Sizing.Axis.grow },
.padding = .{ .x = 16, .y = 16 },
.child_gap = 16,
},
})({
// Body here
}); Since I only implemented functions as I was trying to make the raylib example work ive probably missed something so feel free to give me feedback. |
I would love for clay to be easily usable from both Zig and Odin.
However, there's a major sticking point with both that I don't know how to solve.
The core Element Macros rely on a particular feature of the C preprocessor, specifically "function-like macros" and the ability to pass arbitrary text as an argument to these macros. If you're not familiar with this in C, you can take a look at the definition of any of these macros in clay.h (example) and you'll see that they all follow a general form:
You can see that in order to correctly construct the layout hierarchy, child declarations need to preceded by a
Clay__Open...
and then followed by aClay__Close...
.In clay's use case, these macros allows you to automatically sandwhich child layout elements in the required open / close, by "passing a block as a macro argument" - creating (imo) a very nice developer experience:
As a result it's not actually possible to "forget" to close these containers and end up with a mismatch or with elements incorrectly parented - this macro syntax functions almost like a form of RAII.
Neither Zig nor Odin support this type of "function-like macro" where arbitrary text can be pasted in.
In Odin's case, it might be possible through some combination of
defer
and non capturing lambdas to replicate this type of behaviour, but what I'm really looking for is something fool proof - where you don't have to spend time debugging a missing call toClay__Close...
, and I don't have to build debug tools to help you with that 😛In Zig's case, AFAIK there is even less official support for closures, and just glossing over the docs I can't really think of a way to implement it that wouldn't make layout definition a mess.
Any help or out of the box ideas would be greatly appreciated!
The text was updated successfully, but these errors were encountered: