Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions examples/basic/src/class.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
const std = @import("std");
const napi = @import("napi");

const Test = struct {
name: []u8,
age: i32,
};

const TestWithInit = struct {
name: []u8,
age: i32,

pub fn init(age: i32, name: []u8) TestWithInit {
return TestWithInit{ .name = name, .age = age };
}
};

const TestFactory = struct {
name: []u8,
age: i32,

const Self = @This();

pub fn initWithFactory(age: i32, name: []u8) Self {
return TestFactory{ .name = name, .age = age };
}

pub fn format(self: *Self) []u8 {
return std.fmt.allocPrint(std.heap.page_allocator, "TestFactory {{ name = {s}, age = {d} }}", .{ self.name, self.age }) catch @panic("OOM");
}
};

pub const TestClass = napi.Class(Test);
pub const TestWithInitClass = napi.Class(TestWithInit);
pub const TestWithoutInitClass = napi.ClassWithoutInit(TestWithInit);
pub const TestFactoryClass = napi.Class(TestFactory);
3 changes: 3 additions & 0 deletions examples/basic/src/hello.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub const create_function = function.create_function;
pub const call_thread_safe_function = thread_safe_function.call_thread_safe_function;

pub const TestClass = class.TestClass;
pub const TestWithInitClass = class.TestWithInitClass;
pub const TestWithoutInitClass = class.TestWithoutInitClass;
pub const TestFactoryClass = class.TestFactoryClass;

pub const test_hilog = log.test_hilog;

Expand Down
11 changes: 7 additions & 4 deletions src/napi/wrapper/callback_info.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ const std = @import("std");
const napi = @import("napi-sys").napi_sys;
const value = @import("../value.zig");
const NapiEnv = @import("../env.zig").Env;
const GlobalAllocator = @import("../util/allocator.zig");

