From dd6ccfe6482999e84fb47cd78ebf53908a74d268 Mon Sep 17 00:00:00 2001 From: richerfu Date: Sat, 11 Oct 2025 11:04:18 +0800 Subject: [PATCH] Add factory for class --- examples/basic/src/class.zig | 28 ++++ examples/basic/src/hello.zig | 3 + src/napi/wrapper/callback_info.zig | 11 +- src/napi/wrapper/class.zig | 218 ++++++++++++++++++++--------- 4 files changed, 191 insertions(+), 69 deletions(-) diff --git a/examples/basic/src/class.zig b/examples/basic/src/class.zig index 53aab2b..a52ab20 100644 --- a/examples/basic/src/class.zig +++ b/examples/basic/src/class.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const napi = @import("napi"); const Test = struct { @@ -5,4 +6,31 @@ const Test = struct { 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); diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index 08c80c0..b72b16a 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -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; diff --git a/src/napi/wrapper/callback_info.zig b/src/napi/wrapper/callback_info.zig index 57149f8..92c7467 100644 --- a/src/napi/wrapper/callback_info.zig +++ b/src/napi/wrapper/callback_info.zig @@ -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 { @@ -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; @@ -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]); @@ -38,6 +39,8 @@ pub const CallbackInfo = struct { .env = env, .args = result, .this = this, + .args_raw = args_raw, + .args_count = init_argc, }; } diff --git a/src/napi/wrapper/class.zig b/src/napi/wrapper/class.zig index 0877f69..1ffd861 100644 --- a/src/napi/wrapper/class.zig +++ b/src/napi/wrapper/class.zig @@ -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()); @@ -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); } } } @@ -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; @@ -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; + } } } @@ -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; } }; @@ -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; + } } } @@ -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; }