Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: add Addon<T> class #749

Conversation

gabrielschulhof
Copy link
Contributor

@gabrielschulhof gabrielschulhof commented Jun 23, 2020

  • 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.

Edit: I removed the DefineProperties overload, opting instead to have Addon<T> provide a DefineProperties method to its subclasses.

@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from 8a779a3 to beb1fa3 Compare June 24, 2020 17:25
@gabrielschulhof gabrielschulhof marked this pull request as draft June 24, 2020 22:19
@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from beb1fa3 to a8cccd8 Compare June 25, 2020 01:35
@gabrielschulhof gabrielschulhof marked this pull request as ready for review June 25, 2020 01:35
@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch 9 times, most recently from b1c99cf to ce880ff Compare June 25, 2020 02:22
@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from ce880ff to 871dc65 Compare June 25, 2020 19:31
@gabrielschulhof
Copy link
Contributor Author

Telling it to ignore whitespace makes reviewing easier:

ignore whitespace

@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch 5 times, most recently from 0beeb83 to 6b0469b Compare June 28, 2020 09:01
@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from 6b0469b to 4283b2c Compare July 6, 2020 18:06
@gabrielschulhof
Copy link
Contributor Author

@mhdawson @NickNaso @addaleax @legendecas @KevinEady @jschlight as discussed in today's meeting, I re-worked the PR as follows:

  • rebased onto master
  • broke the changes into two commits
    1. the actual changes
    2. group the InstanceWrap<T> functions into the portion of napi-inl.h that implements InstanceWrap<T> and fix whitespace issues intentionally left in the first commit to make it easier to review

I also made the commit message more verbose:

  • 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.

@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch 2 times, most recently from a6d68c7 to 855372f Compare July 7, 2020 20:23
@mhdawson
Copy link
Member

mhdawson commented Jul 8, 2020

@gabrielschulhof thanks for refactoring. Trying hard to carve out a block of time to review. Failed today but will try again tomorrow.

@gabrielschulhof
Copy link
Contributor Author

@mhdawson thank you!

napi-inl.h Outdated
#define NODE_API_MODULE(modname, regfunc) \
napi_value __napi_ ## regfunc(napi_env env, \
static napi_value __napi_ ## regfunc(napi_env env, \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests seems to compile/pass without this change. Wondering as to my its in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it because I was working on an add-on definition macro right next to it, and we shouldn't be exposing the entry point symbol. You're right though, I'll make it a separate PR.


return exports;
}
NAPI_THROW_IF_FAILED_VOID(env, status);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes mean we won't be able to run the benchmarks if the target is less than NAPI_VERSION 6.

I wonder if the changes to the benchmark code should be additive so that we get new numbers if the target is NAPI_VERSION 6 or greater as opposed to replacing the existing ones.

That would also let us keep an eye on the delta between using the additional wrapper or not.

@mhdawson
Copy link
Member

mhdawson commented Jul 9, 2020

Overall I really like this. I think it really helps with ease of use. Just had a few comments. Once you've updated let me know and I'll LGTM.

Totally separate but something I think this helps enable and that I think it would be really cool is that instead of having to create the code to call DefineAddon(), you could just annotate the methods and we'd generate the required call to DefineAddon(), maybe something like:

 // @function increment
 Napi::Value Increment(const Napi::CallbackInfo& info) {
    return Napi::Number::New(info.Env(), ++value);
  }

It also relates a bit to the question we had about generating documentation as well as we could then also add information
to define the parameters, and possibly even alias them to make code more readable. Maybe something like

 /**
  * @function increment
  * @param {number} increment_amount - amount to increment when the method is called
  * @returns {number} the incremented value
  */
 Napi::Value Increment(const Napi::CallbackInfo& info) {
    value = value + ${PARAM_increment_amount}.Uint32Value();
    return Napi::Number::New(info.Env(), value);
  }

Where ${PARAM_increment_amount} would be converted to info[0].AsNapi::Number() based on being the first parameter defined, and being defined as type number.

I think it would require a pre-processing step but that might be reasonable. Thinking about it, the same could be useful for object wrap as well

I think we could probably come up with annotations for the void* data and attributes although those are probably use infrequently enough that what I've outlined would be useful without them.

@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from 855372f to 30ce2b9 Compare July 10, 2020 06:16
@gabrielschulhof
Copy link
Contributor Author

@mhdawson I have made the changes you requested.

Copy link
Member

@mhdawson mhdawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

* 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.
@gabrielschulhof gabrielschulhof force-pushed the addon-class-from-objectwrap branch from 30ce2b9 to ecafa3e Compare July 22, 2020 19:55
@gabrielschulhof
Copy link
Contributor Author

Rebased and squashed.

gabrielschulhof pushed a commit that referenced this pull request Jul 22, 2020
* 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]>
@gabrielschulhof
Copy link
Contributor Author

Landed in 5af645f.

@gabrielschulhof gabrielschulhof deleted the addon-class-from-objectwrap branch July 22, 2020 20:25
kevindavies8 added a commit to kevindavies8/node-addon-api-Develop that referenced this pull request Aug 24, 2022
* 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: nodejs/node-addon-api#749
Reviewed-By: Michael Dawson <[email protected]>
Marlyfleitas added a commit to Marlyfleitas/node-api-addon-Development that referenced this pull request Aug 26, 2022
* 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: nodejs/node-addon-api#749
Reviewed-By: Michael Dawson <[email protected]>
wroy7860 added a commit to wroy7860/addon-api-benchmark-node that referenced this pull request Sep 19, 2022
* 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: nodejs/node-addon-api#749
Reviewed-By: Michael Dawson <[email protected]>
johnfrench3 pushed a commit to johnfrench3/node-addon-api-git that referenced this pull request Aug 11, 2023
* 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: nodejs/node-addon-api#749
Reviewed-By: Michael Dawson <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants