-
Notifications
You must be signed in to change notification settings - Fork 647
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
instanceof on host objects #1467
Comments
Closing has I realized that Hermes supports the |
First let me say that we strongly recommend against using HostObject, unless absolutely necessary. It is an anti-pattern and should ideally never be used. (Admittedly, it was the only option early on, but since then we added NativeState.) In any case, you shouldn't need any tricks to do this. |
Could explain why On our side, this is how we use it: interface Foo {
bar(param?: string | number): bool;
} We generate the following C++ code: class Foo: HybridObject {
bool bar(param: std::optional<std::variant<std::string, double>>) {
return true;
}
void loadHybridMethods() override {
registerHybridMethod("bar", &Foo::bar, this);
}
} This approach has done wonders for us as we code generate >90% of the work. |
(@tmikov this is the nitro modules API I briefly mentioned on our discord convo, if you remember I had a similar HostObject but in Swift) |
I have created some diagrams to illustrate the difference between The first diagram shows how normal JS objects or classes work.They are fairly efficient because the property dictionary is shared between all instances, methods are stored only once in the prototype, and everything is subject to property caching in the JSVM. Best case, a property access is a comparison and a direct access to the data slot in the object. Additionally, the data is naturally managed by the GC. The second diagram shows HostObjectThe main problem here is that there is no property offset caching, so every access requires a C++ virtual call. Additionally, custom C++ logic is needed for an efficient implementation of property lookup. Additional custom complexity is needed to avoid duplicating methods for every instance of the object. The second problem is that The third diagram shows NativeState
|
I see - thanks for the explanation. Just a few thoughts:
We built quite a few things with So far, (@wcandillon maybe all Hybrid Objects that don't have properties can be |
Continuing the thought experiment on a Inside this function, the user can access the current Camera to the 3D scene: const callback = useFrameCallback((scene) => {
const camera = scene.getCamera() // <-- camera is a jsi::HostObject
camera.setPosition(...)
}) Now I haven't benchmarked this yet, but to me it sounds like returning a We also can't really cache the result to |
NaiveState is a just way to associate native data with a JS object. No more, no less. It does not "support" neither properties nor methods. These concepts are orthogonal to it. However ordinary JS already supports property getters and setters. Their implementation can be in C++ and can access NativeState.
Allocating 30 instances of HostFunction and performing 30 property writes is very cheap. Plus, it is happening exactly once. Why do you believe that this is a performance problem? Ignoring the performance differences, the high level point is that NativeState allows easy integration of native data and code with existing objects, without changing how JS objects and classes already work. Subclassing, methods, reflection, etc, everything just works because it is the same. HostObject, by comparison, requires a custom implementation in order to approximate the behavior of an object. Extra effort to implement property enumeration, subclassing, etc. Debugging gets harder, especially if you have instances from different vendors, because each implements things differently. Etc. Of course software development is all about trade-offs. If the execution of the body of the method itself is expensive, the cost to fetch the method probably doesn't matter. If you have already built the infrastructure for HostObject, the convenience offered by NativeState is marginal. Etc. So, you should, of course, do whatever is best for your case. |
Ideally, the methods should live in the prototype object. Creating the prototyoe should perform almost no work and it should happen exactly once. That would be the natural way to define a JS class with or without NativeState. It is also possible with HostObject, of course (not sure whether you are doing that), but requires extra complexity. Perhaps I don't understand the problem. |
Is there a way for a JSI function to return a class? And if yes, would that help? const Foo = getFoo();
const foo = new Foo(); On our side, the fact that classes are not "workletizable" would make using NativeState a show stopper. However Reanimated support for "workletizable" classes merged in main. So it is something we might consider in the future. Right now we cogen from TS to C++ and here we would need to codegen from TS to JS and C. As Marc mentioned, we would need to register the complete API upfront (in our case it is quite big). |
EDIT: nvm, I can just get
Makes total sense - yep. I really appreciate your insights here, this is what I have been curious about for a long time now. I think my best solution is to continue using the HostObject approach as that works really well for us, I already have property/method caching and externaMemorySize, and everything's auto-generated. I'll add a feature to support NativeState (either manually or automatically) when an object contains no properties. The user of my framework (nitro) can then choose himself if he wants to implement properties on the JS side, or not. Thanks Tzvetan! See you at React Universe! 😄 |
(btw i just sent you an invite to the repo if you're curious what kind of sorcery I'm performing on HostObjects, all is in |
Isn't that just a function that applys a prototype? |
Ohhhh- so wait maybe I am just now getting what you mean, are you saying this is how NativeState should be used?
or am I misunderstanding what you said? |
I'm thinking about ergonomics again. In the case of Skia, for
instance, which is C++, we would need to build a C-style API on top of
it and then load the complete API upfront.
Sounds a bit rough. I'm wondering if there is something nicer that
could be done here.
…On Mon, Aug 5, 2024 at 1:45 PM Marc Rousavy ***@***.***> wrote:
Ideally, the methods should live in the prototype object. Creating the prototyoe should perform almost no work and it should happen exactly once.
Ohhhh- so wait maybe I am just now getting what you mean, are you saying this is how NativeState should be used?
Initialize Prototype globally/only once:
jsi::Object proto(runtime);
proto.setProperty("someFunc", jsi::Function::createFromHostFunction([](...,
const jsi::Value& thisValue,
...) {
jsi::Object thisObj = thisValue.asObject(); // <-- get from `this`
auto state = thisObj.getNativeState<MyNativeState>(); // <-- unwrap native state
return state->someFunc(); // <-- actual C++ impl
});
Then each time we want to return a Camera jsi::Object with NativeState in getCamera, we just re-use the Prototype?
jsi::Object camera(runtime);
camera.setNativeState(cameraState);
// TODO: Somehow apply proto to camera?
return camera;
or am I misunderstanding what you said?
—
Reply to this email directly, view it on GitHub or unsubscribe.
You are receiving this email because you authored the thread.
Triage notifications on the go with GitHub Mobile for iOS or Android.
|
@mrousavy here is an example of how NativeState can be used comfortably. There are two host functions that need to be exposed, which here are shown as JS for illustrative purposes. https://gist.github.com/tmikov/3e6310abcd77ff8066297cdd4927b44d |
The following pattern:
Means that people should be able to write:
Is it possible to support this syntax if
device.queue
is a host object?The text was updated successfully, but these errors were encountered: