Skip to content

Commit 2b66cb6

Browse files
committed
document ConstHandle and exports
1 parent 2d4a3df commit 2b66cb6

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

README.md

+21-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
This library creates bindings for accessing Javascript from within a WASM runtime. For example:
44

55
```zig
6-
// Example for readme
76
const zjb = @import("zjb");
87
98
export fn main() void {
@@ -32,15 +31,15 @@ This package is clearly inspired by Go's solution to this problem: https://pkg.g
3231

3332
## Usage
3433

35-
As of March 2024, zjb requires Zig's master version, not 0.11.
34+
As of May 2024, zjb requires Zig 0.12.0 or greater.
3635

3736
Call into Javascript using `zjb`, generate the Javascript side code, and then build an html page to combine them.
3837

3938
An end to end example is in the example folder. It includes:
4039

4140
- `main.zig` has usage examples for the `zjb` Zig import.
4241
- `build.zig`'s example for how to set up your build file.
43-
- `example/static` includes an HTML and a Javascript to run the example.
42+
- `example/static` includes HTML and Javascript files to run the example.
4443

4544
To view the example in action, run `zig build example`. Then host a webserver from `zig-out/bin`.
4645

@@ -49,12 +48,20 @@ Zjb functions which return a value from Javascript require specifying which type
4948
- `i32`, `i64`, `f32`, `f64`. These are the only numerical types that are supported by the WASM runtime export function signature, so you must cast to one of these before passing.
5049
- `comptime_int`, `comptime_float`. These are valid as arguments, and are passed as f64 to Javascript, which is Javascript's main number type.
5150
- `zjb.Handle`. The Zig side type for referring to Javascript values. Most of the time this will be a Javascript object of some kind, but in some rare cases it might be something else, such as null, a Number, NaN, or undefined. Used as an argument, it is automatically converted into the value that is held in zjb's Javascript `_handles` map. When used as a return value, it is automatically added to zjb's Javascript `_handles` map. It is the caller's responsibility to call `release` to remove it from the `_handles` map when you're done using it.
51+
- `zjb.ConstHandle` as arguments but not return types. These values are returned by `zjb.constString`, `zjb.global`, and `zjb.fnHandle`. `zjb.ConstHandle` works similarly to `zjb.Handle`, with a few notable exceptions: 1. Values are memoized upon first use on the Zig side, so they can be used any number of times without churning garbage. 2. There is no `release` function. These values are intended to be around for the lifetime of your program, with reduced friction of using them. As the functions which produce ConstHandle values all take only comptime arguments, these cannot balloon uncontrolably at runtime. Some values are always defined as handles, `zjb.ConstHandle.null` is Javascript's `null`, `zjb.ConstHandle.global` is the global scope, and `zjb.ConstHandle.empty_string` is a Javascript empty string.
5252
- `void` is a valid type for method calls which have no return value.
5353

54+
Zjb supports multiple ways to expose Zig functions to Javascript:
55+
- `zjb.exportFn` exposes the function with the passed name to Javascript. This supports `zjb.Handle`, so if you pass an object from a Javascript function, a handle will automaticlaly be created and passed into Zig. It is the responsibility of the Zig function being called to call `release` on any handles in its arguments at the appropriate time to avoid memory leaks.
56+
- `zjb.fnHandle` uses `zjb.exportFn` and additionally returns a `zjb.ConstHandle` to that function. This can be used as a callback argument in Javascript functions.
57+
- Zig's `export` keyword on functions works as it always does in WASM, but doesn't support `zjb.Handle` correctly.
58+
5459
A few extra notes:
5560

5661
`zjb.string([]const u8)` decodes the slice of memory as a utf-8 string, returning a Handle. The string will NOT update to reflect changes in the slice in Zig.
5762

63+
`zjb.global` will be set to the value of that global variable the first time it is called. As it is intended to be used for Javascript objects or classes defined in the global scope, that usage will be safe. For example, `console`, `document` or `Map`. If you use it to retrieve a value or object you've defined in Javascript, ensure it's defined before your program runs and doesn't change.
64+
5865
The \_ArrayView functions (`i8ArrayView`, `u8ArrayView`, etc) create the respective JavaScript typed array backed by the same memory as the Zig WASM instance.
5966

6067
`dataView` is similar in functionality to the ArrayView functions, but returns a DataView object. Accepts any pointer or slice.
@@ -93,11 +100,6 @@ const Zjb = class {
93100
}
94101
constructor() {
95102
this._decoder = new TextDecoder();
96-
this._handles = new Map();
97-
this._handles.set(0, null);
98-
this._handles.set(1, window);
99-
this._handles.set(2, "");
100-
this._next_handle = 3;
101103
this.imports = {
102104
"call_o_v_log": (arg0, id) => {
103105
this._handles.get(id).log(this._handles.get(arg0));
@@ -109,16 +111,24 @@ const Zjb = class {
109111
return this.new_handle(this._decoder.decode(new Uint8Array(this.instance.exports.memory.buffer, ptr, len)));
110112
},
111113
};
114+
this.exports = {
115+
};
116+
this._export_reverse_handles = {};
117+
this._handles = new Map();
118+
this._handles.set(0, null);
119+
this._handles.set(1, window);
120+
this._handles.set(2, "");
121+
this._handles.set(3, this.exports);
122+
this._next_handle = 4;
112123
}
113124
};
114125

115126
```
116127

117-
## Todo/Wishlist
128+
## Feature Wishlist
118129

119-
Things that this doesn't have yet, but would be nice:
130+
Zjb fully works, but has not had enough usage to confidently tag a 1.0. Here are some things which would be nice to add:
120131

121-
- Exposing a function that has Handles in arguments, automatically handling everything. Ability to pass in one of those functions as a callback into calls from Zig side. Eg, being able to do setTimeout, or handle async networking tasks entirely from Zig.
122132
- Tests (need to run from a js environment somehow).
123133
- Better error handling. (Both handling javascript exceptions as returned errors, but also properly printing panic's error messages).
124134
- Other random javascript stuff like instanceof, or converting a handle to a number (typically not needed, but might be if you don't know what type something is until after you've got the handle).

src/zjb.zig

+39-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ pub fn fnHandle(comptime name: []const u8, comptime f: anytype) ConstHandle {
5151
pub fn exportFn(comptime name: []const u8, comptime f: anytype) void {
5252
comptime var export_name: []const u8 = "zjb_fn_";
5353
const type_info = @typeInfo(@TypeOf(f)).Fn;
54+
validateToJavascriptReturnType(type_info.return_type orelse void);
5455
inline for (type_info.params) |param| {
56+
validateFromJavascriptArgumentType(param.type orelse void);
5557
export_name = export_name ++ comptime shortTypeName(param.type orelse @compileError("zjb exported functions need specified types."));
5658
}
5759
export_name = export_name ++ "_" ++ comptime shortTypeName(type_info.return_type orelse null) ++ "_" ++ name;
@@ -162,28 +164,33 @@ pub const Handle = enum(i32) {
162164
}
163165

164166
pub fn get(handle: Handle, comptime field: []const u8, comptime RetType: type) RetType {
165-
// validateReturn(RetType);
167+
validateFromJavascriptReturnType(RetType);
166168
const name = comptime "get_" ++ shortTypeName(RetType) ++ "_" ++ field;
167169
const F = fn (Handle) callconv(.C) mapType(RetType);
168170
const f = @extern(*const F, .{ .library_name = "zjb", .name = name });
169171
return @call(.auto, f, .{handle});
170172
}
171173

172174
pub fn set(handle: Handle, comptime field: []const u8, value: anytype) void {
175+
validateToJavascriptArgumentType(@TypeOf(value));
173176
const name = comptime "set_" ++ shortTypeName(@TypeOf(value)) ++ "_" ++ field;
174177
const F = fn (mapType(@TypeOf(value)), Handle) callconv(.C) void;
175178
const f = @extern(*const F, .{ .library_name = "zjb", .name = name });
176179
@call(.auto, f, .{ value, handle });
177180
}
178181

179182
pub fn indexGet(handle: Handle, arg: anytype, comptime RetType: type) RetType {
183+
validateToJavascriptArgumentType(@TypeOf(arg));
184+
validateFromJavascriptReturnType(RetType);
180185
const name = comptime "indexGet_" ++ shortTypeName(@TypeOf(arg)) ++ "_" ++ shortTypeName(RetType);
181186
const F = fn (mapType(@TypeOf(arg)), Handle) callconv(.C) mapType(RetType);
182187
const f = @extern(*const F, .{ .library_name = "zjb", .name = name });
183188
return @call(.auto, f, .{ arg, handle });
184189
}
185190

186191
pub fn indexSet(handle: Handle, arg: anytype, value: anytype) void {
192+
validateToJavascriptArgumentType(@TypeOf(arg));
193+
validateToJavascriptArgumentType(@TypeOf(value));
187194
const name = comptime "indexSet_" ++ shortTypeName(@TypeOf(arg)) ++ shortTypeName(@TypeOf(value));
188195
const F = fn (mapType(@TypeOf(arg)), mapType(@TypeOf(value)), Handle) callconv(.C) void;
189196
const f = @extern(*const F, .{ .library_name = "zjb", .name = name });
@@ -199,6 +206,7 @@ pub const Handle = enum(i32) {
199206
}
200207

201208
fn invoke(handle: Handle, args: anytype, comptime RetType: type, comptime prefix: []const u8, comptime suffix: []const u8) RetType {
209+
validateFromJavascriptReturnType(RetType);
202210
const fields = comptime @typeInfo(@TypeOf(args)).Struct.fields;
203211
comptime var call_params: [fields.len + 1]std.builtin.Type.Fn.Param = undefined;
204212
comptime var extern_name: []const u8 = prefix;
@@ -210,6 +218,7 @@ pub const Handle = enum(i32) {
210218
};
211219

212220
inline for (fields, 0..) |field, i| {
221+
validateToJavascriptArgumentType(field.type);
213222
call_params[i] = .{
214223
.is_generic = false,
215224
.is_noalias = false,
@@ -232,6 +241,34 @@ pub const Handle = enum(i32) {
232241
}
233242
};
234243

244+
fn validateToJavascriptArgumentType(comptime T: type) void {
245+
switch (T) {
246+
Handle, ConstHandle, bool, i32, i64, f32, f64, comptime_int, comptime_float => {},
247+
else => @compileError("unexpected type " ++ @typeName(T) ++ ". Supported types here: zjb.Handle, zjb.ConstHandle, bool, i32, i64, f32, f64, comptime_int, comptime_float."),
248+
}
249+
}
250+
251+
fn validateToJavascriptReturnType(comptime T: type) void {
252+
switch (T) {
253+
Handle, ConstHandle, bool, i32, i64, f32, f64, void => {},
254+
else => @compileError("unexpected type " ++ @typeName(T) ++ ". Supported types here: zjb.Handle, zjb.ConstHandle, bool, i32, i64, f32, f64, void."),
255+
}
256+
}
257+
258+
fn validateFromJavascriptReturnType(comptime T: type) void {
259+
switch (T) {
260+
Handle, bool, i32, i64, f32, f64, void => {},
261+
else => @compileError("unexpected type " ++ @typeName(T) ++ ". Supported types here: zjb.Handle, bool, i32, i64, f32, f64, void."),
262+
}
263+
}
264+
265+
fn validateFromJavascriptArgumentType(comptime T: type) void {
266+
switch (T) {
267+
Handle, bool, i32, i64, f32, f64 => {},
268+
else => @compileError("unexpected type " ++ @typeName(T) ++ ". Supported types here: zjb.Handle, bool, i32, i64, f32, f64."),
269+
}
270+
}
271+
235272
fn shortTypeName(comptime T: type) []const u8 {
236273
return switch (T) {
237274
Handle, ConstHandle => "o",
@@ -242,9 +279,7 @@ fn shortTypeName(comptime T: type) []const u8 {
242279
// handle this just fine, and produces fewer unique methods
243280
// in javascript so there's no reason not to do it.
244281
i32, i64, f32, f64, comptime_int, comptime_float => "n",
245-
else => {
246-
@compileError("unexpected type " ++ @typeName(T) ++ ". Supported types: zjb.Handle, bool, i32, i64, f32, f64, comptime_int, copmtime_float, void (as return type).");
247-
},
282+
else => unreachable,
248283
};
249284
}
250285

0 commit comments

Comments
 (0)