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>`.
* Add `Addon<T>` class as a subclass of `InstanceWrap<T>`,
  reimplementing `Unwrap()` to retrieve the instance data using
  `GetInstanceData<T>()` of `Napi::Env` instead of `napi_unwrap()`.
* 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. This way, they avoid having to call
`info.GetInstanceData<ClassName>()` in the bindings, which brings with
it the risk that the wrong `ClassName` will end up in the template
parameter thus resulting in a hard-to-track-down segfault. 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.

PR-URL: #749
Reviewed-By: Michael Dawson <[email protected]>
  • Loading branch information
Gabriel Schulhof committed Jul 22, 2020
1 parent 6148fb4 commit 5af645f
Show file tree
Hide file tree
Showing 14 changed files with 1,312 additions and 516 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
64 changes: 64 additions & 0 deletions benchmark/function_args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,66 @@ static void FourArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv3 = info[3]; (void) argv3;
}

#if NAPI_VERSION > 5
class FunctionArgsBenchmark : public Napi::Addon<FunctionArgsBenchmark> {
public:
FunctionArgsBenchmark(Napi::Env env, Napi::Object exports) {
DefineAddon(exports, {
InstanceValue("addon", DefineProperties(Napi::Object::New(env), {
InstanceMethod("noArgFunction", &FunctionArgsBenchmark::NoArgFunction),
InstanceMethod("oneArgFunction",
&FunctionArgsBenchmark::OneArgFunction),
InstanceMethod("twoArgFunction",
&FunctionArgsBenchmark::TwoArgFunction),
InstanceMethod("threeArgFunction",
&FunctionArgsBenchmark::ThreeArgFunction),
InstanceMethod("fourArgFunction",
&FunctionArgsBenchmark::FourArgFunction),
}), napi_enumerable),
InstanceValue("addon_templated",
DefineProperties(Napi::Object::New(env), {
InstanceMethod<&FunctionArgsBenchmark::NoArgFunction>(
"noArgFunction"),
InstanceMethod<&FunctionArgsBenchmark::OneArgFunction>(
"oneArgFunction"),
InstanceMethod<&FunctionArgsBenchmark::TwoArgFunction>(
"twoArgFunction"),
InstanceMethod<&FunctionArgsBenchmark::ThreeArgFunction>(
"threeArgFunction"),
InstanceMethod<&FunctionArgsBenchmark::FourArgFunction>(
"fourArgFunction"),
}), napi_enumerable),
});
}
private:
void NoArgFunction(const Napi::CallbackInfo& info) {
(void) info;
}

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

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

void ThreeArgFunction(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 FourArgFunction(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;
}
};
#endif // NAPI_VERSION > 5

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;
Expand Down Expand Up @@ -147,6 +207,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
templated["fourArgFunction"] = Napi::Function::New<FourArgFunction>(env);
exports["templated"] = templated;

#if NAPI_VERSION > 5
FunctionArgsBenchmark::Init(env, exports);
#endif // NAPI_VERSION > 5

return exports;
}

Expand Down
22 changes: 15 additions & 7 deletions benchmark/function_args.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,56 @@ const addonName = path.basename(__filename, '.js');

[ addonName, addonName + '_noexcept' ]
.forEach((addonName) => {
const rootAddon = require(`./build/Release/${addonName}`);
const rootAddon = require('bindings')({
bindings: addonName,
module_root: __dirname
});
delete rootAddon.path;
const implems = Object.keys(rootAddon);
const maxNameLength =
implems.reduce((soFar, value) => Math.max(soFar, value.length), 0);
const anObject = {};

console.log(`${addonName}: `);
console.log(`\n${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
31 changes: 31 additions & 0 deletions benchmark/property_descriptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ static void Setter(const Napi::CallbackInfo& info) {
(void) info[0];
}

#if NAPI_VERSION > 5
class PropDescBenchmark : public Napi::Addon<PropDescBenchmark> {
public:
PropDescBenchmark(Napi::Env, Napi::Object exports) {
DefineAddon(exports, {
InstanceAccessor("addon",
&PropDescBenchmark::Getter,
&PropDescBenchmark::Setter,
napi_enumerable),
InstanceAccessor<&PropDescBenchmark::Getter,
&PropDescBenchmark::Setter>("addon_templated",
napi_enumerable),
});
}

private:
Napi::Value Getter(const Napi::CallbackInfo& info) {
return Napi::Number::New(info.Env(), 42);
}

void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) {
(void) info[0];
(void) val;
}
};
#endif // NAPI_VERSION > 5

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
napi_status status;
napi_property_descriptor core_prop = {
Expand Down Expand Up @@ -54,6 +81,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
Napi::PropertyDescriptor::Accessor<Getter, Setter>("templated",
napi_enumerable));

#if NAPI_VERSION > 5
PropDescBenchmark::Init(env, exports);
#endif // NAPI_VERSION > 5

return exports;
}

Expand Down
16 changes: 12 additions & 4 deletions benchmark/property_descriptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ const addonName = path.basename(__filename, '.js');

[ addonName, addonName + '_noexcept' ]
.forEach((addonName) => {
const rootAddon = require(`./build/Release/${addonName}`);
const rootAddon = require('bindings')({
bindings: addonName,
module_root: __dirname
});
delete rootAddon.path;
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}: `);
console.log(`\n${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 All @@ -23,6 +29,8 @@ const addonName = path.basename(__filename, '.js');
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('');

setters
.on('cycle', (event) => console.log(String(event.target)))
.run();
Expand Down
Loading

0 comments on commit 5af645f

Please sign in to comment.