|
| 1 | +# How to extend the OpenVINO™ JavaScript API code |
| 2 | + |
| 3 | +## Build the OpenVINO™ JavaScript API |
| 4 | + |
| 5 | +For detailed build instructions, refer to the [OpenVINO™ JavaScript API documentation](./README.md). |
| 6 | + |
| 7 | + |
| 8 | +## Project's naming conventions |
| 9 | + |
| 10 | +When implementing the C++ sources for the JavaScript API, it is essential to adhere to the OpenVINO naming conventions described in the [OpenVINO Coding Style Guide](../../../../docs/dev/coding_style.md). In summary, the naming style employs `Snake Case` for methods, functions, and variables, while `Camel Case` is used for class names. Additionally, the naming of entities in the C++ sources should closely mirror their equivalents in the C++ API to maintain consistency. |
| 11 | + |
| 12 | +For methods that are exposed to JavaScript, the naming convention transitions to `Camel Case`, aligning with common JavaScript practices. As an example, a method in the C++ API named `get_element_type` would be represented in the JavaScript API as `getElementType()`. |
| 13 | + |
| 14 | + |
| 15 | +## node-addon-api module |
| 16 | + |
| 17 | +[node addon api](https://github.com/nodejs/node-addon-api) is used to create OpenVINO JavaScript API for Node.js. The quickest way to learn is to follow the official [examples](https://github.com/nodejs/node-addon-examples). It is recommended to check out the tutorial on [how to create a JavaScript object from a C++ object](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api). |
| 18 | + |
| 19 | + |
| 20 | +## Adding a new class and method |
| 21 | + |
| 22 | +To introduce a new `MyTensor` class that interacts with the `ov::Tensor` class, follow these steps: |
| 23 | + - The class should facilitate construction from an ov::Tensor instance and allow initialization from a JavaScript element type and shape. |
| 24 | + - It should also provide a getElementType method that retrieves the ov::Tensor element type. |
| 25 | + |
| 26 | +Begin by creating a header file for the `MyTensor` class in the OpenVINO repository at `<openvino_repo>/src/bindings/js/node/include/my_tensor.hpp`. This file should contain the necessary includes and class definitions: |
| 27 | +```cpp |
| 28 | +class MyTensor : public Napi::ObjectWrap<MyTensor> { |
| 29 | +public: |
| 30 | + // Constructor for the wrapper class |
| 31 | + MyTensor(const Napi::CallbackInfo& info); |
| 32 | + |
| 33 | + // It returns a JavaScript class definition |
| 34 | + static Napi::Function get_class(Napi::Env env); |
| 35 | + |
| 36 | + // It returns the element type of ov::Tensor |
| 37 | + Napi::Value get_element_type(const Napi::CallbackInfo& info); |
| 38 | + |
| 39 | +private: |
| 40 | + ov::Tensor _tensor; |
| 41 | +}; |
| 42 | +``` |
| 43 | +The implementation of the class methods should be placed in a source file at `<openvino_repo>/src/bindings/js/node/src/my_tensor.cpp` |
| 44 | +```cpp |
| 45 | +MyTensor::MyTensor(const Napi::CallbackInfo& info) : Napi::ObjectWrap<MyTensor>(info) { |
| 46 | + std::vector<std::string> allowed_signatures; |
| 47 | +
|
| 48 | + try { |
| 49 | + if (ov::js::validate<Napi::String, Napi::Array>(info, allowed_signatures)) { |
| 50 | + const auto type = js_to_cpp<ov::element::Type_t>(info, 0); |
| 51 | + const auto& shape = js_to_cpp<ov::Shape>(info, 1); |
| 52 | + this->_tensor = ov::Tensor(type, shape); |
| 53 | + } else { |
| 54 | + OPENVINO_THROW("'MyTensor' constructor", ov::js::get_parameters_error_msg(info, allowed_signatures)); |
| 55 | + } |
| 56 | + } catch (std::exception& err) { |
| 57 | + reportError(info.Env(), err.what()); |
| 58 | + } |
| 59 | +} |
| 60 | +
|
| 61 | +Napi::Function MyTensor::get_class(Napi::Env env) { |
| 62 | + return DefineClass(env, |
| 63 | + "MyTensor", |
| 64 | + { |
| 65 | + InstanceMethod("getElementType", &MyTensor::get_element_type), |
| 66 | + }); |
| 67 | +} |
| 68 | +
|
| 69 | +Napi::Value MyTensor::get_element_type(const Napi::CallbackInfo& info) { |
| 70 | + return Napi::String::New(info.Env(), _tensor.get_element_type().to_string()); |
| 71 | +} |
| 72 | +``` |
| 73 | +Finally, update the `CMakeLists.txt` file at `<openvino_repo>/src/bindings/js/node/` to include the new source file in the build process: |
| 74 | +```cmake |
| 75 | +add_library(${PROJECT_NAME} SHARED |
| 76 | + ${CMAKE_CURRENT_SOURCE_DIR}/src/my_tensor.cpp |
| 77 | +) |
| 78 | +``` |
| 79 | + |
| 80 | + |
| 81 | +### Argument validation and conversion |
| 82 | + |
| 83 | +When binding JavaScript arguments with C++ functions, it is crucial to validate and convert the arguments appropriately. The template `ov::js::validate` function is a utility that facilitates this process. It is particularly useful for handling different overloads of functions and ensuring standardized error messages when arguments do not match expected signatures. |
| 84 | +Before implementing a new conversion function, such as `js_to_cpp<ov::Shape>`, review the existing [helper methods](../../node/include/helper.hpp) to see if one already meets your requirements. |
| 85 | + |
| 86 | + |
| 87 | +### New class initialization |
| 88 | + |
| 89 | +When a new class is introduced to the `openvino-node` module, it must be initialized upon module loading. This is done in the [addon.cpp](../../src/addon.cpp) file. The initialization process registers the class with the Node.js environment so that it can be used within JavaScript code. |
| 90 | +```cpp |
| 91 | +Napi::Object init_module(Napi::Env env, Napi::Object exports) { |
| 92 | + auto addon_data = new AddonData(); |
| 93 | + env.SetInstanceData<AddonData>(addon_data); |
| 94 | + init_class(env, exports, "MyTensor", &MyTensor::get_class, addon_data->my_tensor); |
| 95 | + |
| 96 | + return exports; |
| 97 | +} |
| 98 | +``` |
| 99 | +To keep track of the JavaScript class definitions, they are kept in `<openvino_repo>/src/bindings/js/node/include/addon.hpp`. |
| 100 | +```cpp |
| 101 | +struct AddonData { |
| 102 | + Napi::FunctionReference my_tensor; |
| 103 | + // and other class references |
| 104 | +}; |
| 105 | +``` |
| 106 | + |
| 107 | +### Document the new functionality |
| 108 | + |
| 109 | +The last step is to add the TypeScript type definitions and describe the new functionality. |
| 110 | +```typescript |
| 111 | +/** |
| 112 | + * The {@link MyTensor} class and its documentation. |
| 113 | + */ |
| 114 | +interface MyTensor { |
| 115 | + /** |
| 116 | + * It gets the tensor element type. |
| 117 | + */ |
| 118 | + getElementType(): element; |
| 119 | + |
| 120 | +} |
| 121 | +interface MyTensorConstructor { |
| 122 | + /** |
| 123 | + * It constructs a tensor using the element type and shape. The new tensor |
| 124 | + * data will be allocated by default. |
| 125 | + * @param type The element type of the new tensor. |
| 126 | + * @param shape The shape of the new tensor. |
| 127 | + */ |
| 128 | + new(type: element | elementTypeString, shape: number[]): MyTensor; |
| 129 | +} |
| 130 | + |
| 131 | +export interface NodeAddon { |
| 132 | + MyTensor: MyTensorConstructor, |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | + |
| 137 | +## Testing the new code |
| 138 | + |
| 139 | +Now that coding is finished, remember to rebuild the project and test it out. |
| 140 | + |
| 141 | +To learn how to test your code, refer to the guide on [how to test OpenVINO™ JavaScript API.](./test_examples.md) |
| 142 | + |
| 143 | +## See also |
| 144 | + * [OpenVINO™ README](../../../../README.md) |
| 145 | + * [OpenVINO™ bindings README](../../README.md) |
| 146 | + * [Developer documentation](../../../../docs/dev/index.md) |
0 commit comments