ComponentKit/Core/Scope/CKThreadLocalComponentScope.mm (87 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 "CKThreadLocalComponentScope.h"
#import <pthread.h>
#import <stack>
#import <RenderCore/RCAssert.h>
#import <ComponentKit/CKAnalyticsListener.h>
#import <ComponentKit/CKRootTreeNode.h>
#import <ComponentKit/CKRenderHelpers.h>
#import "CKComponentScopeRoot.h"
#import "CKTreeNode.h"
static pthread_key_t _threadKey() noexcept
{
static pthread_key_t thread_key;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
(void)pthread_key_create(&thread_key, nullptr);
});
return thread_key;
}
CKThreadLocalComponentScope *CKThreadLocalComponentScope::currentScope() noexcept
{
return (CKThreadLocalComponentScope *)pthread_getspecific(_threadKey());
}
CKThreadLocalComponentScope::CKThreadLocalComponentScope(CKComponentScopeRoot *previousScopeRoot,
const CKComponentStateUpdateMap &updates,
CKBuildTrigger trigger,
BOOL shouldCollectTreeNodeCreationInformation,
BOOL alwaysBuildRenderTree,
RCComponentCoalescingMode coalescingMode,
BOOL enforceCKComponentSubclasses,
BOOL disableRenderToNilInCoalescedCompositeComponents)
: newScopeRoot([previousScopeRoot newRoot]),
previousScopeRoot(previousScopeRoot),
stateUpdates(updates),
stack(),
systraceListener(previousScopeRoot.analyticsListener.systraceListener),
buildTrigger(trigger),
componentAllocations(0),
treeNodeDirtyIds(CKRender::treeNodeDirtyIdsFor(previousScopeRoot, stateUpdates, trigger)),
shouldCollectTreeNodeCreationInformation(shouldCollectTreeNodeCreationInformation),
coalescingMode(coalescingMode),
disableRenderToNilInCoalescedCompositeComponents(disableRenderToNilInCoalescedCompositeComponents),
enforceCKComponentSubclasses(enforceCKComponentSubclasses),
previousScope(CKThreadLocalComponentScope::currentScope())
{
stack.push({[newScopeRoot rootNode].node(), previousScopeRoot.rootNode.node()});
keys.push({});
ancestorHasStateUpdate.push(NO);
pthread_setspecific(_threadKey(), this);
}
CKThreadLocalComponentScope::~CKThreadLocalComponentScope()
{
stack.pop();
RCCAssert(stack.empty(), @"Didn't expect stack to contain anything in destructor");
RCCAssert(keys.size() == 1 && keys.top().empty(), @"Expected keys to be at initial state in destructor");
RCCAssert(ancestorHasStateUpdate.size() == 1 && ancestorHasStateUpdate.top() == NO, @"Expected ancestorHasStateUpdate to be at initial state in destructor");
pthread_setspecific(_threadKey(), previousScope);
}
void CKThreadLocalComponentScope::push(CKComponentScopePair scopePair, BOOL keysSupportEnabled) noexcept {
stack.push(std::move(scopePair));
if (keysSupportEnabled) {
keys.push({});
}
}
void CKThreadLocalComponentScope::push(CKComponentScopePair scopePair, BOOL keysSupportEnabled, BOOL ancestorHasStateUpdateValue) noexcept {
push(scopePair, keysSupportEnabled);
ancestorHasStateUpdate.push(ancestorHasStateUpdateValue);
}
void CKThreadLocalComponentScope::pop(BOOL keysSupportEnabled, BOOL ancestorStateUpdateSupportEnabled) noexcept {
stack.pop();
if (keysSupportEnabled) {
RCCAssert(
keys.top().empty(),
@"Expected keys to be cleared on pop");
keys.pop();
}
if (ancestorStateUpdateSupportEnabled) {
ancestorHasStateUpdate.pop();
}
}
void CKThreadLocalComponentScope::markCurrentScopeWithRenderComponentInTree() noexcept
{
CKThreadLocalComponentScope *currentScope = CKThreadLocalComponentScope::currentScope();
if (currentScope != nullptr) {
[currentScope->newScopeRoot setHasRenderComponentInTree:YES];
// `markCurrentScopeWithRenderComponentInTree` is being called for every render component from the base constructor of `CKComponent`.
// We can rely on this infomration to increase the `componentAllocations` counter.
currentScope->componentAllocations++;
}
}