pub const CallbackInfo = struct {
raw: napi.napi_callback_info,
env: napi.napi_env,
args: []const value.NapiValue,
args_raw: []napi.napi_value,
args_count: usize,
this: napi.napi_value,

pub fn from_raw(env: napi.napi_env, raw: napi.napi_callback_info) CallbackInfo {
Expand All @@ -16,9 +19,7 @@ pub const CallbackInfo = struct {
@panic("Failed to get callback info");
}

const allocator = std.heap.page_allocator;
const args_raw = allocator.alloc(napi.napi_value, init_argc) catch @panic("OOM");
defer allocator.free(args_raw);
const args_raw = GlobalAllocator.globalAllocator().alloc(napi.napi_value, init_argc) catch @panic("OOM");

var this: napi.napi_value = undefined;

Expand All @@ -27,7 +28,7 @@ pub const CallbackInfo = struct {
@panic("Failed to get callback info");
}

const result = allocator.alloc(value.NapiValue, init_argc) catch @panic("OOM");
const result = GlobalAllocator.globalAllocator().alloc(value.NapiValue, init_argc) catch @panic("OOM");

for (0..init_argc) |i| {
result[i] = value.NapiValue.from_raw(env, args_raw[i]);
Expand All @@ -38,6 +39,8 @@ pub const CallbackInfo = struct {
.env = env,
.args = result,
.this = this,
.args_raw = args_raw,
.args_count = init_argc,
};
}

Expand Down
218 changes: 153 additions & 65 deletions src/napi/wrapper/class.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const CallbackInfo = @import("./callback_info.zig").CallbackInfo;
const napi_env = @import("../env.zig");
const Napi = @import("../util/napi.zig").Napi;
const helper = @import("../util/helper.zig");
const NapiError = @import("./error.zig");
const GlobalAllocator = @import("../util/allocator.zig");

var class_constructors: std.StringHashMap(napi.napi_value) = std.StringHashMap(napi.napi_value).init(GlobalAllocator.globalAllocator());
Expand All @@ -22,37 +23,44 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
const decls = type_info.@"struct".decls;

const class_name = comptime helper.shortTypeName(T);

return struct {
const WrappedType = T;

env: napi.napi_env,
raw: napi.napi_value,

const Self = @This();

// 构造器回调
fn constructor_callback(env: napi.napi_env, callback_info: napi.napi_callback_info) callconv(.c) napi.napi_value {
const infos = CallbackInfo.from_raw(env, callback_info);

const data = GlobalAllocator.globalAllocator().create(T) catch return null;

if (@hasDecl(T, "init")) {
var tuple_args: std.meta.ArgsTuple(T.init) = undefined;
inline for (@typeInfo(std.meta.ArgsTuple(T.init)).@"fn".params, 0..) |arg, i| {
const init_fn = T.init;
const init_fn_type = @TypeOf(init_fn);
const init_fn_info = @typeInfo(init_fn_type);

var tuple_args: std.meta.ArgsTuple(init_fn_type) = undefined;
inline for (init_fn_info.@"fn".params, 0..) |arg, i| {
tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, arg.type.?);
}

data.* = @call(.auto, T.init, tuple_args) catch {
GlobalAllocator.globalAllocator().destroy(data);
return null;
};
if (@typeInfo(init_fn_info.@"fn".return_type.?) == .error_union) {
data.* = @call(.auto, init_fn, tuple_args) catch {
if (NapiError.last_error) |last_err| {
last_err.throwInto(napi_env.Env.from_raw(env));
}
GlobalAllocator.globalAllocator().destroy(data);
return null;
};
} else {
data.* = @call(.auto, init_fn, tuple_args);
}
} else {
// init with zero
data.* = std.mem.zeroes(T);
// if HasInit, init with args with order of fields
if (comptime HasInit) {
inline for (fields, 0..) |field, i| {
@field(data.*, field.name) = Napi.from_napi_value(infos.env, infos.args[i].raw, field.type);
@field(data.*, field.name) = Napi.from_napi_value(env, infos.args[i].raw, field.type);
}
}
}
Expand All @@ -72,6 +80,56 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
return this_obj;
}

fn factory_method_callback(comptime factory_name: []const u8) type {
return struct {
fn call(env: napi.napi_env, callback_info: napi.napi_callback_info) callconv(.c) napi.napi_value {
const infos = CallbackInfo.from_raw(env, callback_info);

const factory_fn = @field(T, factory_name);
const factory_fn_type = @TypeOf(factory_fn);
const factory_fn_info = @typeInfo(factory_fn_type);
const params = factory_fn_info.@"fn".params;

var instance_data: T = undefined;

if (params.len == 0) {
instance_data = factory_fn() catch return null;
} else {
var tuple_args: std.meta.ArgsTuple(factory_fn_type) = undefined;
inline for (params, 0..) |param, i| {
if (i < infos.args.len) {
tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, param.type.?);
}
}
if (@typeInfo(factory_fn_info.@"fn".return_type.?) == .error_union) {
instance_data = @call(.auto, factory_fn, tuple_args) catch return null;
} else {
instance_data = @call(.auto, factory_fn, tuple_args);
}
}

const constructor = class_constructors.get(class_name) orelse return null;
var js_instance: napi.napi_value = undefined;
_ = napi.napi_new_instance(env, constructor, infos.args_count, @ptrCast(infos.args_raw.ptr), &js_instance);

const heap_data = GlobalAllocator.globalAllocator().create(T) catch return null;
heap_data.* = instance_data;

var ref: napi.napi_ref = undefined;
const status = napi.napi_wrap(env, js_instance, heap_data, finalize_callback, null, &ref);
if (status != napi.napi_ok) {
GlobalAllocator.globalAllocator().destroy(heap_data);
return null;
}

var ref_count: u32 = undefined;
_ = napi.napi_reference_unref(env, ref, &ref_count);

return js_instance;
}
};
}

fn finalize_callback(env: napi.napi_env, data: ?*anyopaque, hint: ?*anyopaque) callconv(.c) void {
_ = env;
_ = hint;
Expand All @@ -86,16 +144,17 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
}

fn define_class(env: napi.napi_env) !napi.napi_value {
comptime var property_count: usize = 0;
inline for (fields) |_| {
property_count += 1;
}
comptime var property_count: usize = fields.len;

inline for (decls) |decl| {
if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and
!std.mem.eql(u8, decl.name, "init") and
!std.mem.eql(u8, decl.name, "deinit"))
{
property_count += 1;
const decl_type = @TypeOf(@field(T, decl.name));
if (@typeInfo(decl_type) == .@"fn") {
const fn_name = decl.name;
if (comptime !std.mem.eql(u8, fn_name, "init") and
!std.mem.eql(u8, fn_name, "deinit"))
{
property_count += 1;
}
}
}

Expand Down Expand Up @@ -127,7 +186,6 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
const new_value = Napi.from_napi_value(setter_env, args[0].raw, field.type);
@field(instance.*, field.name) = new_value;
}

return null;
}
};
Expand All @@ -146,45 +204,81 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
}

