-
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 all 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) { | ||
|
@@ -576,49 +633,62 @@ jsi::Value UIManagerBinding::get( | |
jsi::Value const *arguments, | ||
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"); | ||
|
||
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; | ||
} | ||
|
||
if (turboModuleCalled) { | ||
return jsi::Value::undefined(); | ||
} | ||
|
||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, nullptr, {/* .includeTransform = */ true}); | ||
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 (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); | ||
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(), | ||
// 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) { | ||
// There's a bug that occurs if, below, we try to | ||
// call the onSuccessFunction we extracted | ||
// earlier. It seems that JSIFunctions can't be | ||
// passed into capture clauses for c++ anonymous | ||
// functions, because they lack the appropriate | ||
// copy constructor. Since we do definitely need | ||
// access to the onSuccessFunction, we obtain that | ||
// access by passing it into the .call() above and | ||
// then passing it BACK OUT in native land, so we | ||
// can extract it from args and use it right here. | ||
auto onSuccessFunction = | ||
args[0].getObject(rt).getFunction(rt); | ||
measureFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, rt); | ||
return jsi::Value::undefined(); | ||
})); | ||
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(); | ||
}); | ||
} | ||
|
@@ -634,43 +704,58 @@ jsi::Value UIManagerBinding::get( | |
jsi::Value const *arguments, | ||
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"); | ||
|
||
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; | ||
} | ||
|
||
if (turboModuleCalled) { | ||
return jsi::Value::undefined(); | ||
} | ||
|
||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics( | ||
*shadowNode, | ||
nullptr, | ||
{/* .includeTransform = */ true, | ||
/* .includeViewportOffset = */ true}); | ||
|
||
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 (layoutMetrics == EmptyLayoutMetrics) { | ||
onSuccessFunction.call(runtime, {0, 0, 0, 0}); | ||
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(), | ||
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) { | ||
// There's a bug that occurs if, below, we try to | ||
// call the onSuccessFunction we extracted | ||
// earlier. It seems that JSIFunctions can't be | ||
// passed into capture clauses for c++ anonymous | ||
// functions, because they lack the appropriate | ||
// copy constructor. Since we do definitely need | ||
// access to the onSuccessFunction, we obtain that | ||
// access by passing it into the .call() above and | ||
// then passing it BACK OUT in native land, so we | ||
// can extract it from args and use it right here. | ||
auto onSuccessFunction = | ||
args[0].getObject(rt).getFunction(rt); | ||
measureInWindowFromShadowTree( | ||
uiManager, shadowNode, onSuccessFunction, rt); | ||
return jsi::Value::undefined(); | ||
})); | ||
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.