in litho-core/src/main/java/com/facebook/litho/ComponentTree.java [2255:2444]
private void calculateLayout(
@Nullable Size output,
@CalculateLayoutSource int source,
@Nullable String extraAttribution,
@Nullable TreeProps treeProps,
boolean isCreateLayoutInProgress) {
final int widthSpec;
final int heightSpec;
final Component root;
final int localLayoutVersion;
// Cancel any scheduled layout requests we might have in the background queue
// since we are starting a new layout computation.
synchronized (mCurrentCalculateLayoutRunnableLock) {
if (mCurrentCalculateLayoutRunnable != null) {
mLayoutThreadHandler.remove(mCurrentCalculateLayoutRunnable);
mCurrentCalculateLayoutRunnable = null;
}
}
synchronized (this) {
// Can't compute a layout if specs or root are missing
if (!hasSizeSpec() || mRoot == null) {
return;
}
// Check if we already have a compatible layout.
if (isCompatibleComponentAndSpec(mCommittedLayoutState)) {
if (output != null) {
output.width = mCommittedLayoutState.getWidth();
output.height = mCommittedLayoutState.getHeight();
}
return;
}
widthSpec = mWidthSpec;
heightSpec = mHeightSpec;
root = mRoot;
localLayoutVersion = mNextLayoutVersion++;
}
final LayoutState localLayoutState =
calculateLayoutState(
mContext,
root,
widthSpec,
heightSpec,
localLayoutVersion,
mIsLayoutDiffingEnabled,
treeProps,
source,
extraAttribution);
if (localLayoutState == null) {
if (!isReleased()
&& isFromSyncLayout(source)
&& !mComponentsConfiguration.getUseCancelableLayoutFutures()) {
final String errorMessage =
"LayoutState is null, but only async operations can return a null LayoutState. Source: "
+ layoutSourceToString(source)
+ ", current thread: "
+ Thread.currentThread().getName()
+ ". Root: "
+ (mRoot == null ? "null" : mRoot.getSimpleName())
+ ". Interruptible layouts: "
+ mMoveLayoutsBetweenThreads;
if (mComponentsConfiguration.getIgnoreNullLayoutStateError()) {
ComponentsReporter.emitMessage(
ComponentsReporter.LogLevel.ERROR, "ComponentTree:LayoutStateNull", errorMessage);
} else {
throw new IllegalStateException(errorMessage);
}
}
return;
}
if (output != null) {
output.width = localLayoutState.getWidth();
output.height = localLayoutState.getHeight();
}
List<ScopedComponentInfo> scopedComponentInfos = null;
int rootWidth = 0;
int rootHeight = 0;
boolean committedNewLayout = false;
synchronized (this) {
// We don't want to compute, layout, or reduce trees while holding a lock. However this means
// that another thread could compute a layout and commit it before we get to this point. To
// handle this, we make sure that the committed setRootId is only ever increased, meaning
// we only go "forward in time" and will eventually get to the latest layout.
// TODO(t66287929): Remove isCommitted check by only allowing one LayoutStateFuture at a time
if (localLayoutVersion > mCommittedLayoutVersion
&& !localLayoutState.isCommitted()
&& isCompatibleSpec(localLayoutState, mWidthSpec, mHeightSpec)) {
mCommittedLayoutVersion = localLayoutVersion;
mCommittedLayoutState = localLayoutState;
localLayoutState.markCommitted();
committedNewLayout = true;
}
if (DEBUG_LOGS) {
logFinishLayout(source, extraAttribution, localLayoutState, committedNewLayout);
}
final StateHandler layoutStateStateHandler = localLayoutState.consumeStateHandler();
if (committedNewLayout) {
scopedComponentInfos = localLayoutState.consumeScopedComponentInfos();
if (layoutStateStateHandler != null && scopedComponentInfos != null) {
final StateHandler stateHandler = mStateHandler;
if (stateHandler != null) { // we could have been released
if (ComponentsConfiguration.isTimelineEnabled) {
ScopedComponentInfo rootScopedComponentInfo = null;
for (ScopedComponentInfo scopedComponentInfo : scopedComponentInfos) {
if (scopedComponentInfo.getComponent().equals(root)) {
rootScopedComponentInfo = scopedComponentInfo;
break;
}
}
final String globalKey =
(rootScopedComponentInfo != null)
? rootScopedComponentInfo.getContext().getGlobalKey()
: null;
DebugComponentTimeMachine.saveTimelineSnapshot(
this, root, globalKey, stateHandler, treeProps, source, extraAttribution);
}
stateHandler.commit(layoutStateStateHandler);
}
}
if (mMeasureListeners != null) {
rootWidth = localLayoutState.getWidth();
rootHeight = localLayoutState.getHeight();
}
}
if (layoutStateStateHandler != null) {
mInitialStateContainer.unregisterStateHandler(layoutStateStateHandler);
}
// Resetting the count after layout calculation is complete and it was triggered from within
// layout creation
if (!isCreateLayoutInProgress) {
mStateUpdatesFromCreateLayoutCount = 0;
}
}
if (committedNewLayout) {
final List<MeasureListener> measureListeners;
synchronized (this) {
measureListeners = mMeasureListeners == null ? null : new ArrayList<>(mMeasureListeners);
}
if (measureListeners != null) {
for (MeasureListener measureListener : measureListeners) {
measureListener.onSetRootAndSizeSpec(
localLayoutVersion,
rootWidth,
rootHeight,
source == CalculateLayoutSource.UPDATE_STATE_ASYNC
|| source == CalculateLayoutSource.UPDATE_STATE_SYNC);
}
}
}
if (scopedComponentInfos != null) {
bindEventAndTriggerHandlers(scopedComponentInfos);
}
if (committedNewLayout) {
postBackgroundLayoutStateUpdated();
}
if (mPreAllocateMountContentHandler != null) {
mPreAllocateMountContentHandler.remove(mPreAllocateMountContentRunnable);
String tag = EMPTY_STRING;
if (mPreAllocateMountContentHandler.isTracing()) {
tag = "preallocateLayout ";
if (root != null) {
tag = tag + root.getSimpleName();
}
}
mPreAllocateMountContentHandler.post(mPreAllocateMountContentRunnable, tag);
}
}