inline for (decls) |decl| {
if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and
!std.mem.eql(u8, decl.name, "init") and
!std.mem.eql(u8, decl.name, "deinit"))
{
const method = @field(T, decl.name);
const method_info = @typeInfo(@TypeOf(method));
const params = method_info.Fn.params;
const is_instance_method = params.len > 0 and params[0].type.? == *T;

const MethodWrapper = struct {
fn call(method_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value {
const cb_info = CallbackInfo.from_raw(method_env, info);

if (is_instance_method) {
var data: ?*anyopaque = null;
_ = napi.napi_unwrap(method_env, cb_info.This(), &data);
if (data == null) return null;

const instance: *T = @ptrCast(@alignCast(data.?));
const result = method(instance);
return Napi.to_napi_value(method_env, result, decl.name) catch null;
} else {
const result = method();
return Napi.to_napi_value(method_env, result, decl.name) catch null;
const decl_type = @TypeOf(@field(T, decl.name));
if (@typeInfo(decl_type) == .@"fn") {
const fn_name = decl.name;
if (comptime !std.mem.eql(u8, fn_name, "init") and
!std.mem.eql(u8, fn_name, "deinit"))
{
const method = @field(T, fn_name);
const method_info = @typeInfo(@TypeOf(method));
const params = method_info.@"fn".params;

const is_instance_method = params.len > 0 and (params[0].type.? == *T or params[0].type.? == T);

const return_type = method_info.@"fn".return_type.?;
const is_factory_method = blk: {
if ((return_type == T or return_type == *T)) break :blk true;
if (@typeInfo(return_type) == .error_union) {
if (@typeInfo(return_type).error_union.payload == T or @typeInfo(return_type).error_union.payload == *T) break :blk true;
}
break :blk false;
};

if (is_factory_method and !is_instance_method) {
const FactoryWrapper = factory_method_callback(fn_name);
properties[prop_idx] = napi.napi_property_descriptor{
.utf8name = @ptrCast(fn_name.ptr),
.name = null,
.method = FactoryWrapper.call,
.getter = null,
.setter = null,
.value = null,
.attributes = napi.napi_static,
.data = null,
};
} else {
const MethodWrapper = struct {
fn call(method_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value {
const cb_info = CallbackInfo.from_raw(method_env, info);
var data: ?*anyopaque = null;
_ = napi.napi_unwrap(method_env, cb_info.This(), &data);
if (data == null) return null;

var tuple_args: std.meta.ArgsTuple(@TypeOf(method)) = undefined;

// inject instance
if (is_instance_method) {
if (method_info.@"fn".params[0].type.? != *T) {
@compileError("Method " ++ fn_name ++ " must have a self parameter, which is a pointer to the class");
}
const instance: *T = @ptrCast(@alignCast(data.?));
tuple_args[0] = instance;
}

const args_offset = if (is_instance_method) 1 else 0;
// inject args
inline for (method_info.@"fn".params[args_offset..], args_offset..) |param, i| {
tuple_args[i] = Napi.from_napi_value(method_env, cb_info.args[i - args_offset].raw, param.type.?);
}
const result = @call(.auto, method, tuple_args);
return Napi.to_napi_value(method_env, result, fn_name) catch null;
}
};

properties[prop_idx] = napi.napi_property_descriptor{
.utf8name = @ptrCast(fn_name.ptr),
.name = null,
.method = MethodWrapper.call,
.getter = null,
.setter = null,
.value = null,
.attributes = comptime if (is_instance_method) napi.napi_default else napi.napi_static,
.data = null,
};
}
};

properties[prop_idx] = napi.napi_property_descriptor{
.utf8name = @ptrCast(decl.name.ptr),
.name = null,
.method = MethodWrapper.call,
.getter = null,
.setter = null,
.value = null,
.attributes = if (is_instance_method) napi.napi_default else napi.napi_static,
.data = null,
};
prop_idx += 1;
prop_idx += 1;
}
}
}

Expand All @@ -197,29 +291,23 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {

fn define_custom_method(_: napi.napi_env, _: napi.napi_value) !void {}

/// to_napi_value will create a class constructor and return it
pub fn to_napi_value(env: napi_env.Env) !napi.napi_value {
const constructor = try Self.define_class(env.raw);

try Self.define_custom_method(env.raw, constructor);

return constructor;
}
};
}

/// Create a class with default constructor function
pub fn Class(comptime T: type) type {
return ClassWrapper(T, true);
}

/// Create a class without default constructor function
pub fn ClassWithoutInit(comptime T: type) type {
return ClassWrapper(T, false);
}

pub fn isClass(T: anytype) bool {
const type_name = @typeName(T);

return std.mem.indexOf(u8, type_name, "ClassWrapper") != null;
}