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 25, 2020
1 parent 4c01af2 commit a8cccd8
Show file tree
Hide file tree
Showing 14 changed files with 1,381 additions and 606 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
196 changes: 124 additions & 72 deletions benchmark/function_args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,76 +78,128 @@ 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) {
// Define the core bindings using plain N-API.
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);

DefineAddon(exports, {
InstanceValue("core", DefineProperties(Napi::Object::New(env), {
InstanceValue("noArgFunction", Napi::Value(env, no_arg_function)),
InstanceValue("oneArgFunction", Napi::Value(env, one_arg_function)),
InstanceValue("twoArgFunction", Napi::Value(env, two_arg_function)),
InstanceValue("threeArgFunction", Napi::Value(env, three_arg_function)),
InstanceValue("fourArgFunction", Napi::Value(env, four_arg_function)),
}), napi_enumerable),
InstanceValue("cplusplus", DefineProperties(Napi::Object::New(env), {
InstanceValue("noArgFunction", Napi::Function::New(env, NoArgFunction)),
InstanceValue("oneArgFunction",
Napi::Function::New(env, OneArgFunction)),
InstanceValue("twoArgFunction",
Napi::Function::New(env, TwoArgFunction)),
InstanceValue("threeArgFunction",
Napi::Function::New(env, ThreeArgFunction)),
InstanceValue("fourArgFunction",
Napi::Function::New(env, FourArgFunction)),
}), napi_enumerable),
InstanceValue("templated", DefineProperties(Napi::Object::New(env), {
InstanceValue("noArgFunction", Napi::Function::New<NoArgFunction>(env)),
InstanceValue("oneArgFunction",
Napi::Function::New<OneArgFunction>(env)),
InstanceValue("twoArgFunction",
Napi::Function::New<TwoArgFunction>(env)),
InstanceValue("threeArgFunction",
Napi::Function::New<ThreeArgFunction>(env)),
InstanceValue("fourArgFunction",
Napi::Function::New<FourArgFunction>(env)),
}), napi_enumerable),
InstanceValue("instance", DefineProperties(Napi::Object::New(env), {
InstanceMethod("noArgFunction", &FunctionArgs::InstNoArgFunction),
InstanceMethod("oneArgFunction", &FunctionArgs::InstOneArgFunction),
InstanceMethod("twoArgFunction", &FunctionArgs::InstTwoArgFunction),
InstanceMethod("threeArgFunction", &FunctionArgs::InstThreeArgFunction),
InstanceMethod("fourArgFunction", &FunctionArgs::InstFourArgFunction),
}), napi_enumerable),
InstanceValue("templated_instance",
DefineProperties(Napi::Object::New(env), {
InstanceMethod<&FunctionArgs::InstNoArgFunction>("noArgFunction"),
InstanceMethod<&FunctionArgs::InstOneArgFunction>("oneArgFunction"),
InstanceMethod<&FunctionArgs::InstTwoArgFunction>("twoArgFunction"),
InstanceMethod<&FunctionArgs::InstThreeArgFunction>("threeArgFunction"),
InstanceMethod<&FunctionArgs::InstFourArgFunction>("fourArgFunction"),
}), napi_enumerable)
});
}

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)
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
82 changes: 51 additions & 31 deletions benchmark/property_descriptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,55 @@ static void Setter(const Napi::CallbackInfo& info) {
(void) info[0];
}

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
napi_status status;
napi_property_descriptor core_prop = {
"core",
nullptr,
nullptr,
Getter_Core,
Setter_Core,
nullptr,
napi_enumerable,
nullptr
};

status = napi_define_properties(env, exports, 1, &core_prop);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor(env,
exports,
"cplusplus",
Getter,
Setter,
napi_enumerable));

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor<Getter, Setter>("templated",
napi_enumerable));

return exports;
}
class PropDescBenchmark : public Napi::Addon<PropDescBenchmark> {
public:
PropDescBenchmark(Napi::Env env, Napi::Object exports) {
napi_status status;
napi_property_descriptor core_prop = {
"core",
nullptr,
nullptr,
Getter_Core,
Setter_Core,
nullptr,
napi_enumerable,
nullptr
};

status = napi_define_properties(env, exports, 1, &core_prop);
NAPI_THROW_IF_FAILED_VOID(env, status);

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor(env,
exports,
"cplusplus",
Getter,
Setter,
napi_enumerable));

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor<Getter, Setter>("templated",
napi_enumerable));

DefineAddon(exports, {
InstanceAccessor("instance",
&PropDescBenchmark::InstGetter,
&PropDescBenchmark::InstSetter,
napi_enumerable),
InstanceAccessor<&PropDescBenchmark::InstGetter,
&PropDescBenchmark::InstSetter>("instance_templated",
napi_enumerable)
});
}
private:
Napi::Value InstGetter(const Napi::CallbackInfo& info) {
return Napi::Number::New(info.Env(), 42);
}

void InstSetter(const Napi::CallbackInfo& info, const Napi::Value& newValue) {
(void) info[0];
(void) newValue;
}
};

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
NODE_API_ADDON(PropDescBenchmark)
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 a8cccd8

Please sign in to comment.