From 673b59d31995091e83037ab53234778bcefb1d21 Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Tue, 19 Dec 2017 02:51:45 +0900 Subject: [PATCH] src: Initial implementation of DataView class This is an initial implementation of DataView class. This change includes the following things: - DataView::New() methods - Getters for DataView (ArrayBuffer(), ByteOffset(), ByteLength()) - Tests for them PR-URL: https://github.com/nodejs/node-addon-api/pull/205 Reviewed-By: Michael Dawson Reviewed-By: Kyle Farnung --- napi-inl.h | 82 +++++++++++++++++++++++++++++++++++++++ napi.h | 30 ++++++++++++++ test/binding.cc | 2 + test/binding.gyp | 1 + test/dataview/dataview.cc | 46 ++++++++++++++++++++++ test/dataview/dataview.js | 38 ++++++++++++++++++ test/index.js | 1 + 7 files changed, 200 insertions(+) create mode 100644 test/dataview/dataview.cc create mode 100644 test/dataview/dataview.js diff --git a/napi-inl.h b/napi-inl.h index b5724682a..e84ca7896 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1171,6 +1171,88 @@ inline void ArrayBuffer::EnsureInfo() const { } } +#if NAPI_DATA_VIEW_FEATURE +//////////////////////////////////////////////////////////////////////////////// +// DataView class +//////////////////////////////////////////////////////////////////////////////// +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, + "Start offset is outside the bounds of the buffer")); + return DataView(); + } + return New(env, arrayBuffer, byteOffset, + arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length")); + return DataView(); + } + napi_value value; + napi_status status = napi_create_dataview( + env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} + +inline DataView::DataView() : Object() { +} + +inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { +} + +inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_dataview_info( + _env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + &arrayBuffer /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +inline size_t DataView::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_dataview_info( + _env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + &byteOffset /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t DataView::ByteLength() const { + size_t byteLength; + napi_status status = napi_get_dataview_info( + _env, + _value /* dataView */, + &byteLength /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteLength; +} +#endif + //////////////////////////////////////////////////////////////////////////////// // TypedArray class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index ac508023d..3ec1fbfe2 100644 --- a/napi.h +++ b/napi.h @@ -778,6 +778,36 @@ namespace Napi { T* data); }; +#if NAPI_DATA_VIEW_FEATURE + /// The DataView provides a low-level interface for reading/writing multiple + /// number types in an ArrayBuffer irrespective of the platform's endianness. + class DataView : public Object { + public: + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); + + DataView(); ///< Creates a new _empty_ DataView instance. + DataView(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + // TODO: This class isn't a complete implementation yet, and will + // incrementally add additional methods to read/write data into buffer. + // Currently, this class is wrapped by the NAPI_DATA_VIEW_FEATURE macro flag + // and this should be enabled only in the tests until the implementation is + // completed. + }; +#endif + class Function : public Object { public: /// Callable must implement operator() accepting a const CallbackInfo& diff --git a/test/binding.cc b/test/binding.cc index 3f08d33a2..d7cf26432 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -7,6 +7,7 @@ Object InitAsyncWorker(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); Object InitBuffer(Env env); +Object InitDataView(Env env); Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); @@ -22,6 +23,7 @@ Object Init(Env env, Object exports) { exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); exports.Set("buffer", InitBuffer(env)); + exports.Set("dataview", InitDataView(env)); exports.Set("error", InitError(env)); exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 637db1e51..36de592c1 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -7,6 +7,7 @@ 'basic_types/value.cc', 'binding.cc', 'buffer.cc', + 'dataview/dataview.cc', 'error.cc', 'external.cc', 'function.cc', diff --git a/test/dataview/dataview.cc b/test/dataview/dataview.cc new file mode 100644 index 000000000..fced805f6 --- /dev/null +++ b/test/dataview/dataview.cc @@ -0,0 +1,46 @@ +#include "napi.h" + +using namespace Napi; + +static Value CreateDataView1(const CallbackInfo& info) { + ArrayBuffer arrayBuffer = info[0].As(); + return DataView::New(info.Env(), arrayBuffer); +} + +static Value CreateDataView2(const CallbackInfo& info) { + ArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset); +} + +static Value CreateDataView3(const CallbackInfo& info) { + ArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + size_t byteLength = info[2].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); +} + +static Value GetArrayBuffer(const CallbackInfo& info) { + return info[0].As().ArrayBuffer(); +} + +static Value GetByteOffset(const CallbackInfo& info) { + return Number::New(info.Env(), info[0].As().ByteOffset()); +} + +static Value GetByteLength(const CallbackInfo& info) { + return Number::New(info.Env(), info[0].As().ByteLength()); +} + +Object InitDataView(Env env) { + Object exports = Object::New(env); + + exports["createDataView1"] = Function::New(env, CreateDataView1); + exports["createDataView2"] = Function::New(env, CreateDataView2); + exports["createDataView3"] = Function::New(env, CreateDataView3); + exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer); + exports["getByteOffset"] = Function::New(env, GetByteOffset); + exports["getByteLength"] = Function::New(env, GetByteLength); + + return exports; +} diff --git a/test/dataview/dataview.js b/test/dataview/dataview.js new file mode 100644 index 000000000..4e3936457 --- /dev/null +++ b/test/dataview/dataview.js @@ -0,0 +1,38 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + function testDataViewCreation(factory, arrayBuffer, offset, length) { + const view = factory(arrayBuffer, offset, length); + offset = offset ? offset : 0; + assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); + assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + assert.strictEqual(dataview.getByteOffset(view), offset); + assert.strictEqual(dataview.getByteLength(view), + length ? length : arrayBuffer.byteLength - offset); + } + + function testInvalidRange(factory, arrayBuffer, offset, length) { + assert.throws(() => { + factory(arrayBuffer, offset, length); + }, RangeError); + } + + const dataview = binding.dataview; + const arrayBuffer = new ArrayBuffer(10); + + testDataViewCreation(dataview.createDataView1, arrayBuffer); + testDataViewCreation(dataview.createDataView2, arrayBuffer, 2); + testDataViewCreation(dataview.createDataView2, arrayBuffer, 10); + testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4); + testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0); + + testInvalidRange(dataview.createDataView2, arrayBuffer, 11); + testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0); + testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5); +} diff --git a/test/index.js b/test/index.js index e367ae57b..9482a115e 100644 --- a/test/index.js +++ b/test/index.js @@ -13,6 +13,7 @@ let testModules = [ 'basic_types/number', 'basic_types/value', 'buffer', + 'dataview/dataview', 'error', 'external', 'function',