diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts index 8a93704..619fd07 100644 --- a/examples/basic/index.d.ts +++ b/examples/basic/index.d.ts @@ -298,3 +298,54 @@ export declare function get_arraybuffer(arraybuffer: ArrayBuffer): number; export declare function get_arraybuffer_as_string( arraybuffer: ArrayBuffer ): string; + +// ============== TypedArray Functions ============== + +/** + * Creates a Uint8Array with 4 bytes: [1, 2, 3, 4] + * @returns The created typed array + */ +export declare function create_uint8_typedarray(): Uint8Array; + +/** + * Gets the length of a Uint8Array + * @param array - The typed array + * @returns The element count + */ +export declare function get_uint8_typedarray_length(array: Uint8Array): number; + +/** + * Sums all items in a Float32Array + * @param array - The typed array + * @returns The sum of all elements + */ +export declare function sum_float32_typedarray(array: Float32Array): number; + +// ============== DataView Functions ============== + +/** + * Creates a DataView with 4 bytes initialized to 0x78, 0x56, 0x34, 0x12 + * @returns The created data view + */ +export declare function create_dataview(): DataView; + +/** + * Gets the byte length of a DataView + * @param view - The data view + * @returns The byte length + */ +export declare function get_dataview_length(view: DataView): number; + +/** + * Gets the first byte of a DataView + * @param view - The data view + * @returns The first byte, or 0 if empty + */ +export declare function get_dataview_first_byte(view: DataView): number; + +/** + * Reads the first 4 bytes of a DataView as a little-endian uint32 + * @param view - The data view + * @returns The uint32 value + */ +export declare function get_dataview_uint32_le(view: DataView): number; diff --git a/examples/basic/src/dataview.zig b/examples/basic/src/dataview.zig new file mode 100644 index 0000000..231037b --- /dev/null +++ b/examples/basic/src/dataview.zig @@ -0,0 +1,22 @@ +const napi = @import("napi"); + +pub fn create_dataview(env: napi.Env) !napi.DataView { + var view = try napi.DataView.New(env, 4); + try view.setUint32(0, 0x12345678, true); + return view; +} + +pub fn get_dataview_length(view: napi.DataView) usize { + return view.byteLength(); +} + +pub fn get_dataview_first_byte(view: napi.DataView) u8 { + if (view.byteLength() == 0) { + return 0; + } + return view.asConstSlice()[0]; +} + +pub fn get_dataview_uint32_le(view: napi.DataView) !u32 { + return try view.getUint32(0, true); +} diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index 4f6f3e5..102137f 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -12,6 +12,8 @@ const class = @import("class.zig"); const log = @import("log/log.zig"); const buffer = @import("buffer.zig"); const arraybuffer = @import("arraybuffer.zig"); +const typedarray = @import("typedarray.zig"); +const dataview = @import("dataview.zig"); pub const test_i32 = number.test_i32; pub const test_f32 = number.test_f32; @@ -56,6 +58,15 @@ pub const create_arraybuffer = arraybuffer.create_arraybuffer; pub const get_arraybuffer = arraybuffer.get_arraybuffer; pub const get_arraybuffer_as_string = arraybuffer.get_arraybuffer_as_string; +pub const create_uint8_typedarray = typedarray.create_uint8_typedarray; +pub const get_uint8_typedarray_length = typedarray.get_uint8_typedarray_length; +pub const sum_float32_typedarray = typedarray.sum_float32_typedarray; + +pub const create_dataview = dataview.create_dataview; +pub const get_dataview_length = dataview.get_dataview_length; +pub const get_dataview_first_byte = dataview.get_dataview_first_byte; +pub const get_dataview_uint32_le = dataview.get_dataview_uint32_le; + comptime { napi.NODE_API_MODULE("hello", @This()); } diff --git a/examples/basic/src/typedarray.zig b/examples/basic/src/typedarray.zig new file mode 100644 index 0000000..6708d24 --- /dev/null +++ b/examples/basic/src/typedarray.zig @@ -0,0 +1,17 @@ +const napi = @import("napi"); + +pub fn create_uint8_typedarray(env: napi.Env) !napi.Uint8Array { + return napi.Uint8Array.copy(env, &[_]u8{ 1, 2, 3, 4 }); +} + +pub fn get_uint8_typedarray_length(array: napi.Uint8Array) usize { + return array.length(); +} + +pub fn sum_float32_typedarray(array: napi.Float32Array) f32 { + var sum: f32 = 0; + for (array.asConstSlice()) |item| { + sum += item; + } + return sum; +} 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 2c60206..f7ba387 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 @@ -274,4 +274,55 @@ export declare function get_buffer(buffer: ArrayBuffer): number; * @param buffer - The buffer * @returns The buffer as a string */ -export declare function get_buffer_as_string(buffer: ArrayBuffer): string; \ No newline at end of file +export declare function get_buffer_as_string(buffer: ArrayBuffer): string; + +// ============== TypedArray Functions ============== + +/** + * Creates a Uint8Array with 4 bytes: [1, 2, 3, 4] + * @returns The created typed array + */ +export declare function create_uint8_typedarray(): Uint8Array; + +/** + * Gets the length of a Uint8Array + * @param array - The typed array + * @returns The element count + */ +export declare function get_uint8_typedarray_length(array: Uint8Array): number; + +/** + * Sums all items in a Float32Array + * @param array - The typed array + * @returns The sum of all elements + */ +export declare function sum_float32_typedarray(array: Float32Array): number; + +// ============== DataView Functions ============== + +/** + * Creates a DataView with 4 bytes initialized to 0x78, 0x56, 0x34, 0x12 + * @returns The created data view + */ +export declare function create_dataview(): DataView; + +/** + * Gets the byte length of a DataView + * @param view - The data view + * @returns The byte length + */ +export declare function get_dataview_length(view: DataView): number; + +/** + * Gets the first byte of a DataView + * @param view - The data view + * @returns The first byte, or 0 if empty + */ +export declare function get_dataview_first_byte(view: DataView): number; + +/** + * Reads the first 4 bytes of a DataView as a little-endian uint32 + * @param view - The data view + * @returns The uint32 value + */ +export declare function get_dataview_uint32_le(view: DataView): number; diff --git a/src/napi.zig b/src/napi.zig index f7514fc..d70492e 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -9,6 +9,8 @@ const thread_safe_function = @import("./napi/wrapper/thread_safe_function.zig"); const class = @import("./napi/wrapper/class.zig"); 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"); pub const napi_sys = @import("napi-sys"); pub const Env = env.Env; @@ -36,6 +38,18 @@ pub const Class = class.Class; pub const ClassWithoutInit = class.ClassWithoutInit; pub const Buffer = buffer.Buffer; pub const ArrayBuffer = arraybuffer.ArrayBuffer; +pub const TypedArray = typedarray.TypedArray; +pub const Int8Array = typedarray.Int8Array; +pub const Uint8Array = typedarray.Uint8Array; +pub const Int16Array = typedarray.Int16Array; +pub const Uint16Array = typedarray.Uint16Array; +pub const Int32Array = typedarray.Int32Array; +pub const Uint32Array = typedarray.Uint32Array; +pub const Float32Array = typedarray.Float32Array; +pub const Float64Array = typedarray.Float64Array; +pub const BigInt64Array = typedarray.BigInt64Array; +pub const BigUint64Array = typedarray.BigUint64Array; +pub const DataView = dataview.DataView; 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 8d1354f..02d38d3 100644 --- a/src/napi/util/helper.zig +++ b/src/napi/util/helper.zig @@ -84,6 +84,14 @@ pub fn isThreadSafeFunction(comptime T: type) bool { return false; } +pub fn isTypedArray(comptime T: type) bool { + return @hasDecl(T, "is_napi_typedarray"); +} + +pub fn isDataView(comptime T: type) bool { + return @hasDecl(T, "is_napi_dataview"); +} + 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 2d4a6db..4196e26 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -9,12 +9,13 @@ const ThreadSafeFunction = @import("../wrapper/thread_safe_function.zig").Thread const class = @import("../wrapper/class.zig"); const Buffer = @import("../wrapper/buffer.zig").Buffer; const ArrayBuffer = @import("../wrapper/arraybuffer.zig").ArrayBuffer; +const DataView = @import("../wrapper/dataview.zig").DataView; pub const Napi = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { const infos = @typeInfo(T); switch (T) { - NapiValue.BigInt, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer, ArrayBuffer => { + NapiValue.BigInt, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer, ArrayBuffer, DataView => { return T.from_raw(env, raw); }, else => { @@ -91,6 +92,12 @@ pub const Napi = struct { } return Function(args_type, return_type).from_raw(env, raw); } + if (comptime helper.isTypedArray(T)) { + return T.from_raw(env, raw); + } + if (comptime helper.isDataView(T)) { + return T.from_raw(env, raw); + } if (comptime helper.isTuple(T)) { return NapiValue.Array.from_napi_value(env, raw, T); @@ -136,7 +143,7 @@ pub const Napi = struct { const infos = @typeInfo(value_type); switch (value_type) { - NapiValue.BigInt, NapiValue.Bool, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer, ArrayBuffer => { + NapiValue.BigInt, NapiValue.Bool, NapiValue.Number, NapiValue.String, NapiValue.Object, NapiValue.Promise, NapiValue.Array, NapiValue.Undefined, NapiValue.Null, Buffer, ArrayBuffer, DataView => { return value.raw; }, // If value is already a napi_value, return it directly @@ -194,9 +201,15 @@ pub const Napi = struct { if (comptime helper.isNapiFunction(value_type)) { return value.raw; } + if (comptime helper.isTypedArray(value_type)) { + return value.raw; + } if (comptime helper.isThreadSafeFunction(value_type)) { @compileError("ThreadSafeFunction is not supported for to_napi_value"); } + if (comptime helper.isDataView(value_type)) { + return value.raw; + } 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/array.zig b/src/napi/value/array.zig index 47406b9..88ff662 100644 --- a/src/napi/value/array.zig +++ b/src/napi/value/array.zig @@ -6,6 +6,7 @@ const helper = @import("../util/helper.zig"); const ArrayList = std.ArrayList; const NapiError = @import("../wrapper/error.zig"); const GlobalAllocator = @import("../util/allocator.zig"); +const typedarray = @import("../wrapper/typedarray.zig"); pub const Array = struct { env: napi.napi_env, @@ -22,6 +23,12 @@ pub const Array = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { const infos = @typeInfo(T); + var is_typedarray = false; + _ = napi.napi_is_typedarray(env, raw, &is_typedarray); + + if (is_typedarray and comptime supports_typedarray_target(T)) { + return from_typedarray_value(env, raw, T); + } switch (infos) { .array => { @@ -89,6 +96,145 @@ pub const Array = struct { } } + fn numericCast(comptime Dst: type, value: anytype) Dst { + const dst_info = @typeInfo(Dst); + const src_info = @typeInfo(@TypeOf(value)); + + return switch (dst_info) { + .int => switch (src_info) { + .int => @intCast(value), + .float => @intFromFloat(value), + else => @compileError("Unsupported typed array destination type: " ++ @typeName(Dst)), + }, + .float => switch (src_info) { + .int => @floatFromInt(value), + .float => @floatCast(value), + else => @compileError("Unsupported typed array destination type: " ++ @typeName(Dst)), + }, + else => @compileError("Unsupported typed array destination type: " ++ @typeName(Dst)), + }; + } + + fn supports_typedarray_target(comptime T: type) bool { + const infos = @typeInfo(T); + switch (infos) { + .array => |arr| return typedarray.isSupportedElementType(arr.child), + .pointer => |ptr| return helper.isSlice(T) and typedarray.isSupportedElementType(ptr.child), + .@"struct" => { + if (helper.isArrayList(T)) { + return typedarray.isSupportedElementType(helper.getArrayListElementType(T)); + } + return false; + }, + else => return false, + } + } + + fn fillFromTypedArray(comptime Dst: type, out: []Dst, raw_type: napi.napi_typedarray_type, data: ?*anyopaque, len: usize) void { + switch (raw_type) { + napi.napi_int8_array => { + const source: []const i8 = if (len == 0 or data == null) &[_]i8{} else @as([*]const i8, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_uint8_array, napi.napi_uint8_clamped_array => { + const source: []const u8 = if (len == 0 or data == null) &[_]u8{} else @as([*]const u8, @ptrCast(data))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_int16_array => { + const source: []const i16 = if (len == 0 or data == null) &[_]i16{} else @as([*]const i16, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_uint16_array => { + const source: []const u16 = if (len == 0 or data == null) &[_]u16{} else @as([*]const u16, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_int32_array => { + const source: []const i32 = if (len == 0 or data == null) &[_]i32{} else @as([*]const i32, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_uint32_array => { + const source: []const u32 = if (len == 0 or data == null) &[_]u32{} else @as([*]const u32, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_float32_array => { + const source: []const f32 = if (len == 0 or data == null) &[_]f32{} else @as([*]const f32, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_float64_array => { + const source: []const f64 = if (len == 0 or data == null) &[_]f64{} else @as([*]const f64, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_bigint64_array => { + const source: []const i64 = if (len == 0 or data == null) &[_]i64{} else @as([*]const i64, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + napi.napi_biguint64_array => { + const source: []const u64 = if (len == 0 or data == null) &[_]u64{} else @as([*]const u64, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + }, + else => unreachable, + } + } + + fn from_typedarray_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { + var raw_type: napi.napi_typedarray_type = undefined; + var len: usize = 0; + var data: ?*anyopaque = null; + var arraybuffer: napi.napi_value = undefined; + var byte_offset: usize = 0; + + _ = napi.napi_get_typedarray_info(env, raw, &raw_type, &len, &data, &arraybuffer, &byte_offset); + + const infos = @typeInfo(T); + + switch (infos) { + .array => |arr| { + if (!comptime typedarray.isSupportedElementType(arr.child)) { + @compileError("TypedArray only supports numeric array targets, got: " ++ @typeName(T)); + } + + var result: T = std.mem.zeroes(T); + const copy_len = @min(len, arr.len); + fillFromTypedArray(arr.child, result[0..copy_len], raw_type, data, copy_len); + return result; + }, + .pointer => |ptr| { + if (!comptime helper.isSlice(T)) { + @compileError("TypedArray only supports slice targets, got: " ++ @typeName(T)); + } + if (!comptime typedarray.isSupportedElementType(ptr.child)) { + @compileError("TypedArray only supports numeric slice targets, got: " ++ @typeName(T)); + } + + const allocator = GlobalAllocator.globalAllocator(); + const buf = allocator.alloc(ptr.child, len) catch @panic("OOM"); + fillFromTypedArray(ptr.child, buf, raw_type, data, len); + return buf; + }, + .@"struct" => { + if (comptime helper.isArrayList(T)) { + const child = comptime helper.getArrayListElementType(T); + if (!comptime typedarray.isSupportedElementType(child)) { + @compileError("TypedArray only supports numeric ArrayList targets, got: " ++ @typeName(T)); + } + + const allocator = GlobalAllocator.globalAllocator(); + var result: T = ArrayList(child).empty; + result.ensureTotalCapacity(allocator, len) catch @panic("OOM"); + const items = allocator.alloc(child, len) catch @panic("OOM"); + defer allocator.free(items); + fillFromTypedArray(child, items, raw_type, data, len); + for (items) |item| { + result.append(allocator, item) catch @panic("OOM"); + } + return result; + } + @compileError("TypedArray only supports array, slice, and ArrayList targets, got: " ++ @typeName(T)); + }, + else => @compileError("TypedArray only supports array, slice, and ArrayList targets, got: " ++ @typeName(T)), + } + } + pub fn New(env: Env, array: anytype) !Array { const array_type = @TypeOf(array); const infos = @typeInfo(array_type); diff --git a/src/napi/wrapper/dataview.zig b/src/napi/wrapper/dataview.zig new file mode 100644 index 0000000..bbaba3d --- /dev/null +++ b/src/napi/wrapper/dataview.zig @@ -0,0 +1,240 @@ +const std = @import("std"); +const napi = @import("napi-sys").napi_sys; +const Env = @import("../env.zig").Env; +const ArrayBuffer = @import("./arraybuffer.zig").ArrayBuffer; +const NapiError = @import("./error.zig"); +const Endian = std.builtin.Endian; + +pub const DataView = struct { + pub const is_napi_dataview = true; + + env: napi.napi_env, + raw: napi.napi_value, + data: [*]u8, + byte_length: usize, + byte_offset: usize, + arraybuffer: ArrayBuffer, + + pub fn from_raw(env: napi.napi_env, raw: napi.napi_value) DataView { + var byte_length: usize = 0; + var data: ?*anyopaque = null; + var arraybuffer_raw: napi.napi_value = undefined; + var byte_offset: usize = 0; + + _ = napi.napi_get_dataview_info( + env, + raw, + &byte_length, + &data, + &arraybuffer_raw, + &byte_offset, + ); + + return DataView{ + .env = env, + .raw = raw, + .data = if (byte_length == 0 or data == null) &[_]u8{} else @ptrCast(data), + .byte_length = byte_length, + .byte_offset = byte_offset, + .arraybuffer = ArrayBuffer.from_raw(env, arraybuffer_raw), + }; + } + + pub fn fromArrayBuffer(env: Env, arraybuffer: ArrayBuffer, byte_offset: usize, byte_length: usize) !DataView { + if (byte_offset + byte_length > arraybuffer.length()) { + return NapiError.Error.fromStatus(NapiError.Status.InvalidArg); + } + + var raw: napi.napi_value = undefined; + const status = napi.napi_create_dataview( + env.raw, + byte_length, + arraybuffer.raw, + byte_offset, + &raw, + ); + + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + 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); + } + + pub fn copy(env: Env, data: []const u8) !DataView { + const arraybuffer = try ArrayBuffer.copy(env, data); + 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]; + } + + pub fn asConstSlice(self: DataView) []const u8 { + return self.data[0..self.byte_length]; + } + + pub fn byteLength(self: DataView) usize { + return self.byte_length; + } + + fn endianOf(little_endian: bool) Endian { + return if (little_endian) .little else .big; + } + + fn ensureRange(self: DataView, byte_offset: usize, len: usize) !void { + if (byte_offset > self.byte_length or len > self.byte_length - byte_offset) { + return NapiError.Error.rangeError("DataView offset is out of bounds"); + } + } + + fn bytesAt(self: DataView, byte_offset: usize, len: usize) ![]u8 { + try self.ensureRange(byte_offset, len); + return self.asSlice()[byte_offset .. byte_offset + len]; + } + + pub fn readInt(self: DataView, comptime T: type, byte_offset: usize, little_endian: bool) !T { + const info = @typeInfo(T); + if (info != .int) { + @compileError("readInt only supports integer types"); + } + + const bytes = try self.bytesAt(byte_offset, @sizeOf(T)); + const fixed: *const [@sizeOf(T)]u8 = @ptrCast(bytes.ptr); + return std.mem.readInt(T, fixed, endianOf(little_endian)); + } + + pub fn writeInt(self: DataView, comptime T: type, byte_offset: usize, value: T, little_endian: bool) !void { + const info = @typeInfo(T); + if (info != .int) { + @compileError("writeInt only supports integer types"); + } + + const bytes = try self.bytesAt(byte_offset, @sizeOf(T)); + const fixed: *[@sizeOf(T)]u8 = @ptrCast(bytes.ptr); + std.mem.writeInt(T, fixed, value, endianOf(little_endian)); + } + + pub fn readFloat(self: DataView, comptime T: type, byte_offset: usize, little_endian: bool) !T { + const info = @typeInfo(T); + if (info != .float) { + @compileError("readFloat only supports floating-point types"); + } + + const Bits = std.meta.Int(.unsigned, @bitSizeOf(T)); + const bits = try self.readInt(Bits, byte_offset, little_endian); + return @bitCast(bits); + } + + pub fn writeFloat(self: DataView, comptime T: type, byte_offset: usize, value: T, little_endian: bool) !void { + const info = @typeInfo(T); + if (info != .float) { + @compileError("writeFloat only supports floating-point types"); + } + + const Bits = std.meta.Int(.unsigned, @bitSizeOf(T)); + try self.writeInt(Bits, byte_offset, @bitCast(value), little_endian); + } + + pub fn getInt8(self: DataView, byte_offset: usize) !i8 { + return self.readInt(i8, byte_offset, true); + } + + pub fn getUint8(self: DataView, byte_offset: usize) !u8 { + return self.readInt(u8, byte_offset, true); + } + + pub fn getInt16(self: DataView, byte_offset: usize, little_endian: bool) !i16 { + return self.readInt(i16, byte_offset, little_endian); + } + + pub fn getUint16(self: DataView, byte_offset: usize, little_endian: bool) !u16 { + return self.readInt(u16, byte_offset, little_endian); + } + + pub fn getInt32(self: DataView, byte_offset: usize, little_endian: bool) !i32 { + return self.readInt(i32, byte_offset, little_endian); + } + + pub fn getUint32(self: DataView, byte_offset: usize, little_endian: bool) !u32 { + return self.readInt(u32, byte_offset, little_endian); + } + + pub fn getBigInt64(self: DataView, byte_offset: usize, little_endian: bool) !i64 { + return self.readInt(i64, byte_offset, little_endian); + } + + pub fn getBigUint64(self: DataView, byte_offset: usize, little_endian: bool) !u64 { + return self.readInt(u64, byte_offset, little_endian); + } + + pub fn getFloat32(self: DataView, byte_offset: usize, little_endian: bool) !f32 { + return self.readFloat(f32, byte_offset, little_endian); + } + + pub fn getFloat64(self: DataView, byte_offset: usize, little_endian: bool) !f64 { + return self.readFloat(f64, byte_offset, little_endian); + } + + pub fn setInt8(self: DataView, byte_offset: usize, value: i8) !void { + try self.writeInt(i8, byte_offset, value, true); + } + + pub fn setUint8(self: DataView, byte_offset: usize, value: u8) !void { + try self.writeInt(u8, byte_offset, value, true); + } + + pub fn setInt16(self: DataView, byte_offset: usize, value: i16, little_endian: bool) !void { + try self.writeInt(i16, byte_offset, value, little_endian); + } + + pub fn setUint16(self: DataView, byte_offset: usize, value: u16, little_endian: bool) !void { + try self.writeInt(u16, byte_offset, value, little_endian); + } + + pub fn setInt32(self: DataView, byte_offset: usize, value: i32, little_endian: bool) !void { + try self.writeInt(i32, byte_offset, value, little_endian); + } + + pub fn setUint32(self: DataView, byte_offset: usize, value: u32, little_endian: bool) !void { + try self.writeInt(u32, byte_offset, value, little_endian); + } + + pub fn setBigInt64(self: DataView, byte_offset: usize, value: i64, little_endian: bool) !void { + try self.writeInt(i64, byte_offset, value, little_endian); + } + + pub fn setBigUint64(self: DataView, byte_offset: usize, value: u64, little_endian: bool) !void { + try self.writeInt(u64, byte_offset, value, little_endian); + } + + pub fn setFloat32(self: DataView, byte_offset: usize, value: f32, little_endian: bool) !void { + try self.writeFloat(f32, byte_offset, value, little_endian); + } + + pub fn setFloat64(self: DataView, byte_offset: usize, value: f64, little_endian: bool) !void { + try self.writeFloat(f64, byte_offset, value, little_endian); + } +}; diff --git a/src/napi/wrapper/typedarray.zig b/src/napi/wrapper/typedarray.zig new file mode 100644 index 0000000..dd29a63 --- /dev/null +++ b/src/napi/wrapper/typedarray.zig @@ -0,0 +1,163 @@ +const std = @import("std"); +const napi = @import("napi-sys").napi_sys; +const Env = @import("../env.zig").Env; +const ArrayBuffer = @import("./arraybuffer.zig").ArrayBuffer; +const NapiError = @import("./error.zig"); + +pub fn isSupportedElementType(comptime T: type) bool { + return switch (T) { + i8, u8, i16, u16, i32, u32, f32, f64, i64, u64 => true, + else => false, + }; +} + +pub fn defaultTypeFor(comptime T: type) napi.napi_typedarray_type { + return switch (T) { + i8 => napi.napi_int8_array, + u8 => napi.napi_uint8_array, + i16 => napi.napi_int16_array, + u16 => napi.napi_uint16_array, + i32 => napi.napi_int32_array, + u32 => napi.napi_uint32_array, + f32 => napi.napi_float32_array, + f64 => napi.napi_float64_array, + i64 => napi.napi_bigint64_array, + u64 => napi.napi_biguint64_array, + else => @compileError("Unsupported TypedArray element type: " ++ @typeName(T)), + }; +} + +fn validateElementType(comptime T: type) void { + if (!comptime isSupportedElementType(T)) { + @compileError("Unsupported TypedArray element type: " ++ @typeName(T)); + } +} + +pub fn TypedArray(comptime T: type) type { + validateElementType(T); + + return struct { + pub const is_napi_typedarray = true; + pub const element_type = T; + + env: napi.napi_env, + raw: napi.napi_value, + data: [*]T, + len: usize, + typedarray_type: napi.napi_typedarray_type, + byte_offset: usize, + arraybuffer: ArrayBuffer, + + const Self = @This(); + + pub fn from_raw(env: napi.napi_env, raw: napi.napi_value) Self { + var typedarray_type: napi.napi_typedarray_type = undefined; + var len: usize = 0; + var data: ?*anyopaque = null; + var arraybuffer_raw: napi.napi_value = undefined; + var byte_offset: usize = 0; + + _ = napi.napi_get_typedarray_info( + env, + raw, + &typedarray_type, + &len, + &data, + &arraybuffer_raw, + &byte_offset, + ); + + return Self{ + .env = env, + .raw = raw, + .data = if (len == 0 or data == null) &[_]T{} else @ptrCast(@alignCast(data)), + .len = len, + .typedarray_type = typedarray_type, + .byte_offset = byte_offset, + .arraybuffer = ArrayBuffer.from_raw(env, arraybuffer_raw), + }; + } + + pub fn fromArrayBuffer(env: Env, arraybuffer: ArrayBuffer, len: usize, byte_offset: usize) !Self { + if (byte_offset % @sizeOf(T) != 0) { + return NapiError.Error.fromStatus(NapiError.Status.InvalidArg); + } + + const byte_length = len * @sizeOf(T); + if (byte_offset + byte_length > arraybuffer.length()) { + return NapiError.Error.fromStatus(NapiError.Status.InvalidArg); + } + + var raw: napi.napi_value = undefined; + const status = napi.napi_create_typedarray( + env.raw, + defaultTypeFor(T), + len, + arraybuffer.raw, + byte_offset, + &raw, + ); + + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + 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); + } + + pub fn copy(env: Env, data: []const T) !Self { + var result = try Self.New(env, data.len); + @memcpy(result.asSlice(), data); + 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]; + } + + pub fn asConstSlice(self: Self) []const T { + return self.data[0..self.len]; + } + + pub fn length(self: Self) usize { + return self.len; + } + + pub fn byteLength(self: Self) usize { + return self.len * @sizeOf(T); + } + }; +} + +pub const Int8Array = TypedArray(i8); +pub const Uint8Array = TypedArray(u8); +pub const Int16Array = TypedArray(i16); +pub const Uint16Array = TypedArray(u16); +pub const Int32Array = TypedArray(i32); +pub const Uint32Array = TypedArray(u32); +pub const Float32Array = TypedArray(f32); +pub const Float64Array = TypedArray(f64); +pub const BigInt64Array = TypedArray(i64); +pub const BigUint64Array = TypedArray(u64);