forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: public Native view recycling implementation based on limited pools of views. In this diff I introduced new UIManager method: dropViews. Instead of removing views from tag->view maps when they are detached we keep them there until we get a call to dropViews with the appropriate tag. JS may keep a pool of object and selectively decide not to enqueue drop for certain views. Then instead of removing those views it may decide to reuse tag that has been previously allocated for a view that is no longer in use. Special handling is required for layout-only nodes as they only can transition from layout-only to non-layout-only (reverse transition hasn't been implemented). Because of that we'd loose benefits of view flattening if we decide to recycle existing non-layout-only view as a layout-only one. This diff provides only a simple and manual method for configuring pools by calling `ReactNativeViewPool.configure` with a dict from native view name to the view count. Note that we may not want recycle all the views (e.g. when we render mapview we don't want to keep it in memory after it's detached) Reviewed By: davidaurelio Differential Revision: D2677289 fb-gh-sync-id: 29f44ce5b01db3ec353522af051b6a50924614a2
- Loading branch information
Showing
11 changed files
with
342 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
/** | ||
* Copyright (c) 2015-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. | ||
* | ||
* @providesModule ReactNativeViewPool | ||
* @flow | ||
*/ | ||
'use strict'; | ||
|
||
var ReactNativeTagHandles = require('ReactNativeTagHandles'); | ||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); | ||
var RCTUIManager = require('NativeModules').UIManager; | ||
var Platform = require('Platform'); | ||
|
||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); | ||
var emptyFunction = require('emptyFunction'); | ||
var flattenStyle = require('flattenStyle'); | ||
|
||
var EMPTY_POOL = [[]]; | ||
|
||
var ENABLED = !!RCTUIManager.dropViews; | ||
|
||
/* indicies used for _addToPool arrays */ | ||
var TAGS_IDX = 0; | ||
var KEYS_IDX = 1; | ||
var PROPS_IDX = 2; | ||
|
||
var _pools = {}; | ||
var _poolSize = {}; | ||
|
||
var layoutOnlyProps = RCTUIManager.layoutOnlyProps; | ||
|
||
function isCollapsableForStyle(style) { | ||
var flatStyle = flattenStyle(style); | ||
for (var styleKey in flatStyle) { | ||
if (layoutOnlyProps[styleKey] !== true) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
function isCollapsable(viewRef) { | ||
var props = viewRef._currentElement.props; | ||
if (props.collapsable !== undefined && !props.collapsable) { | ||
return false; | ||
} | ||
var validAttributes = viewRef.viewConfig.validAttributes; | ||
for (var propKey in props) { | ||
if (!!validAttributes[propKey] && propKey !== 'style' && propKey !== 'collapsable') { | ||
return false; | ||
} | ||
} | ||
return !props.style || isCollapsableForStyle(viewRef._currentElement.props.style); | ||
} | ||
|
||
function enqueueCreate(viewRef, rootTag) { | ||
var tag = ReactNativeTagHandles.allocateTag(); | ||
|
||
if (__DEV__) { | ||
deepFreezeAndThrowOnMutationInDev(viewRef._currentElement.props); | ||
} | ||
|
||
var updatePayload = ReactNativeAttributePayload.create( | ||
viewRef._currentElement.props, | ||
viewRef.viewConfig.validAttributes | ||
); | ||
|
||
RCTUIManager.createView( | ||
tag, | ||
viewRef.viewConfig.uiViewClassName, | ||
rootTag, | ||
updatePayload | ||
); | ||
|
||
return tag; | ||
} | ||
|
||
function getViewTag(viewRef) { | ||
return ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(viewRef._rootNodeID); | ||
} | ||
|
||
function getViewProps(viewRef) { | ||
return viewRef._currentElement.props; | ||
} | ||
|
||
function getViewValidAttributes(viewRef) { | ||
return viewRef.viewConfig.validAttributes; | ||
} | ||
|
||
function getRootViewTag(viewRef) { | ||
var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(viewRef._rootNodeID); | ||
return ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID]; | ||
} | ||
|
||
function poolKey(viewRef) { | ||
var viewClass = viewRef.viewConfig.uiViewClassName; | ||
if (Platform.OS === 'android' && viewClass === 'RCTView') { | ||
return isCollapsable(viewRef) ? 'CollapsedRCTView' : 'RCTView'; | ||
} | ||
return viewClass; | ||
} | ||
|
||
class ReactNativeViewPool { | ||
constructor() { | ||
this._pool = {}; | ||
this._poolQueue = {}; | ||
this._addToPool = [[],[],[]]; | ||
this._viewsToDelete = []; | ||
if (__DEV__) { | ||
this._recycleStats = {}; | ||
this._deleteStats = {}; | ||
} | ||
} | ||
|
||
onReconcileTransactionClose() { | ||
// flush all deletes, move object from pool_queue to the actual pool | ||
if (this._viewsToDelete.length > 0) { | ||
RCTUIManager.dropViews(this._viewsToDelete); | ||
} | ||
var addToPoolTags = this._addToPool[TAGS_IDX]; | ||
var addToPoolKeys = this._addToPool[KEYS_IDX]; | ||
var addToPoolProps = this._addToPool[PROPS_IDX]; | ||
for (var i = addToPoolTags.length - 1; i >= 0; i--) { | ||
var nativeTag = addToPoolTags[i]; | ||
var key = addToPoolKeys[i]; | ||
var props = addToPoolProps[i]; | ||
var views = this._pool[key] || [[],[]]; | ||
views[0].push(nativeTag); | ||
views[1].push(props); | ||
this._pool[key] = views; | ||
} | ||
this._viewsToDelete = []; | ||
this._addToPool = [[],[],[]]; | ||
this._poolQueue = {}; | ||
} | ||
|
||
acquire(viewRef, rootTag) { | ||
var key = poolKey(viewRef); | ||
if ((this._pool[key] || EMPTY_POOL)[0].length) { | ||
var views = this._pool[key]; | ||
var nativeTag = views[0].pop(); | ||
var oldProps = views[1].pop(); | ||
var updatePayload = ReactNativeAttributePayload.diff( | ||
oldProps, | ||
getViewProps(viewRef), | ||
getViewValidAttributes(viewRef) | ||
); | ||
if (__DEV__) { | ||
this._recycleStats[key] = (this._recycleStats[key] || 0) + 1; | ||
} | ||
|
||
if (updatePayload) { | ||
RCTUIManager.updateView( | ||
nativeTag, | ||
viewRef.viewConfig.uiViewClassName, | ||
updatePayload | ||
); | ||
} | ||
return nativeTag; | ||
} else { | ||
// If there is no view available for the given pool key, we just enqueue call to create one | ||
return enqueueCreate(viewRef, rootTag); | ||
} | ||
} | ||
|
||
release(viewRef) { | ||
var key = poolKey(viewRef); | ||
var nativeTag = getViewTag(viewRef); | ||
var pooledCount = (this._pool[key] || EMPTY_POOL)[0].length + (this._poolQueue[key] || 0); | ||
if (pooledCount < (_poolSize[key] || 0)) { | ||
// we have room in the pool for this view | ||
// we can add it to the queue so that it will be added to the actual pull in | ||
// onReconcileTransactionClose | ||
this._addToPool[TAGS_IDX].push(nativeTag); | ||
this._addToPool[KEYS_IDX].push(key); | ||
this._addToPool[PROPS_IDX].push(getViewProps(viewRef)); | ||
this._poolQueue[key] = (this._poolQueue[key] || 0) + 1; | ||
} else { | ||
if (__DEV__) { | ||
if (_poolSize[key]) { | ||
this._deleteStats[key] = (this._deleteStats[key] || 0) + 1; | ||
} | ||
} | ||
this._viewsToDelete.push(nativeTag); | ||
} | ||
} | ||
|
||
clear() { | ||
for (var key in this._pool) { | ||
var poolTags = this._pool[key][0]; | ||
for (var i = poolTags.length - 1; i >= 0; i--) { | ||
this._viewsToDelete.push(poolTags[i]); | ||
} | ||
} | ||
var addToPoolTags = this._addToPool[0]; | ||
for (var i = addToPoolTags.length - 1; i >= 0; i--) { | ||
this._viewsToDelete.push(addToPoolTags[i]); | ||
} | ||
this._addToPool = [[],[],[]]; | ||
this.onReconcileTransactionClose(); | ||
} | ||
|
||
printStats() { | ||
if (__DEV__) { | ||
console.log('Stats', this._recycleStats, this._deleteStats); | ||
} | ||
} | ||
} | ||
|
||
module.exports = { | ||
|
||
onReconcileTransactionClose: function() { | ||
if (ENABLED) { | ||
for (var pool in _pools) { | ||
_pools[pool].onReconcileTransactionClose(); | ||
} | ||
} | ||
}, | ||
|
||
acquire: function(viewRef) { | ||
var rootTag = getRootViewTag(viewRef); | ||
if (ENABLED) { | ||
var pool = _pools[rootTag]; | ||
if (!pool) { | ||
pool = _pools[rootTag] = new ReactNativeViewPool(); | ||
} | ||
return pool.acquire(viewRef, rootTag); | ||
} else { | ||
return enqueueCreate(viewRef, rootTag); | ||
} | ||
}, | ||
|
||
release: ENABLED ? function(viewRef) { | ||
var pool = _pools[getRootViewTag(viewRef)]; | ||
if (pool) { | ||
pool.release(viewRef); | ||
} | ||
} : emptyFunction, | ||
|
||
clearPoolForRootView: ENABLED ? function(rootID) { | ||
var pool = _pools[rootID]; | ||
if (pool) { | ||
pool.clear(); | ||
delete _pools[rootID]; | ||
} | ||
} : emptyFunction, | ||
|
||
configure: function(pool_size) { | ||
_poolSize = pool_size; | ||
}, | ||
|
||
printStats: function() { | ||
if (__DEV__) { | ||
console.log('Pool size', _poolSize); | ||
for (var pool in _pools) { | ||
_pools[pool].onReconcileTransactionClose(); | ||
} | ||
} | ||
}, | ||
}; |
Oops, something went wrong.