Skip to content

Commit

Permalink
src: add Addon<T> class
Browse files Browse the repository at this point in the history
* separate out instance-related APIs from `ObjectWrap<T>` into a new
  class `InstanceWrap<T>` which then becomes a base class for
  `ObjectWrap<T>`.
* Expose new `DefineProperties` overload on `Object` which accepts a
  list of `napi_property_descriptor` and implement the other overloads
  as a call to it.
* Add `Addon<T>` class as a subclass of `InstanceWrap<T>`.
* Add macros `NODE_API_ADDON()` and `NODE_API_NAMED_ADDON()` to load an
  add-on from its `Addon<T>` subclass definition.

Bindings created like this perform slightly worse than static ones in
exchange for the benefit of having the context of a class instance as
their C++ `this` object. Static bindings can still be created and
associated with the `exports` object and they can use
`Napi::Env::GetInstanceData()` to retrieve the add-on instance.
  • Loading branch information
Gabriel Schulhof committed Jun 23, 2020
1 parent 4c01af2 commit 8a779a3
Show file tree
Hide file tree
Showing 12 changed files with 1,294 additions and 576 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ The oldest Node.js version supported by the current version of node-addon-api is

The following is the documentation for node-addon-api.

- [Addon Structure](doc/addon.md)
- [Basic Types](doc/basic_types.md)
- [Array](doc/basic_types.md#array)
- [Symbol](doc/symbol.md)
Expand Down
201 changes: 129 additions & 72 deletions benchmark/function_args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,76 +78,133 @@ static void FourArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv3 = info[3]; (void) argv3;
}

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
napi_value no_arg_function, one_arg_function, two_arg_function,
three_arg_function, four_arg_function;
napi_status status;

status = napi_create_function(env,
"noArgFunction",
NAPI_AUTO_LENGTH,
NoArgFunction_Core,
nullptr,
&no_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"oneArgFunction",
NAPI_AUTO_LENGTH,
OneArgFunction_Core,
nullptr,
&one_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"twoArgFunction",
NAPI_AUTO_LENGTH,
TwoArgFunction_Core,
nullptr,
&two_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"threeArgFunction",
NAPI_AUTO_LENGTH,
ThreeArgFunction_Core,
nullptr,
&three_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"fourArgFunction",
NAPI_AUTO_LENGTH,
FourArgFunction_Core,
nullptr,
&four_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

Napi::Object core = Napi::Object::New(env);
core["noArgFunction"] = Napi::Value(env, no_arg_function);
core["oneArgFunction"] = Napi::Value(env, one_arg_function);
core["twoArgFunction"] = Napi::Value(env, two_arg_function);
core["threeArgFunction"] = Napi::Value(env, three_arg_function);
core["fourArgFunction"] = Napi::Value(env, four_arg_function);
exports["core"] = core;

Napi::Object cplusplus = Napi::Object::New(env);
cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
exports["cplusplus"] = cplusplus;

Napi::Object templated = Napi::Object::New(env);
templated["noArgFunction"] = Napi::Function::New<NoArgFunction>(env);
templated["oneArgFunction"] = Napi::Function::New<OneArgFunction>(env);
templated["twoArgFunction"] = Napi::Function::New<TwoArgFunction>(env);
templated["threeArgFunction"] = Napi::Function::New<ThreeArgFunction>(env);
templated["fourArgFunction"] = Napi::Function::New<FourArgFunction>(env);
exports["templated"] = templated;

return exports;
}
class FunctionArgs : public Napi::Addon<FunctionArgs> {
public:
FunctionArgs(Napi::Env env, Napi::Object exports) {
napi_value no_arg_function, one_arg_function, two_arg_function,
three_arg_function, four_arg_function;
napi_status status;

status = napi_create_function(env,
"noArgFunction",
NAPI_AUTO_LENGTH,
NoArgFunction_Core,
nullptr,
&no_arg_function);
NAPI_THROW_IF_FAILED_VOID(env, status);

status = napi_create_function(env,
"oneArgFunction",
NAPI_AUTO_LENGTH,
OneArgFunction_Core,
nullptr,
&one_arg_function);
NAPI_THROW_IF_FAILED_VOID(env, status);

status = napi_create_function(env,
"twoArgFunction",
NAPI_AUTO_LENGTH,
TwoArgFunction_Core,
nullptr,
&two_arg_function);
NAPI_THROW_IF_FAILED_VOID(env, status);

status = napi_create_function(env,
"threeArgFunction",
NAPI_AUTO_LENGTH,
ThreeArgFunction_Core,
nullptr,
&three_arg_function);
NAPI_THROW_IF_FAILED_VOID(env, status);

status = napi_create_function(env,
"fourArgFunction",
NAPI_AUTO_LENGTH,
FourArgFunction_Core,
nullptr,
&four_arg_function);
NAPI_THROW_IF_FAILED_VOID(env, status);

Napi::Object core = Napi::Object::New(env);
core["noArgFunction"] = Napi::Value(env, no_arg_function);
core["oneArgFunction"] = Napi::Value(env, one_arg_function);
core["twoArgFunction"] = Napi::Value(env, two_arg_function);
core["threeArgFunction"] = Napi::Value(env, three_arg_function);
core["fourArgFunction"] = Napi::Value(env, four_arg_function);
exports["core"] = core;

Napi::Object cplusplus = Napi::Object::New(env);
cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
exports["cplusplus"] = cplusplus;

Napi::Object templated = Napi::Object::New(env);
templated["noArgFunction"] = Napi::Function::New<NoArgFunction>(env);
templated["oneArgFunction"] = Napi::Function::New<OneArgFunction>(env);
templated["twoArgFunction"] = Napi::Function::New<TwoArgFunction>(env);
templated["threeArgFunction"] = Napi::Function::New<ThreeArgFunction>(env);
templated["fourArgFunction"] = Napi::Function::New<FourArgFunction>(env);
exports["templated"] = templated;

DefineAddon(exports, {
InstanceMethod("noArgFunction_inst", &FunctionArgs::InstNoArgFunction),
InstanceMethod("oneArgFunction_inst", &FunctionArgs::InstOneArgFunction),
InstanceMethod("twoArgFunction_inst", &FunctionArgs::InstTwoArgFunction),
InstanceMethod("threeArgFunction_inst", &FunctionArgs::InstThreeArgFunction),
InstanceMethod("fourArgFunction_inst", &FunctionArgs::InstFourArgFunction),
InstanceMethod<&FunctionArgs::InstNoArgFunction>("noArgFunction_intpl"),
InstanceMethod<&FunctionArgs::InstOneArgFunction>("oneArgFunction_intpl"),
InstanceMethod<&FunctionArgs::InstTwoArgFunction>("twoArgFunction_intpl"),
InstanceMethod<&FunctionArgs::InstThreeArgFunction>("threeArgFunction_intpl"),
InstanceMethod<&FunctionArgs::InstFourArgFunction>("fourArgFunction_intpl"),
});

Napi::Object inst = Napi::Object::New(env);
inst["noArgFunction"] = exports.Get("noArgFunction_inst");
inst["oneArgFunction"] = exports.Get("oneArgFunction_inst");
inst["twoArgFunction"] = exports.Get("twoArgFunction_inst");
inst["threeArgFunction"] = exports.Get("threeArgFunction_inst");
inst["fourArgFunction"] = exports.Get("fourArgFunction_inst");
exports["instance"] = inst;

Napi::Object intpl = Napi::Object::New(env);
intpl["noArgFunction"] = exports.Get("noArgFunction_intpl");
intpl["oneArgFunction"] = exports.Get("oneArgFunction_intpl");
intpl["twoArgFunction"] = exports.Get("twoArgFunction_intpl");
intpl["threeArgFunction"] = exports.Get("threeArgFunction_intpl");
intpl["fourArgFunction"] = exports.Get("fourArgFunction_intpl");
exports["instance_templated"] = intpl;
}

private:
void InstNoArgFunction(const Napi::CallbackInfo& info) {
(void) info;
}

void InstOneArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
}

void InstTwoArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
}

void InstThreeArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
Napi::Value argv2 = info[2]; (void) argv2;
}

void InstFourArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
Napi::Value argv2 = info[2]; (void) argv2;
Napi::Value argv3 = info[3]; (void) argv3;
}
};

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
NODE_API_ADDON(FunctionArgs)
14 changes: 9 additions & 5 deletions benchmark/function_args.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,50 @@ const addonName = path.basename(__filename, '.js');
.forEach((addonName) => {
const rootAddon = require(`./build/Release/${addonName}`);
const implems = Object.keys(rootAddon);
const maxNameLength =
implems.reduce((soFar, value) => Math.max(soFar, value.length), 0);
const anObject = {};

console.log(`${addonName}: `);

console.log('no arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].noArgFunction;
return suite.add(implem, () => fn());
return suite.add(implem.padStart(maxNameLength, ' '), () => fn());
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('one argument:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].oneArgFunction;
return suite.add(implem, () => fn('x'));
return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x'));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('two arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].twoArgFunction;
return suite.add(implem, () => fn('x', 12));
return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('three arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].threeArgFunction;
return suite.add(implem, () => fn('x', 12, true));
return suite.add(implem.padStart(maxNameLength, ' '),
() => fn('x', 12, true));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('four arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].fourArgFunction;
return suite.add(implem, () => fn('x', 12, true, anObject));
return suite.add(implem.padStart(maxNameLength, ' '),
() => fn('x', 12, true, anObject));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();
Expand Down
6 changes: 4 additions & 2 deletions benchmark/property_descriptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ const addonName = path.basename(__filename, '.js');
const rootAddon = require(`./build/Release/${addonName}`);
const getters = new Benchmark.Suite;
const setters = new Benchmark.Suite;
const maxNameLength = Object.keys(rootAddon)
.reduce((soFar, value) => Math.max(soFar, value.length), 0);

console.log(`${addonName}: `);

Object.keys(rootAddon).forEach((key) => {
getters.add(`${key} getter`, () => {
getters.add(`${key} getter`.padStart(maxNameLength + 7), () => {
const x = rootAddon[key];
});
setters.add(`${key} setter`, () => {
setters.add(`${key} setter`.padStart(maxNameLength + 7), () => {
rootAddon[key] = 5;
})
});
Expand Down
Loading

0 comments on commit 8a779a3

Please sign in to comment.