Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(better) support for typed arrays #287

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 13 additions & 5 deletions lib/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ function addFunction(object, info) {
define(object, getFunctionDescription(info))
}

function fieldGetter(fieldInfo) {
function fieldGetter(fieldInfo, lengthFieldInfo) {
return function () {
return internal.StructFieldGetter(this, fieldInfo);
return internal.StructFieldGetter(this, fieldInfo, lengthFieldInfo);
};
}

Expand All @@ -168,7 +168,7 @@ function fieldSetter(fieldInfo) {
};
}

function addField(object, fieldInfo) {
function addField(object, fieldInfo, lengthFieldInfo) {
const flags = GI.field_info_get_flags(fieldInfo)
const writable = (flags & GI.FieldInfoFlags.WRITABLE) !== 0
const readable = (flags & GI.FieldInfoFlags.READABLE) !== 0
Expand All @@ -178,7 +178,7 @@ function addField(object, fieldInfo) {
Object.defineProperty(object, name, {
configurable: true,
enumerable: readable,
get: readable ? fieldGetter(fieldInfo) : undefined,
get: readable ? fieldGetter(fieldInfo, lengthFieldInfo) : undefined,
set: writable ? fieldSetter(fieldInfo) : undefined
})
}
Expand Down Expand Up @@ -317,6 +317,13 @@ function makeUnion(info) {
return constructor
}

function getStructFieldLength(info, fieldInfo) {
const type = GI.field_info_get_type(fieldInfo);
const arrayLength = GI.type_info_get_array_length(type);
if (arrayLength !== -1)
return GI.struct_info_get_field(info, arrayLength);
}

function makeStruct(info) {
const constructor = internal.MakeBoxedClass(info);

Expand All @@ -329,7 +336,8 @@ function makeStruct(info) {
const nFields = GI.struct_info_get_n_fields(info);
for (let i = 0; i < nFields; i++) {
const fieldInfo = GI.struct_info_get_field(info, i);
addField(constructor.prototype, fieldInfo);
const lengthFieldInfo = getStructFieldLength(info, fieldInfo);
addField(constructor.prototype, fieldInfo, lengthFieldInfo);
}

return constructor
Expand Down
24 changes: 23 additions & 1 deletion src/gi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ NAN_METHOD(StructFieldSetter) {
NAN_METHOD(StructFieldGetter) {
Local<Object> jsBoxed = info[0].As<Object>();
Local<Object> jsFieldInfo = info[1].As<Object>();
Local<Object> jsLengthFieldInfo = info[2].As<Object>();

if (jsBoxed->InternalFieldCount() == 0) {
Nan::ThrowError("StructFieldGetter: instance is not a boxed");
Expand Down Expand Up @@ -286,6 +287,27 @@ NAN_METHOD(StructFieldGetter) {
bool mustCopy = true;
BaseInfo typeInfo = g_field_info_get_type(*fieldInfo);

long length = -1;
if (jsLengthFieldInfo->IsObject()) {
if (jsLengthFieldInfo->InternalFieldCount() == 0) {
Nan::ThrowError("StructFieldGetter: length field info is invalid");
return;
}
BaseInfo lengthFieldInfo =
g_base_info_ref((GIFieldInfo *) GNodeJS::PointerFromWrapper(jsLengthFieldInfo));
if (lengthFieldInfo.isEmpty()) {
Nan::ThrowError("StructFieldGetter: length field info is NULL");
return;
}
GIArgument lengthValue;
BaseInfo lengthTypeInfo = g_field_info_get_type(*lengthFieldInfo);
if (!g_field_info_get_field(*lengthFieldInfo, boxed, &lengthValue)) {
Nan::ThrowError("Length is not a primitive field");
return;
}
length = GNodeJS::GIArgumentToLength(*lengthTypeInfo, &lengthValue, false);
}

if (!g_field_info_get_field(*fieldInfo, boxed, &value)) {
/* If g_field_info_get_field() failed, this is a non-primitive type */

Expand All @@ -306,7 +328,7 @@ NAN_METHOD(StructFieldGetter) {
return;
}

RETURN(GNodeJS::GIArgumentToV8(*typeInfo, &value, -1, mustCopy));
RETURN(GNodeJS::GIArgumentToV8(*typeInfo, &value, length, mustCopy));
}

NAN_METHOD(StartLoop) {
Expand Down
148 changes: 127 additions & 21 deletions src/value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,19 +238,15 @@ Local<Value> GErrorToV8 (GITypeInfo *type_info, GError *err) {
return obj;
}

Local<Value> ArrayToV8 (GITypeInfo *type_info, void* data, long length) {

auto array = New<Array>();

if (data == nullptr || length == 0)
return array;

auto array_type = g_type_info_get_array_type (type_info);
auto item_type_info = g_type_info_get_param_type (type_info, 0);
auto item_size = GetTypeSize (item_type_info);
// auto item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
// Resolve an array of a given type, into its data and element length
// May also validate item_size for some types of array
static void ArrayGetDataAndLength (GITypeInfo *type_info, void*& data, long& length, long& item_size) {
if (data == nullptr) {
length = 0;
return;
}

switch (array_type) {
switch (g_type_info_get_array_type (type_info)) {
case GI_ARRAY_TYPE_C:
{
if (length == -1) {
Expand All @@ -275,30 +271,71 @@ Local<Value> ArrayToV8 (GITypeInfo *type_info, void* data, long length) {
GArray *g_array = (GArray*) data;
data = g_array->data;
length = g_array->len;
item_size = g_array_get_element_size (g_array);
auto element_size = g_array_get_element_size (g_array);
if (item_size != element_size)
g_critical ("Returned GArray has element-size %lu but element type has size %u", item_size, element_size);
break;
}
case GI_ARRAY_TYPE_PTR_ARRAY:
{
GPtrArray *ptr_array = (GPtrArray*) data;
data = ptr_array->pdata;
length = ptr_array->len;
item_size = sizeof(gpointer);
if (item_size != sizeof(gpointer))
g_critical ("Returned GPtrArray but element type has size %lu", item_size);
break;
}
default:
g_assert_not_reached();
break;
}
}

if (data == nullptr || length == 0)
goto out;
// FIXME: we could even avoid the memcpy for transfer=none and/or transfer=full
inline Local<v8::ArrayBuffer> MakeArrayBuffer (void* data, long byte_length) {
auto buf = v8::ArrayBuffer::New(Isolate::GetCurrent(), byte_length);
memcpy(buf->GetContents().Data(), data, byte_length);
return buf;
}

Local<Value> ArrayToV8 (GITypeInfo *type_info, void* data, long length) {
auto item_type_info = g_type_info_get_param_type (type_info, 0);
long item_size = GetTypeSize (item_type_info);
// auto item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;

ArrayGetDataAndLength(type_info, data, length, item_size);

// First try to convert into TypedArray

auto type_tag = g_type_info_get_tag (item_type_info);
auto byte_length = length * item_size;
Local<Value> typedarray =
// (type_tag == GI_TYPE_TAG_BOOLEAN) ? v8::Uint8Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_INT8) ? v8::Int8Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_UINT8) ? Nan::CopyBuffer((char*)data, byte_length).ToLocalChecked().As<v8::Value>() :
(type_tag == GI_TYPE_TAG_INT16) ? v8::Int16Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_UINT16) ? v8::Uint16Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_INT32) ? v8::Int32Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_UINT32) ? v8::Uint32Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_INT64) ? v8::BigInt64Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_UINT64) ? v8::BigUint64Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_FLOAT) ? v8::Float32Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_DOUBLE) ? v8::Float64Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
// special types:
(type_tag == GI_TYPE_TAG_GTYPE) ? v8::BigUint64Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
(type_tag == GI_TYPE_TAG_UNICHAR) ? v8::Uint32Array::New(MakeArrayBuffer(data, byte_length), 0, length).As<v8::Value>() :
Local<Value>(); // empty handle

if (!typedarray.IsEmpty()) {
g_base_info_unref(item_type_info);
return typedarray;
}

/*
* Fill array elements
*/

auto array = New<Array>();
GIArgument value;

for (int i = 0; i < length; i++) {
Expand All @@ -307,8 +344,6 @@ Local<Value> ArrayToV8 (GITypeInfo *type_info, void* data, long length) {
Nan::Set(array, i, GIArgumentToV8(item_type_info, &value));
}


out:
g_base_info_unref(item_type_info);
return array;
}
Expand Down Expand Up @@ -391,18 +426,51 @@ GArray * V8ToGArray(GITypeInfo *type_info, Local<Value> value) {
}
}

g_base_info_unref (element_info);

} else if (value->IsTypedArray ()) {
auto array = Local<TypedArray>::Cast (TO_OBJECT (value));
int length = array->Length ();

GITypeInfo* element_info = g_type_info_get_param_type (type_info, 0);
gsize element_size = GetTypeSize(element_info);

if (array->ByteLength() != length * element_size) { // FIXME hacky but cheap
// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
//Nan::ThrowTypeError("Typed array must have the same element size");
}

g_array = g_array_sized_new (zero_terminated, FALSE, element_size, length);
g_array_set_size(g_array, length);
array->CopyContents(g_array->data, length * element_size);

g_base_info_unref (element_info);
} else {
Nan::ThrowTypeError("Not an array.");
// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
//Nan::ThrowTypeError("Not an array.");
}

return g_array;
}

// FIXME: for transfer=none we could simply pass the typedarray's data
// and avoid even the memcpy, but this would be a breaking change
static void *V8ArrayToCArray(GITypeInfo *type_info, Local<Value> value) {
auto array = Local<Array>::Cast (TO_OBJECT (value));
int length = array->Length();

int fixed_size = g_type_info_get_array_fixed_size(type_info);
if (fixed_size != -1 && fixed_size != length) {
// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
//Nan::ThrowRangeError("Array not matching fixed size.");
}

bool isZeroTerminated = g_type_info_is_zero_terminated(type_info);
GITypeInfo* element_info = g_type_info_get_param_type (type_info, 0);
gsize element_size = GetTypeSize(element_info);
Expand Down Expand Up @@ -433,15 +501,30 @@ static void *V8ArrayToCArray(GITypeInfo *type_info, Local<Value> value) {

static void *V8TypedArrayToCArray(GITypeInfo *type_info, Local<Value> value) {
auto array = Local<TypedArray>::Cast (TO_OBJECT (value));
size_t length = array->ByteLength();
int length = array->Length();

int fixed_size = g_type_info_get_array_fixed_size(type_info);
if (fixed_size != -1 && fixed_size != length) {
// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
//Nan::ThrowRangeError("Array not matching fixed size.");
}

bool isZeroTerminated = g_type_info_is_zero_terminated(type_info);
GITypeInfo* element_info = g_type_info_get_param_type (type_info, 0);
gsize element_size = GetTypeSize(element_info);

if (array->ByteLength() != length * element_size) { // FIXME hacky but cheap
// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
//Nan::ThrowTypeError("Typed array must have the same element size");
}

void *result = malloc(element_size * (length + (isZeroTerminated ? 1 : 0)));

array->CopyContents(result, length);
array->CopyContents(result, length * element_size);

if (isZeroTerminated) {
void* pointer = (void*)((ulong)result + length * element_size);
Expand All @@ -468,6 +551,9 @@ void *V8ToCArray(GITypeInfo *type_info, Local<Value> value) {
return V8TypedArrayToCArray(type_info, value);
}

// FIXME: not currently working, exceptions seem to be disabled.
// for now it's better to abort() than to risk a crash from undefined behaviour
g_assert_not_reached();
Nan::ThrowTypeError("Expected value to be an array");

return NULL;
Expand Down Expand Up @@ -1330,6 +1416,8 @@ bool CanConvertV8ToGValue(GValue *gvalue, Local<Value> value) {
} else if (G_VALUE_HOLDS_OBJECT (gvalue)) {
return ValueIsInstanceOfGType(value, G_VALUE_TYPE (gvalue));
} else if (G_VALUE_HOLDS_BOXED (gvalue)) {
if (G_VALUE_TYPE (gvalue) == G_TYPE_BYTE_ARRAY)
return value->IsUint8Array();
return ValueIsInstanceOfGType(value, G_VALUE_TYPE (gvalue));
} else if (G_VALUE_HOLDS_PARAM (gvalue)) {
return value->IsObject();
Expand Down Expand Up @@ -1402,6 +1490,19 @@ bool V8ToGValue(GValue *gvalue, Local<Value> value, bool mustCopy) {
}
g_value_set_object (gvalue, GObjectFromWrapper (value));
} else if (G_VALUE_HOLDS_BOXED (gvalue)) {
if (G_VALUE_TYPE (gvalue) == G_TYPE_BYTE_ARRAY) {
if (!value->IsUint8Array()) {
Throw::CannotConvertGType("GByteArray", G_VALUE_TYPE (gvalue));
return false;
}
auto array = value.As<v8::Uint8Array>();
auto garray = g_byte_array_sized_new(array->ByteLength());
g_byte_array_set_size(garray, array->ByteLength());
array->CopyContents(garray->data, garray->len);
g_value_take_boxed(gvalue, garray);
return true;
}

if (!ValueIsInstanceOfGType(value, G_VALUE_TYPE (gvalue))) {
Throw::CannotConvertGType("boxed", G_VALUE_TYPE (gvalue));
return false;
Expand Down Expand Up @@ -1469,6 +1570,11 @@ Local<Value> GValueToV8(const GValue *gvalue, bool mustCopy) {
return WrapperFromGObject (G_OBJECT (g_value_get_object (gvalue)));
} else if (G_VALUE_HOLDS_BOXED (gvalue)) {
GType gtype = G_VALUE_TYPE (gvalue);
if (gtype == G_TYPE_BYTE_ARRAY) {
auto garray = (GByteArray*) g_value_get_boxed(gvalue);
return Nan::CopyBuffer((char*)garray->data, garray->len).ToLocalChecked();
}

GIBaseInfo *info = g_irepository_find_by_gtype(NULL, gtype);
if (info == NULL) {
Throw::InvalidGType(gtype);
Expand Down
25 changes: 19 additions & 6 deletions tests/conversion__array.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ describe('IN-array', () => {
assert(result === Buffer.from('hello').toString('base64'))
})

describe('IN-array (Uint8Array)', () => {
const data = Uint8Array.from([ 104, 101, 108, 108, 111 ]) // hello
const result = glib.base64Encode(data, data.length)
console.log('Result:', result)
assert(result === Buffer.from('hello').toString('base64'))
})

describe('IN-array (Int8Array)', () => {
const data = Int8Array.from([ 104, 101, 108, 108, 111 ]) // hello
const result = glib.base64Encode(data, data.length)
console.log('Result:', result)
assert(result === Buffer.from('hello').toString('base64'))
})

describe('OUT-array (array-length after)', () => {
const data = [ 104, 101, 108, 108, 111 ] // hello
const result = glib.computeChecksumForData(glib.ChecksumType.MD5, data)
Expand All @@ -29,14 +43,13 @@ describe('OUT-array (array-length after)', () => {

describe('OUT-array (array-length before)', () => {
const filepath = path.join(__dirname, 'lorem-ipsum.txt')
const result = glib.fileGetContents(filepath)
console.log('Result:', result)
const content = result[1].map(c => String.fromCharCode(c)).join('')
const actualContent = fs.readFileSync(filepath).toString()
const [ ok, content ] = glib.fileGetContents(filepath)
const actualContent = fs.readFileSync(filepath)

console.log([content, actualContent])
assert(result[0] === true, 'glib_file_get_contents failed')
assert(content === actualContent, 'file content is wrong')
assert(ok === true, 'glib_file_get_contents failed')
assert(content instanceof Uint8Array, 'did not return Uint8Array')
assert(actualContent.equals(content), 'file content is wrong')
})

describe('OUT-array (zero-terminated)', () => {
Expand Down
Loading