diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts index 619fd07..f543f23 100644 --- a/examples/basic/index.d.ts +++ b/examples/basic/index.d.ts @@ -190,6 +190,13 @@ export declare function basic_function(left: number, right: number): number; */ export declare function create_function(): CallbackFunction; +/** + * Creates a native reference to the callback, reads it back, and calls it with (1, 2) + * @param cb - A callback function that takes two numbers and returns a number + * @returns The result of calling cb(1, 2) + */ +export declare function call_function_with_reference(cb: CallbackFunction): number; + // ============== Thread Safe Function ============== /** diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index 102137f..e278774 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -14,6 +14,7 @@ const buffer = @import("buffer.zig"); const arraybuffer = @import("arraybuffer.zig"); const typedarray = @import("typedarray.zig"); const dataview = @import("dataview.zig"); +const reference = @import("reference.zig"); pub const test_i32 = number.test_i32; pub const test_f32 = number.test_f32; @@ -40,6 +41,7 @@ pub const return_nullable = object.return_nullable; pub const call_function = function.call_function; pub const basic_function = function.basic_function; pub const create_function = function.create_function; +pub const call_function_with_reference = reference.call_function_with_reference; pub const call_thread_safe_function = thread_safe_function.call_thread_safe_function; diff --git a/examples/basic/src/reference.zig b/examples/basic/src/reference.zig new file mode 100644 index 0000000..6624b2f --- /dev/null +++ b/examples/basic/src/reference.zig @@ -0,0 +1,11 @@ +const napi = @import("napi"); + +const Args = struct { i32, i32 }; + +pub fn call_function_with_reference(env: napi.Env, cb: napi.Function(Args, i32)) !i32 { + var reference = try cb.CreateRef(); + defer reference.Unref(env) catch @panic("Failed to unref reference"); + + const function = try reference.GetValue(env); + return try function.Call(.{ 1, 2 }); +} diff --git a/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts b/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts index f7ba387..e156915 100644 --- a/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts +++ b/harmony_example/entry/src/main/cpp/types/libhello/Index.d.ts @@ -192,6 +192,13 @@ export declare function basic_function(left: number, right: number): number; */ export declare function create_function(): CallbackFunction; +/** + * Creates a native reference to the callback, reads it back, and calls it with (1, 2) + * @param cb - A callback function that takes two numbers and returns a number + * @returns The result of calling cb(1, 2) + */ +export declare function call_function_with_reference(cb: CallbackFunction): number; + // ============== Thread Safe Function ============== /** diff --git a/src/napi.zig b/src/napi.zig index d70492e..8684bb1 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -11,6 +11,7 @@ const buffer = @import("./napi/wrapper/buffer.zig"); const arraybuffer = @import("./napi/wrapper/arraybuffer.zig"); const typedarray = @import("./napi/wrapper/typedarray.zig"); const dataview = @import("./napi/wrapper/dataview.zig"); +const reference = @import("./napi/wrapper/reference.zig"); pub const napi_sys = @import("napi-sys"); pub const Env = env.Env; @@ -50,6 +51,12 @@ pub const Float64Array = typedarray.Float64Array; pub const BigInt64Array = typedarray.BigInt64Array; pub const BigUint64Array = typedarray.BigUint64Array; pub const DataView = dataview.DataView; +pub const Reference = reference.Reference; +pub const Ref = reference.Reference; +pub fn FunctionRef(comptime Args: type, comptime Return: type) type { + return reference.Reference(function.Function(Args, Return)); +} +pub const ObjectRef = reference.Reference(value.Object); pub const NODE_API_MODULE = module.NODE_API_MODULE; pub const NODE_API_MODULE_WITH_INIT = module.NODE_API_MODULE_WITH_INIT; diff --git a/src/napi/util/helper.zig b/src/napi/util/helper.zig index 02d38d3..4b84db6 100644 --- a/src/napi/util/helper.zig +++ b/src/napi/util/helper.zig @@ -92,6 +92,10 @@ pub fn isDataView(comptime T: type) bool { return @hasDecl(T, "is_napi_dataview"); } +pub fn isReference(comptime T: type) bool { + return @hasDecl(T, "is_napi_reference"); +} + pub fn isArrayList(comptime T: type) bool { const info = @typeInfo(T); if (info != .@"struct") { diff --git a/src/napi/util/napi.zig b/src/napi/util/napi.zig index 4196e26..cb1a73b 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -98,6 +98,9 @@ pub const Napi = struct { if (comptime helper.isDataView(T)) { return T.from_raw(env, raw); } + if (comptime helper.isReference(T)) { + return T.from_napi_value(env, raw); + } if (comptime helper.isTuple(T)) { return NapiValue.Array.from_napi_value(env, raw, T); @@ -210,6 +213,9 @@ pub const Napi = struct { if (comptime helper.isDataView(value_type)) { return value.raw; } + if (comptime helper.isReference(value_type)) { + return try value.to_napi_value(env); + } if (comptime helper.isTuple(value_type)) { const array = try NapiValue.Array.New(Env.from_raw(env), value); return array.raw; diff --git a/src/napi/value/function.zig b/src/napi/value/function.zig index 49ca02a..7b39eeb 100644 --- a/src/napi/value/function.zig +++ b/src/napi/value/function.zig @@ -6,6 +6,7 @@ const Napi = @import("../util/napi.zig").Napi; const NapiError = @import("../wrapper/error.zig"); const Undefined = @import("./undefined.zig").Undefined; const GlobalAllocator = @import("../util/allocator.zig"); +const Reference = @import("../wrapper/reference.zig").Reference; pub fn Function(comptime Args: type, comptime Return: type) type { const ArgsInfos = @typeInfo(Args); @@ -135,5 +136,9 @@ pub fn Function(comptime Args: type, comptime Return: type) type { return Napi.from_napi_value(self.env, result, Return); } + + pub fn CreateRef(self: Self) !Reference(Self) { + return Reference(Self).New(Env.from_raw(self.env), self); + } }; } diff --git a/src/napi/value/object.zig b/src/napi/value/object.zig index 8cd8063..473e360 100644 --- a/src/napi/value/object.zig +++ b/src/napi/value/object.zig @@ -11,6 +11,7 @@ const String = @import("./string.zig").String; const helper = @import("../util/helper.zig"); const Napi = @import("../util/napi.zig").Napi; const NapiError = @import("../wrapper/error.zig"); +const Reference = @import("../wrapper/reference.zig").Reference; pub const Object = struct { env: napi.napi_env, @@ -158,4 +159,8 @@ pub const Object = struct { } return result; } + + pub fn CreateRef(self: Object) !Reference(Object) { + return Reference(Object).New(Env.from_raw(self.env), self); + } }; diff --git a/src/napi/wrapper/dataview.zig b/src/napi/wrapper/dataview.zig index bbaba3d..f7dee79 100644 --- a/src/napi/wrapper/dataview.zig +++ b/src/napi/wrapper/dataview.zig @@ -61,10 +61,6 @@ pub const DataView = struct { return DataView.from_raw(env.raw, raw); } - pub fn from_arraybuffer(env: Env, arraybuffer: ArrayBuffer, byte_offset: usize, byte_length: usize) !DataView { - return DataView.fromArrayBuffer(env, arraybuffer, byte_offset, byte_length); - } - pub fn New(env: Env, byte_length: usize) !DataView { const arraybuffer = try ArrayBuffer.New(env, byte_length); return DataView.fromArrayBuffer(env, arraybuffer, 0, byte_length); @@ -75,19 +71,11 @@ pub const DataView = struct { return DataView.fromArrayBuffer(env, arraybuffer, 0, data.len); } - pub fn copy_from(env: Env, data: []const u8) !DataView { - return DataView.copy(env, data); - } - pub fn from(env: Env, data: []u8) !DataView { const arraybuffer = try ArrayBuffer.from(env, data); return DataView.fromArrayBuffer(env, arraybuffer, 0, data.len); } - pub fn from_data(env: Env, data: []u8) !DataView { - return DataView.from(env, data); - } - pub fn asSlice(self: DataView) []u8 { return self.data[0..self.byte_length]; } diff --git a/src/napi/wrapper/reference.zig b/src/napi/wrapper/reference.zig new file mode 100644 index 0000000..b6d93fa --- /dev/null +++ b/src/napi/wrapper/reference.zig @@ -0,0 +1,113 @@ +const napi = @import("napi-sys").napi_sys; +const Env = @import("../env.zig").Env; +const NapiError = @import("./error.zig"); +pub fn Reference(comptime T: type) type { + if (!@hasDecl(T, "from_raw")) { + @compileError("Reference(T) requires T.from_raw"); + } + if (!@hasField(T, "raw")) { + @compileError("Reference(T) requires T.raw"); + } + + return struct { + pub const is_napi_reference = true; + pub const referenced_type = T; + + raw_ref: napi.napi_ref, + taken: bool, + + const Self = @This(); + + pub fn from_raw(env: napi.napi_env, raw: napi.napi_ref) Self { + _ = env; + return Self{ + .raw_ref = raw, + .taken = false, + }; + } + + pub fn New(env: Env, value: T) !Self { + var raw_ref: napi.napi_ref = undefined; + const status = napi.napi_create_reference(env.raw, value.raw, 1, &raw_ref); + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + return Self.from_raw(env.raw, raw_ref); + } + + pub fn from_napi_value(env: napi.napi_env, raw_value: napi.napi_value) Self { + const value = T.from_raw(env, raw_value); + return Self.New(Env.from_raw(env), value) catch @panic("Failed to create reference"); + } + + pub fn to_napi_value(self: Self, env: napi.napi_env) !napi.napi_value { + return try self.get_raw_value(Env.from_raw(env)); + } + + fn get_raw_value(self: Self, env: Env) !napi.napi_value { + if (self.taken) { + return NapiError.Error.fromStatus(@as([]const u8, "Ref value has been deleted")); + } + + var raw_value: napi.napi_value = undefined; + const status = napi.napi_get_reference_value(env.raw, self.raw_ref, &raw_value); + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + if (raw_value == null) { + return NapiError.Error.fromStatus(NapiError.Status.InvalidArg); + } + + return raw_value; + } + + pub fn get_value(self: Self, env: Env) !T { + const raw_value = try self.get_raw_value(env); + return T.from_raw(env.raw, raw_value); + } + + pub fn GetValue(self: Self, env: Env) !T { + const raw_value = try self.get_raw_value(env); + return T.from_raw(env.raw, raw_value); + } + + pub fn Unref(self: *Self, env: Env) !void { + if (self.taken or self.raw_ref == null) { + return NapiError.Error.fromStatus(@as([]const u8, "Ref value has been deleted")); + } + + var count: u32 = 0; + const unref_status = napi.napi_reference_unref(env.raw, self.raw_ref, &count); + if (unref_status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(unref_status)); + } + + const delete_status = napi.napi_delete_reference(env.raw, self.raw_ref); + if (delete_status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(delete_status)); + } + + self.taken = true; + self.raw_ref = null; + } + + pub fn Ref(self: *Self, env: Env) !u32 { + if (self.taken or self.raw_ref == null) { + return NapiError.Error.fromStatus(@as([]const u8, "Ref value has been deleted")); + } + + var count: u32 = 0; + const status = napi.napi_reference_ref(env.raw, self.raw_ref, &count); + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + return count; + } + + pub fn Delete(self: *Self, env: Env) !void { + return Self.Unref(self, env); + } + }; +} diff --git a/src/napi/wrapper/typedarray.zig b/src/napi/wrapper/typedarray.zig index dd29a63..5fe02b3 100644 --- a/src/napi/wrapper/typedarray.zig +++ b/src/napi/wrapper/typedarray.zig @@ -105,10 +105,6 @@ pub fn TypedArray(comptime T: type) type { return Self.from_raw(env.raw, raw); } - pub fn from_arraybuffer(env: Env, arraybuffer: ArrayBuffer, len: usize, byte_offset: usize) !Self { - return Self.fromArrayBuffer(env, arraybuffer, len, byte_offset); - } - pub fn New(env: Env, len: usize) !Self { const arraybuffer = try ArrayBuffer.New(env, len * @sizeOf(T)); return Self.fromArrayBuffer(env, arraybuffer, len, 0); @@ -120,19 +116,11 @@ pub fn TypedArray(comptime T: type) type { return result; } - pub fn copy_from(env: Env, data: []const T) !Self { - return Self.copy(env, data); - } - pub fn from(env: Env, data: []T) !Self { const arraybuffer = try ArrayBuffer.from(env, std.mem.sliceAsBytes(data)); return Self.fromArrayBuffer(env, arraybuffer, data.len, 0); } - pub fn from_data(env: Env, data: []T) !Self { - return Self.from(env, data); - } - pub fn asSlice(self: Self) []T { return self.data[0..self.len]; }