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++; } }