From 2ee7da1cd6117f3cb330f80eed230ac09cabcd0f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 7 Oct 2020 11:11:03 -0700 Subject: [PATCH] Implement a more efficient vecFromJSArray (#12463) This is a followup of #5519 and #5655 since they were closed. It is possible to implement emscripten::vecFromJSArray more efficiently for numeric arrays by using the optimized TypedArray.prototype.set function. The main issue with this method is that it will silently fail(or succeed) if elements of the array or not numbers, as it does not do any type checking but instead works as if it called the javascript Number() function for each element. (See ToNumber for more details) So instead of simply updating vecFromJSArray to use this new implementation and break code (since there's no typechecking anymore) I added a new convertJSArrayToNumberVector (name subject to change) and improved performance a tiny bit for vecFromJSArray by: * Taking the val parameter by const reference instead of copy * Reserving the storage of the vector --- AUTHORS | 1 + site/source/docs/api_reference/val.h.rst | 18 +++++++++---- system/include/emscripten/val.h | 28 ++++++++++++++++---- tests/embind/test_val.cpp | 33 ++++++++++++++++++++---- tests/embind/test_val.out | 15 ++++++++++- 5 files changed, 79 insertions(+), 16 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9caffe731..0a7162ac1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -512,3 +512,4 @@ a license to everyone to use it as detailed in LICENSE.) * Marat Dukhan (copyright owned by Google, LLC) * Stephan Reiter (copyright owned by Google, LLC) * kamenokonyokonyoko +* Lectem diff --git a/site/source/docs/api_reference/val.h.rst b/site/source/docs/api_reference/val.h.rst index f91328a9e..3111a4fac 100644 --- a/site/source/docs/api_reference/val.h.rst +++ b/site/source/docs/api_reference/val.h.rst @@ -246,14 +246,22 @@ Guide material for this class can be found in :ref:`embind-val-guide`. :returns: **HamishW**-Replace with description. - .. cpp:function:: std::vector vecFromJSArray(val v) + .. cpp:function:: std::vector vecFromJSArray(const val& v) - **HamishW**-Replace with description. + Copies a javascript array into a std::vector, checking the type of each element. + For a more efficient but unsafe version working with numbers, see convertJSObjectToNumberVector. - **HamishW**. I believe NOT internal. Please confirm. + :param val v: The javascript array to be copied + :returns: A std::vector made from the javascript array - :param val v: **HamishW**-Replace with description. - :returns: **HamishW**-Replace with description. + .. cpp:function:: std::vector convertJSArrayToNumberVector(const val& v) + + Converts a javascript object into a std::vector efficiently, as if using the javascript `Number()` function on each element. + This is way more efficient than vecFromJSArray on any array with more than 2 values, but is less safe. + No type checking is done, so any invalid array entry will silently be replaced by a NaN value (or 0 for interger types). + + :param val v: The javascript (typed) array to be copied + :returns: A std::vector made from the javascript array .. cpp:function:: val await() const diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index 347cc4e23..272046fad 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -580,15 +580,33 @@ namespace emscripten { }; } - template - std::vector vecFromJSArray(val v) { - auto l = v["length"].as(); + template + std::vector vecFromJSArray(const val& v) { + const size_t l = v["length"].as(); std::vector rv; - for(unsigned i = 0; i < l; ++i) { + rv.reserve(l); + for (size_t i = 0; i < l; ++i) { rv.push_back(v[i].as()); } return rv; - }; + } + + template + std::vector convertJSArrayToNumberVector(const val& v) { + const size_t l = v["length"].as(); + + std::vector rv; + rv.resize(l); + + // Copy the array into our vector through the use of typed arrays. + // It will try to convert each element through Number(). + // See https://www.ecma-international.org/ecma-262/6.0/#sec-%typedarray%.prototype.set-array-offset + // and https://www.ecma-international.org/ecma-262/6.0/#sec-tonumber + val memoryView{ typed_memory_view(l, rv.data()) }; + memoryView.call("set", v); + + return rv; + } } diff --git a/tests/embind/test_val.cpp b/tests/embind/test_val.cpp index 32dfbd943..59806c41c 100644 --- a/tests/embind/test_val.cpp +++ b/tests/embind/test_val.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -570,7 +571,7 @@ int main() ensure_js("test_val_throw_(new TypeError('message'))"); // this test should probably go elsewhere as it is not a member of val - test("template std::vector vecFromJSArray(val v)"); + test("template std::vector vecFromJSArray(const val& v)"); EM_ASM( // can't declare like this because i get: // error: expected ')' @@ -578,11 +579,33 @@ int main() //a = [1, '2']; a = []; a[0] = 1; - a[1] = 'b'; + a[1] = '42'; + a[2] = 'b'; + a[3] = new Date(100000); ); - ensure(vecFromJSArray(val::global("a")).at(0).as() == 1); - ensure(vecFromJSArray(val::global("a")).at(1).as() == "b"); - ensure(vecFromJSArray(val::global("a")).size() == 2); + const std::vector& aAsArray = vecFromJSArray(val::global("a")); + ensure(aAsArray.at(0).as() == 1); + ensure(aAsArray.at(1).as() == "42"); + ensure(aAsArray.at(2).as() == "b"); + ensure(aAsArray.size() == 4); + + test("template std::vector convertJSArrayToNumberVector(const val& v)"); + + const std::vector& aAsNumberVectorFloat = convertJSArrayToNumberVector(val::global("a")); + ensure(aAsNumberVectorFloat.size() == 4); + + ensure(aAsNumberVectorFloat.at(0) == 1.f); + ensure(aAsNumberVectorFloat.at(1) == 42.f); // String containing numbers are converted correctly + ensure(std::isnan(aAsNumberVectorFloat.at(2))); // NaN returned if can not be converted for floats + ensure(aAsNumberVectorFloat.at(3) == 100000.f); // Date returns milliseconds since epoch + + const std::vector& aAsNumberVectorUint32_t = convertJSArrayToNumberVector(val::global("a")); + ensure(aAsNumberVectorUint32_t.size() == 4); + + ensure(aAsNumberVectorUint32_t.at(0) == 1); + ensure(aAsNumberVectorUint32_t.at(1) == 42); // String containing numbers are converted correctly + ensure(aAsNumberVectorUint32_t.at(2) == 0); // 0 is returned if can not be converted for integers + ensure(aAsNumberVectorUint32_t.at(3) == 100000); // Date returns milliseconds since epoch printf("end\n"); return 0; diff --git a/tests/embind/test_val.out b/tests/embind/test_val.out index a0a88d9d0..6aa5d6ece 100644 --- a/tests/embind/test_val.out +++ b/tests/embind/test_val.out @@ -244,7 +244,20 @@ pass pass pass test: -template std::vector vecFromJSArray(val v) +template std::vector vecFromJSArray(const val& v) +pass +pass +pass +pass +test: +template std::vector convertJSArrayToNumberVector(const val& v) +pass +pass +pass +pass +pass +pass +pass pass pass pass