Skip to content

Commit 1a9cd1b

Browse files
authored
Add basic implementation for Class (#21)
* Class init * Basic implementation for Class
1 parent 1ecec6f commit 1a9cd1b

8 files changed

Lines changed: 289 additions & 2 deletions

File tree

examples/basic/src/class.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const napi = @import("napi");
2+
3+
const Test = struct {
4+
name: []u8,
5+
age: i32,
6+
};
7+
8+
pub const TestClass = napi.Class(Test);

examples/basic/src/hello.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const array = @import("array.zig");
88
const object = @import("object.zig");
99
const function = @import("function.zig");
1010
const thread_safe_function = @import("thread_safe_function.zig");
11+
const class = @import("class.zig");
1112
const log = @import("log/log.zig");
1213

1314
pub const test_i32 = number.test_i32;
@@ -38,6 +39,8 @@ pub const create_function = function.create_function;
3839

3940
pub const call_thread_safe_function = thread_safe_function.call_thread_safe_function;
4041

42+
pub const TestClass = class.TestClass;
43+
4144
pub const test_hilog = log.test_hilog;
4245

4346
comptime {

src/napi.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const module = @import("./prelude/module.zig");
66
const worker = @import("./napi/wrapper/worker.zig");
77
const err = @import("./napi/wrapper/error.zig");
88
const thread_safe_function = @import("./napi/wrapper/thread_safe_function.zig");
9+
const class = @import("./napi/wrapper/class.zig");
910

1011
pub const napi_sys = @import("napi-sys");
1112

@@ -30,6 +31,8 @@ pub const Function = function.Function;
3031
pub const CallbackInfo = callback_info.CallbackInfo;
3132
pub const Worker = worker.Worker;
3233
pub const ThreadSafeFunction = thread_safe_function.ThreadSafeFunction;
34+
pub const Class = class.Class;
35+
pub const ClassWithoutInit = class.ClassWithoutInit;
3336

3437
pub const NODE_API_MODULE = module.NODE_API_MODULE;
3538
pub const NODE_API_MODULE_WITH_INIT = module.NODE_API_MODULE_WITH_INIT;

src/napi/util/allocator.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const std = @import("std");
2+
3+
pub const AllocatorManager = struct {
4+
allocator: std.mem.Allocator,
5+
6+
const Self = @This();
7+
8+
pub fn init() Self {
9+
return Self{
10+
.allocator = std.heap.page_allocator,
11+
};
12+
}
13+
14+
pub fn get(self: *const Self) std.mem.Allocator {
15+
return self.allocator;
16+
}
17+
18+
pub fn set(self: *Self, new_allocator: std.mem.Allocator) void {
19+
self.allocator = new_allocator;
20+
}
21+
};
22+
23+
pub const global_manager = AllocatorManager.init();
24+
25+
pub fn globalAllocator() std.mem.Allocator {
26+
return global_manager.get();
27+
}
28+
29+
pub fn setGlobalAllocator(new_allocator: std.mem.Allocator) void {
30+
global_manager.set(new_allocator);
31+
}

src/napi/util/helper.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,8 @@ pub fn collectFunctionArgs(comptime functions: anytype) type {
192192
.is_tuple = true,
193193
} });
194194
}
195+
196+
pub fn shortTypeName(comptime T: type) []const u8 {
197+
var iter = std.mem.splitBackwardsScalar(u8, @typeName(T), '.');
198+
return iter.first();
199+
}

src/napi/util/napi.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const Env = @import("../env.zig").Env;
66
const NapiError = @import("../wrapper/error.zig");
77
const Function = @import("../value/function.zig").Function;
88
const ThreadSafeFunction = @import("../wrapper/thread_safe_function.zig").ThreadSafeFunction;
9+
const class = @import("../wrapper/class.zig");
910

