RenderCore/RCLayout.mm (111 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 "RCLayout.h" #import <stack> #import <sstream> #import <unordered_map> /** Deletes the target off the main thread; important since component layouts are large recursive structures. */ struct CKOffMainThreadDeleter { void operator()(std::vector<RCLayoutChild> *target) noexcept; }; using namespace CK::Component; RCLayout::RCLayout(id<CKMountable> c, CGSize s) noexcept : component(c), size(s), children(emptyChildren()), extra(nil) { RCCAssertNotNil(c, @"Nil components are not allowed"); }; RCLayout::RCLayout(id<CKMountable> c, CGSize s, const std::vector<RCLayoutChild> &ch, NSDictionary *e) noexcept : component(c), size(s), children(new std::vector<RCLayoutChild>(ch), CKOffMainThreadDeleter()), extra(e) { RCCAssertNotNil(c, @"Nil components are not allowed"); }; RCLayout::RCLayout(id<CKMountable> c, CGSize s, std::vector<RCLayoutChild> &&ch, NSDictionary *e) noexcept : component(c), size(s), children(new std::vector<RCLayoutChild>(std::move(ch)), CKOffMainThreadDeleter()), extra(e) { RCCAssertNotNil(c, @"Nil components are not allowed"); }; RCLayout::RCLayout() noexcept : component(nil), size({0, 0}), children(emptyChildren()), extra(nil) {}; std::string RCLayout::description(int indent) const { std::stringstream s; s << std::string(indent, ' ') << "{" << std::endl; s << std::string(indent + 2, ' ') << "size: {" << size.width << ", " << size.height << "}," << std::endl; if (!children->empty()) { s << std::string(indent + 2, ' ') << "[" << std::endl; for (const auto &child : *children) { s << std::string(indent + 4, ' ') << "{" << std::endl; s << std::string(indent + 6, ' ') << "position: {" << child.position.x << ", " << child.position.y << "}," << std::endl; s << child.layout.description(indent + 6); s << std::string(indent + 4, ' ') << "}," << std::endl; } s << std::string(indent + 2, ' ') << "]" << std::endl; } s << std::string(indent, ' ') << "}" << std::endl; return s.str(); } static void _deleteComponentLayoutChild(void *target) noexcept { delete (std::vector<RCLayoutChild> *)target; } void CKOffMainThreadDeleter::operator()(std::vector<RCLayoutChild> *target) noexcept { // When deallocating a large layout tree this is called first on the root node // so we dispatch once and deallocate the whole tree on a background thread. // However, if you have a RCLayout as an ivar/variable, it will be initialized // with the default contstructor and an empty vector. When you set the ivar, this method is called // to deallocate the empty layout, and in this case it's not worth doing the dispatch. if ([NSThread isMainThread] && target && !target->empty()) { // use dispatch_async_f to avoid block allocations dispatch_async_f(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), target, &_deleteComponentLayoutChild); } else { delete target; } } std::shared_ptr<const std::vector<RCLayoutChild>> RCLayout::emptyChildren() noexcept { static std::shared_ptr<const std::vector<RCLayoutChild>> cached(new std::vector<RCLayoutChild>()); return cached; } NSSet<id<CKMountable>> *CKMountLayout(const RCLayout &layout, UIView *view, NSSet<id<CKMountable>> *previouslyMountedComponents, id<CKMountable> supercomponent, CK::Component::MountAnalyticsContext *mountAnalyticsContext, id<CKMountLayoutListener> listener) { struct MountItem { const RCLayout &layout; MountContext mountContext; id<CKMountable> supercomponent; BOOL visited; }; // Using a stack to mount ensures that the components are mounted // in a DFS fashion which is handy if you want to animate a subpart // of the tree std::stack<MountItem> stack; stack.push({layout, MountContext::RootContext(view, mountAnalyticsContext), supercomponent, NO}); auto const mountedComponents = CK::makeNonNull([NSMutableSet set]); while (!stack.empty()) { MountItem &item = stack.top(); if (item.visited) { if (auto const c = item.layout.component) { [c childrenDidMount]; [listener didMountComponent:c]; } stack.pop(); } else { item.visited = YES; if (item.layout.component == nil) { continue; // Nil components in a layout struct are invalid, but handle them gracefully } [listener willMountComponent:item.layout.component]; const MountResult mountResult = [item.layout.component mountInContext:item.mountContext layout:item.layout supercomponent:item.supercomponent]; [mountedComponents addObject:item.layout.component]; if (mountResult.mountChildren) { // Ordering of components should correspond to ordering of mount. Push components on backwards so the // bottom-most component is mounted first. for (auto riter = item.layout.children->rbegin(); riter != item.layout.children->rend(); riter ++) { stack.push({riter->layout, mountResult.contextForChildren.offset(riter->position, item.layout.size, riter->layout.size), item.layout.component, NO}); } } } } // Unmount any components that were in previouslyMountedComponents but are no longer in mountedComponents. for (id<CKMountable> component in previouslyMountedComponents) { if (![mountedComponents containsObject:component]) { [component unmount]; } } return mountedComponents; } void CKUnmountComponents(NSSet<id<CKMountable>> *componentsToUnmount) { for (id<CKMountable> component in componentsToUnmount) { [component unmount]; } }