-
Notifications
You must be signed in to change notification settings - Fork 15
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
Fallback to Shadow Tree measurement if native measure fails #49
base: 0.72.3-discord-1
Are you sure you want to change the base?
Changes from 9 commits
4e7fe95
60fda14
d1b44dc
7513657
7fe78d0
de30705
cbd01c0
3bbd06d
0275515
6a7d884
9759572
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,6 +140,63 @@ void UIManagerBinding::invalidate() const { | |
uiManager_->setDelegate(nullptr); | ||
} | ||
|
||
void measureFromShadowTree( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to break this work out into a method. It's my understanding c++ doesn't allow you to introduce functions inside functions, so I had to do it here. Is that right? Is there not a way for this code to show up closer to where it's used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems correct. You could maybe move it closer by making a lambda, or a macro, but those introduce their own set of tradeoffs. You probably want to make this a proper private method of the |
||
UIManager *uiManager, | ||
ShadowNode::Shared shadowNode, | ||
jsi::Function &onSuccessFunction, | ||
jsi::Runtime &runtime) { | ||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, nullptr, {/* .includeTransform = */ true}); | ||
|
||
if (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); | ||
return; | ||
} | ||
auto newestCloneOfShadowNode = | ||
uiManager->getNewestCloneOfShadowNode(*shadowNode); | ||
|
||
auto layoutableShadowNode = | ||
traitCast<LayoutableShadowNode const *>(newestCloneOfShadowNode.get()); | ||
Point originRelativeToParent = layoutableShadowNode != nullptr | ||
? layoutableShadowNode->getLayoutMetrics().frame.origin | ||
: Point(); | ||
|
||
auto frame = layoutMetrics.frame; | ||
onSuccessFunction.call( | ||
runtime, | ||
{jsi::Value{runtime, (double)originRelativeToParent.x}, | ||
jsi::Value{runtime, (double)originRelativeToParent.y}, | ||
jsi::Value{runtime, (double)frame.size.width}, | ||
jsi::Value{runtime, (double)frame.size.height}, | ||
jsi::Value{runtime, (double)frame.origin.x}, | ||
jsi::Value{runtime, (double)frame.origin.y}}); | ||
} | ||
|
||
void measureInWindowFromShadowTree( | ||
UIManager *uiManager, | ||
ShadowNode::Shared shadowNode, | ||
jsi::Function &onSuccessFunction, | ||
jsi::Runtime &runtime) { | ||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, | ||
nullptr, | ||
{/* .includeTransform = */ true, | ||
/* .includeViewportOffset = */ true}); | ||
|
||
if (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0}); | ||
return; | ||
} | ||
|
||
auto frame = layoutMetrics.frame; | ||
onSuccessFunction.call( | ||
runtime, | ||
{jsi::Value{runtime, (double)frame.origin.x}, | ||
jsi::Value{runtime, (double)frame.origin.y}, | ||
jsi::Value{runtime, (double)frame.size.width}, | ||
jsi::Value{runtime, (double)frame.size.height}}); | ||
} | ||
|
||
jsi::Value UIManagerBinding::get( | ||
jsi::Runtime &runtime, | ||
jsi::PropNameID const &name) { | ||
|
@@ -577,48 +634,59 @@ jsi::Value UIManagerBinding::get( | |
size_t /*count*/) noexcept -> jsi::Value { | ||
auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); | ||
bool turboModuleCalled = false; | ||
auto nativeMeasurerValue = runtime.global().getProperty(runtime, "__turboModuleProxy") | ||
.asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); | ||
auto nativeMeasurerValue = | ||
runtime.global() | ||
.getProperty(runtime, "__turboModuleProxy") | ||
.asObject(runtime) | ||
.asFunction(runtime) | ||
.call(runtime, "NativeFabricMeasurerTurboModule"); | ||
auto onSuccessFunction = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this into the |
||
arguments[1].getObject(runtime).getFunction(runtime); | ||
|
||
if (nativeMeasurerValue.isObject()) { | ||
// This calls measureNatively if the NativeFabricMeasurerTurboModule is found. | ||
// The return value doesn't matter here because the measure values will be passed through the callback. | ||
jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureNatively") | ||
.call(runtime, shadowNode.get()->getTag(), arguments[1].getObject(runtime).getFunction(runtime)); | ||
turboModuleCalled = true; | ||
// This calls measureNatively if the NativeFabricMeasurerTurboModule | ||
// is found. The return value doesn't matter here because the | ||
// measure values will be passed through the callback. | ||
jsi::Value returnValue = | ||
nativeMeasurerValue.asObject(runtime) | ||
.getPropertyAsFunction(runtime, "measureNatively") | ||
.call( | ||
runtime, | ||
shadowNode.get()->getTag(), | ||
// measureNatively takes two callbacks, one for if the | ||
// layout is found and one for if an error occurs so we | ||
// can gracefully fallback to measuring out of the | ||
// shadow tree. | ||
onSuccessFunction, | ||
jsi::Function::createFromHostFunction( | ||
runtime, | ||
jsi::PropNameID::forAscii( | ||
runtime, "onNativeMeasureFail"), | ||
1, | ||
[uiManager, shadowNode]( | ||
jsi::Runtime &rt, | ||
const jsi::Value &thisVal, | ||
const jsi::Value *args, | ||
size_t count) { | ||
// We return onSuccessFunction out of the onFail | ||
// instead of using the one defined above to avoid | ||
// a scoping bug. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to rewrite this comment to describe a bit better what the scoping bug is. Essentially something about how functions can't be passed into capture lists because they can't be copied so we need to get a new reference to the function. Unsure of how to phrase that 🤔 |
||
auto onSuccessFunction = | ||
args[0].getObject(rt).getFunction(rt); | ||
measureFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, rt); | ||
return jsi::Value::undefined(); | ||
})); | ||
turboModuleCalled = true; | ||
} | ||
|
||
if (turboModuleCalled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're setting turboModuleCalled = true two lines above this. why check now if it's set? why not just return Value::undefined() above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably was a rough edge around the initial implementation of this. Your recommendation sounds like a cleaner way to do it. |
||
return jsi::Value::undefined(); | ||
} | ||
|
||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, nullptr, {/* .includeTransform = */ true}); | ||
auto onSuccessFunction = | ||
arguments[1].getObject(runtime).getFunction(runtime); | ||
|
||
if (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); | ||
return jsi::Value::undefined(); | ||
} | ||
auto newestCloneOfShadowNode = | ||
uiManager->getNewestCloneOfShadowNode(*shadowNode); | ||
|
||
auto layoutableShadowNode = traitCast<LayoutableShadowNode const *>( | ||
newestCloneOfShadowNode.get()); | ||
Point originRelativeToParent = layoutableShadowNode != nullptr | ||
? layoutableShadowNode->getLayoutMetrics().frame.origin | ||
: Point(); | ||
measureFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, runtime); | ||
|
||
auto frame = layoutMetrics.frame; | ||
onSuccessFunction.call( | ||
runtime, | ||
{jsi::Value{runtime, (double)originRelativeToParent.x}, | ||
jsi::Value{runtime, (double)originRelativeToParent.y}, | ||
jsi::Value{runtime, (double)frame.size.width}, | ||
jsi::Value{runtime, (double)frame.size.height}, | ||
jsi::Value{runtime, (double)frame.origin.x}, | ||
jsi::Value{runtime, (double)frame.origin.y}}); | ||
return jsi::Value::undefined(); | ||
}); | ||
} | ||
|
@@ -635,42 +703,55 @@ jsi::Value UIManagerBinding::get( | |
size_t /*count*/) noexcept -> jsi::Value { | ||
auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); | ||
bool turboModuleCalled = false; | ||
auto nativeMeasurerValue = runtime.global().getProperty(runtime, "__turboModuleProxy") | ||
.asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); | ||
auto nativeMeasurerValue = | ||
runtime.global() | ||
.getProperty(runtime, "__turboModuleProxy") | ||
.asObject(runtime) | ||
.asFunction(runtime) | ||
.call(runtime, "NativeFabricMeasurerTurboModule"); | ||
auto onSuccessFunction = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the |
||
arguments[1].getObject(runtime).getFunction(runtime); | ||
|
||
if (nativeMeasurerValue.isObject()) { | ||
// This calls measureNatively if the NativeFabricMeasurerTurboModule is found. | ||
// The return value doesn't matter here because the measure values will be passed through the callback. | ||
jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureInWindowNatively") | ||
.call(runtime, shadowNode.get()->getTag(), arguments[1].getObject(runtime).getFunction(runtime)); | ||
turboModuleCalled = true; | ||
// This calls measureNatively if the NativeFabricMeasurerTurboModule | ||
// is found. The return value doesn't matter here because the | ||
// measure values will be passed through the callback. | ||
jsi::Value returnValue = | ||
nativeMeasurerValue.asObject(runtime) | ||
.getPropertyAsFunction(runtime, "measureInWindowNatively") | ||
.call( | ||
runtime, | ||
shadowNode.get()->getTag(), | ||
onSuccessFunction, | ||
jsi::Function::createFromHostFunction( | ||
runtime, | ||
jsi::PropNameID::forAscii( | ||
runtime, "onNativeMeasureFail"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
1, | ||
[uiManager, shadowNode]( | ||
jsi::Runtime &rt, | ||
const jsi::Value &thisVal, | ||
const jsi::Value *args, | ||
size_t count) { | ||
// We return onSuccessFunction out of the onFail | ||
// instead of using the one defined above to avoid | ||
// a scoping bug. | ||
auto onSuccessFunction = | ||
args[0].getObject(rt).getFunction(rt); | ||
measureInWindowFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, rt); | ||
return jsi::Value::undefined(); | ||
})); | ||
turboModuleCalled = true; | ||
} | ||
|
||
if (turboModuleCalled) { | ||
return jsi::Value::undefined(); | ||
} | ||
|
||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, | ||
nullptr, | ||
{/* .includeTransform = */ true, | ||
/* .includeViewportOffset = */ true}); | ||
|
||
auto onSuccessFunction = | ||
arguments[1].getObject(runtime).getFunction(runtime); | ||
|
||
if (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0}); | ||
return jsi::Value::undefined(); | ||
} | ||
|
||
auto frame = layoutMetrics.frame; | ||
onSuccessFunction.call( | ||
runtime, | ||
{jsi::Value{runtime, (double)frame.origin.x}, | ||
jsi::Value{runtime, (double)frame.origin.y}, | ||
jsi::Value{runtime, (double)frame.size.width}, | ||
jsi::Value{runtime, (double)frame.size.height}}); | ||
measureInWindowFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, runtime); | ||
|
||
return jsi::Value::undefined(); | ||
}); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If keeping this approach, I'd put the above comment about scoping here too.