ComponentKit/Core/CKBuildComponent.mm (132 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "CKBuildComponent.h"
#import "CKAnalyticsListener.h"
#import "CKComponentBoundsAnimation.h"
#import "CKComponentContextHelper.h"
#import "CKComponentEvents.h"
#import "CKComponentInternal.h"
#import "CKComponentScopeRoot.h"
#import "CKComponentSubclass.h"
#import "CKRenderHelpers.h"
#import "CKThreadLocalComponentScope.h"
#import "CKTreeNode.h"
#import "CKComponentCreationValidation.h"
namespace CKBuildComponentHelpers {
/**
Computes and returns the bounds animations for the transition from a prior generation's scope root.
*/
static auto boundsAnimationFromPreviousScopeRoot(CKComponentScopeRoot *newRoot,
CKComponentScopeRoot *previousRoot) -> CKComponentBoundsAnimation
{
NSMapTable *const uniqueIdentifierToOldComponent = [NSMapTable strongToStrongObjectsMapTable];
[previousRoot
enumerateComponentsMatchingPredicate:&CKComponentBoundsAnimationPredicate
block:^(id<CKComponentProtocol> component) {
CKComponent *oldComponent = (CKComponent *)component;
id uniqueIdentifier = [oldComponent uniqueIdentifier];
if (uniqueIdentifier) {
[uniqueIdentifierToOldComponent setObject:oldComponent forKey:uniqueIdentifier];
}
}];
__block CKComponentBoundsAnimation boundsAnimation {};
[newRoot
enumerateComponentsMatchingPredicate:&CKComponentBoundsAnimationPredicate
block:^(id<CKComponentProtocol> component) {
CKComponent *newComponent = (CKComponent *)component;
id uniqueIdentifier = [newComponent uniqueIdentifier];
if (uniqueIdentifier) {
CKComponent *oldComponent = [uniqueIdentifierToOldComponent objectForKey:uniqueIdentifier];
if (oldComponent) {
auto const ba = [newComponent boundsAnimationFromPreviousComponent:oldComponent];
if (ba.duration != 0) {
boundsAnimation = ba;
#if CK_ASSERTIONS_ENABLED
boundsAnimation.component = newComponent;
#endif
}
}
}
}];
return boundsAnimation;
}
}
auto CKBuildComponentTrigger(CK::NonNull<CKComponentScopeRoot *> scopeRoot,
const CKComponentStateUpdateMap &stateUpdates,
BOOL treeEnvironmentChanged,
BOOL treeHasPropsUpdate) -> CKBuildTrigger
{
CKBuildTrigger trigger = CKBuildTriggerNone;
if ([scopeRoot isEmpty] == NO) {
if (stateUpdates.empty() == false) {
trigger |= CKBuildTriggerStateUpdate;
}
if (treeHasPropsUpdate) {
trigger |= CKBuildTriggerPropsUpdate;
}
if (treeEnvironmentChanged) {
trigger |= CKBuildTriggerEnvironmentUpdate;
} else if (stateUpdates.empty()) {
trigger |= CKBuildTriggerPropsUpdate;
}
} else {
RCCAssert(stateUpdates.empty(), @"No previous scope root but state updates");
}
return trigger;
}
CKBuildComponentResult CKBuildComponent(CK::NonNull<CKComponentScopeRoot *> previousRoot,
const CKComponentStateUpdateMap &stateUpdates,
NS_NOESCAPE CKComponent *(^componentFactory)(void))
{
auto const buildTrigger = CKBuildComponentTrigger(previousRoot, stateUpdates, NO, NO);
return CKBuildComponent(previousRoot, stateUpdates, componentFactory, buildTrigger, CKReadGlobalConfig().coalescingMode);
}
CKBuildComponentResult CKBuildComponent(CK::NonNull<CKComponentScopeRoot *> previousRoot,
const CKComponentStateUpdateMap &stateUpdates,
NS_NOESCAPE CKComponent *(^componentFactory)(void),
CKBuildTrigger buildTrigger,
CKReflowTrigger reflowTrigger,
RCComponentCoalescingMode coalescingMode)
{
RCCAssertNotNil(componentFactory, @"Must have component factory to build a component");
auto const globalConfig = CKReadGlobalConfig();
auto const analyticsListener = [previousRoot analyticsListener];
auto const shouldCollectTreeNodeCreationInformation = [analyticsListener shouldCollectTreeNodeCreationInformation:previousRoot];
CKThreadLocalComponentScope threadScope(previousRoot,
stateUpdates,
buildTrigger,
shouldCollectTreeNodeCreationInformation,
globalConfig.alwaysBuildRenderTree,
coalescingMode,
/* enforce CKComponent */ YES,
globalConfig.disableRenderToNilInCoalescedCompositeComponents);
[analyticsListener willBuildComponentTreeWithScopeRoot:previousRoot
buildTrigger:buildTrigger
stateUpdates:stateUpdates];
#if CK_ASSERTIONS_ENABLED
const CKComponentContext<CKComponentCreationValidationContext> validationContext([[CKComponentCreationValidationContext alloc] initWithSource:CKComponentCreationValidationSourceBuild]);
#endif
auto const component = componentFactory();
// Build the component tree if we have a render component in the hierarchy.
if ([threadScope.newScopeRoot hasRenderComponentInTree] || globalConfig.alwaysBuildRenderTree) {
CKBuildComponentTreeParams params = {
.scopeRoot = threadScope.newScopeRoot,
.previousScopeRoot = previousRoot,
.stateUpdates = stateUpdates,
.treeNodeDirtyIds = threadScope.treeNodeDirtyIds,
.buildTrigger = buildTrigger,
.systraceListener = threadScope.systraceListener,
.shouldCollectTreeNodeCreationInformation = shouldCollectTreeNodeCreationInformation,
.coalescingMode = coalescingMode,
};
// Build the component tree from the render function.
CKRender::ComponentTree::Root::build(component, params);
}
auto newScopeRoot = threadScope.newScopeRoot;
auto const boundsAnimation = CKBuildComponentHelpers::boundsAnimationFromPreviousScopeRoot(newScopeRoot, previousRoot);
[analyticsListener didBuildComponentTreeWithScopeRoot:newScopeRoot
buildTrigger:buildTrigger
stateUpdates:stateUpdates
component:component
boundsAnimation:boundsAnimation];
[newScopeRoot setRootComponent:component];
return {
.component = component,
.scopeRoot = newScopeRoot,
.boundsAnimation = boundsAnimation,
.buildTrigger = buildTrigger,
};
}