1011
pub const Napi = struct {
1112
pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T {
@@ -202,6 +203,7 @@ pub const Napi = struct {
202203
const array = try NapiValue.Array.New(Env.from_raw(env), value);
203204
return array.raw;
204205
}
206+
205207
const object = try NapiValue.Object.New(Env.from_raw(env), value);
206208
return object.raw;
207209
},
@@ -227,7 +229,9 @@ pub const Napi = struct {
227229
return NapiValue.String.New(Env.from_raw(env), value).raw;
228230
},
229231
else => {
230-
232+
if (comptime class.isClass(value)) {
233+
return try value.to_napi_value(Env.from_raw(env));
234+
}
231235
// TODO: Implement this
232236
@compileError("Unsupported type: " ++ @typeName(value));
233237
},

src/napi/wrapper/callback_info.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub const CallbackInfo = struct {
77
raw: napi.napi_callback_info,
88
env: napi.napi_env,
99
args: []const value.NapiValue,
10+
this: napi.napi_value,
1011

1112
pub fn from_raw(env: napi.napi_env, raw: napi.napi_callback_info) CallbackInfo {
1213
var init_argc: usize = 0;
@@ -19,7 +20,9 @@ pub const CallbackInfo = struct {
1920
const args_raw = allocator.alloc(napi.napi_value, init_argc) catch @panic("OOM");
2021
defer allocator.free(args_raw);
2122

22-
const status2 = napi.napi_get_cb_info(env, raw, &init_argc, args_raw.ptr, null, null);
23+
var this: napi.napi_value = undefined;
24+
25+
const status2 = napi.napi_get_cb_info(env, raw, &init_argc, args_raw.ptr, &this, null);
2326
if (status2 != napi.napi_ok) {
2427
@panic("Failed to get callback info");
2528
}
@@ -34,6 +37,7 @@ pub const CallbackInfo = struct {
3437
.raw = raw,
3538
.env = env,
3639
.args = result,
40+
.this = this,
3741
};
3842
}
3943

@@ -44,4 +48,8 @@ pub const CallbackInfo = struct {
4448
pub fn Get(self: CallbackInfo, index: usize) value.NapiValue {
4549
return self.args[index];
4650
}
51+
52+
pub fn This(self: CallbackInfo) napi.napi_value {
53+
return self.this;
54+
}
4755
};

src/napi/wrapper/class.zig

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
const std = @import("std");
2+
const napi = @import("napi-sys").napi_sys;
3+
const CallbackInfo = @import("./callback_info.zig").CallbackInfo;
4+
const napi_env = @import("../env.zig");
5+
const Napi = @import("../util/napi.zig").Napi;
6+
const helper = @import("../util/helper.zig");
7+
const GlobalAllocator = @import("../util/allocator.zig");
8+
9+
var class_constructors: std.StringHashMap(napi.napi_value) = std.StringHashMap(napi.napi_value).init(GlobalAllocator.globalAllocator());
10+
11+
pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type {
12+
const type_info = @typeInfo(T);
13+
if (type_info != .@"struct") {
14+
@compileError("Class() only support struct type");
15+
}
16+
17+
if (type_info.@"struct".is_tuple) {
18+
@compileError("Class() does not support tuple type");
19+
}
20+
21+
const fields = type_info.@"struct".fields;
22+
const decls = type_info.@"struct".decls;
23+
24+
const class_name = comptime helper.shortTypeName(T);
25+
return struct {
26+
const WrappedType = T;
27+
28+
env: napi.napi_env,
29+
raw: napi.napi_value,
30+
31+
const Self = @This();
32+
33+
// 构造器回调
34+
fn constructor_callback(env: napi.napi_env, callback_info: napi.napi_callback_info) callconv(.c) napi.napi_value {
35+
const infos = CallbackInfo.from_raw(env, callback_info);
36+
37+
const data = GlobalAllocator.globalAllocator().create(T) catch return null;
38+
39+
if (@hasDecl(T, "init")) {
40+
var tuple_args: std.meta.ArgsTuple(T.init) = undefined;
41+
inline for (@typeInfo(std.meta.ArgsTuple(T.init)).@"fn".params, 0..) |arg, i| {
42+
tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, arg.type.?);
43+
}
44+
45+
data.* = @call(.auto, T.init, tuple_args) catch {
46+
GlobalAllocator.globalAllocator().destroy(data);
47+
return null;
48+
};
49+
} else {
50+
// init with zero
51+
data.* = std.mem.zeroes(T);
52+
// if HasInit, init with args with order of fields
53+
if (comptime HasInit) {
54+
inline for (fields, 0..) |field, i| {
55+
@field(data.*, field.name) = Napi.from_napi_value(infos.env, infos.args[i].raw, field.type);
56+
}
57+
}
58+
}
59+
60+
const this_obj = infos.This();
61+
62+
var ref: napi.napi_ref = undefined;
63+
const status = napi.napi_wrap(env, this_obj, data, finalize_callback, null, &ref);
64+
if (status != napi.napi_ok) {
65+
GlobalAllocator.globalAllocator().destroy(data);
66+
return null;
67+
}
68+
69+
var ref_count: u32 = undefined;
70+
_ = napi.napi_reference_unref(env, ref, &ref_count);
71+
72+
return this_obj;
73+
}
74+
75+
fn finalize_callback(env: napi.napi_env, data: ?*anyopaque, hint: ?*anyopaque) callconv(.c) void {
76+
_ = env;
77+
_ = hint;
78+
79+
if (data) |ptr| {
80+
const typed_data: *T = @ptrCast(@alignCast(ptr));
81+
if (@hasDecl(T, "deinit")) {
82+
typed_data.deinit();
83+
}
84+
GlobalAllocator.globalAllocator().destroy(typed_data);
85+
}
86+
}
87+
88+
fn define_class(env: napi.napi_env) !napi.napi_value {
89+
comptime var property_count: usize = 0;
90+
inline for (fields) |_| {
91+
property_count += 1;
92+
}
93+
inline for (decls) |decl| {
94+
if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and
95+
!std.mem.eql(u8, decl.name, "init") and
96+
!std.mem.eql(u8, decl.name, "deinit"))
97+
{
98+
property_count += 1;
99+
}
100+
}
101+
102+
var properties: [property_count]napi.napi_property_descriptor = undefined;
103+
var prop_idx: usize = 0;
104+
105+
inline for (fields) |field| {
106+
const FieldAccessor = struct {
107+
fn getter(getter_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value {
108+
const cb_info = CallbackInfo.from_raw(getter_env, info);
109+
var data: ?*anyopaque = null;
110+
_ = napi.napi_unwrap(getter_env, cb_info.This(), &data);
111+
if (data == null) return null;
112+
113+
const instance: *T = @ptrCast(@alignCast(data.?));
114+
const field_value = @field(instance.*, field.name);
115+
return Napi.to_napi_value(getter_env, field_value, field.name) catch null;
116+
}
117+
118+
fn setter(setter_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value {
119+
const cb_info = CallbackInfo.from_raw(setter_env, info);
120+
var data: ?*anyopaque = null;
121+
_ = napi.napi_unwrap(setter_env, cb_info.This(), &data);
122+
if (data == null) return null;
123+
124+
const instance: *T = @ptrCast(@alignCast(data.?));
125+
const args = cb_info.args;
126+
if (args.len > 0) {
127+
const new_value = Napi.from_napi_value(setter_env, args[0].raw, field.type);
128+
@field(instance.*, field.name) = new_value;
129+
}
130+
131+
return null;
132+
}
133+
};
134+
135+
properties[prop_idx] = napi.napi_property_descriptor{
136+
.utf8name = @ptrCast(field.name.ptr),
137+
.name = null,
138+
.method = null,
139+
.getter = FieldAccessor.getter,
140+
.setter = FieldAccessor.setter,
141+
.value = null,
142+
.attributes = napi.napi_default,
143+
.data = null,
144+
};
145+
prop_idx += 1;
146+
}
147+
148+
inline for (decls) |decl| {
149+
if (@typeInfo(@TypeOf(@field(T, decl.name))) == .Fn and
150+
!std.mem.eql(u8, decl.name, "init") and
151+
!std.mem.eql(u8, decl.name, "deinit"))
152+
{
153+
const method = @field(T, decl.name);
154+
const method_info = @typeInfo(@TypeOf(method));
155+
const params = method_info.Fn.params;
156+
const is_instance_method = params.len > 0 and params[0].type.? == *T;
157+
158+
const MethodWrapper = struct {
159+
fn call(method_env: napi.napi_env, info: napi.napi_callback_info) callconv(.c) napi.napi_value {
160+
const cb_info = CallbackInfo.from_raw(method_env, info);
161+
162+
if (is_instance_method) {
163+
var data: ?*anyopaque = null;
164+
_ = napi.napi_unwrap(method_env, cb_info.This(), &data);
165+
if (data == null) return null;
166+
167+
const instance: *T = @ptrCast(@alignCast(data.?));
168+
const result = method(instance);
169+
return Napi.to_napi_value(method_env, result, decl.name) catch null;
170+
} else {
171+
const result = method();
172+
return Napi.to_napi_value(method_env, result, decl.name) catch null;
173+
}
174+
}
175+
};
176+
177+
properties[prop_idx] = napi.napi_property_descriptor{
178+
.utf8name = @ptrCast(decl.name.ptr),
179+
.name = null,
180+
.method = MethodWrapper.call,
181+
.getter = null,
182+
.setter = null,
183+
.value = null,
184+
.attributes = if (is_instance_method) napi.napi_default else napi.napi_static,
185+
.data = null,
186+
};
187+
prop_idx += 1;
188+
}
189+
}
190+
191+
var constructor: napi.napi_value = undefined;
192+
_ = napi.napi_define_class(env, class_name.ptr, class_name.len, constructor_callback, null, prop_idx, &properties, &constructor);
193+
194+
try class_constructors.put(class_name, constructor);
195+
return constructor;
196+
}
197+
198+
fn define_custom_method(_: napi.napi_env, _: napi.napi_value) !void {}
199+
200+
/// to_napi_value will create a class constructor and return it
201+
pub fn to_napi_value(env: napi_env.Env) !napi.napi_value {
202+
const constructor = try Self.define_class(env.raw);
203+
204+
try Self.define_custom_method(env.raw, constructor);
205+
206+
return constructor;
207+
}
208+
};
209+
}
210+
211+
/// Create a class with default constructor function
212+
pub fn Class(comptime T: type) type {
213+
return ClassWrapper(T, true);
214+
}
215+
216+
/// Create a class without default constructor function
217+
pub fn ClassWithoutInit(comptime T: type) type {
218+
return ClassWrapper(T, false);
219+
}
220+
221+
pub fn isClass(T: anytype) bool {
222+
const type_name = @typeName(T);
223+
224+
return std.mem.indexOf(u8, type_name, "ClassWrapper") != null;
225+
}

0 commit comments

Comments
 (0)