/** * Copyright (c) 2017-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. */ package com.facebook.litho; import android.support.v4.util.LongSparseArray; /** * Utility class used to calculate the id of a {@link LayoutOutput} in the context of a * {@link LayoutState}. It keeps track of all the {@link LayoutOutput}s with the same baseId * in order to generate unique ids even if the baseId is shared by multiple LayoutOutputs. */ class LayoutStateOutputIdCalculator { private final LongSparseArray<Integer> mLayoutCurrentSequenceForBaseId = new LongSparseArray<>(8); private final LongSparseArray<Integer> mVisibilityCurrentSequenceForBaseId = new LongSparseArray<>(8); private static final int MAX_SEQUENCE = 65535; // (2^16 - 1) private static final int MAX_LEVEL = 255; // (2^8 - 1) // 16 bits are for sequence, 2 for type and 8 for level. private static final short COMPONENT_ID_SHIFT = 26; // 16 bits are sequence and then 2 for type. private static final short LEVEL_SHIFT = 18; // Last 16 bits are for sequence. private static final short TYPE_SHIFT = 16; void calculateAndSetLayoutOutputIdAndUpdateState( LayoutOutput layoutOutput, int level, @LayoutOutput.LayoutOutputType int type, long previousId, boolean isCachedOutputUpdated) { // We need to assign an id to this LayoutOutput. We want the ids to be as consistent as possible // between different layout calculations. For this reason the id generation is a function based // on the component of the LayoutOutput, the output type {@link LayoutOutput#LayoutOutputType} // the depth of this output in the view hierarchy and an incremental sequence number for // LayoutOutputs that have all the other parameters in common. long baseLayoutId = LayoutStateOutputIdCalculator.calculateLayoutOutputBaseId( layoutOutput, level, type); int sequence; if (previousId > 0 && getLevelFromId(previousId) == level) { sequence = getSequenceFromId(previousId); } else { sequence = -1; } final int currentSequence = mLayoutCurrentSequenceForBaseId.get(baseLayoutId, 0); final int layoutOutputUpdateState; // If sequence is positive we are trying to re-use the id from a previous LayoutOutput. // We can only do that if the sequence that layoutOutput used is not already assigned. if (sequence < currentSequence) { // If we failed re-using the same id from the previous LayoutOutput we default the // UpdateState to STATE_UNKNOWN sequence = currentSequence + 1; layoutOutputUpdateState = LayoutOutput.STATE_UNKNOWN; } else { // If we successfully re-used the id from a previous LayoutOutput we can also set the // UpdateState so that MountItem won't need to call shouldComponentUpdate. layoutOutputUpdateState = isCachedOutputUpdated ? LayoutOutput.STATE_UPDATED : LayoutOutput.STATE_DIRTY; } layoutOutput.setUpdateState(layoutOutputUpdateState); long layoutOutputId = LayoutStateOutputIdCalculator.calculateId(baseLayoutId, sequence); layoutOutput.setId(layoutOutputId); mLayoutCurrentSequenceForBaseId.put(baseLayoutId, sequence + 1); } void calculateAndSetVisibilityOutputId( VisibilityOutput visibilityOutput, int level, long previousId) { // We need to assign an id to this VisibilityOutput. We want the ids to be as consistent as // possible between different layout calculations. For this reason the id generation is a // function based on the component of the VisibilityOutput, the depth of this output in the view // hierarchy and an incremental sequence number for VisibilityOutputs that have all the other // parameters in common. final long baseVisibilityId = calculateVisibilityOutputBaseId( visibilityOutput, level); int sequence; if (previousId > 0 && getLevelFromId(previousId) == level) { sequence = getSequenceFromId(previousId); } else { sequence = -1; } final int currentSequence = mVisibilityCurrentSequenceForBaseId.get(baseVisibilityId, 0); // If sequence is positive we are trying to re-use the id from a previous VisibilityOutput. // We can only do that if the sequence that visibilityOutput used is not already assigned. if (sequence < currentSequence) { sequence = currentSequence + 1; } final long visibilityOutputId = calculateId(baseVisibilityId, sequence); visibilityOutput.setId(visibilityOutputId); mVisibilityCurrentSequenceForBaseId.put(baseVisibilityId, sequence + 1); } void clear() { mLayoutCurrentSequenceForBaseId.clear(); mVisibilityCurrentSequenceForBaseId.clear(); } /** * Calculates the final id for a LayoutOutput based on the baseId see * {@link LayoutStateOutputIdCalculator#calculateLayoutOutputBaseId(LayoutOutput, int, int)} and * on a sequence number. The sequence number must be guaranteed to be unique for LayoutOutputs * with the same baseId. */ static long calculateId(long baseId, int sequence) { if (sequence < 0 || sequence > MAX_SEQUENCE) { throw new IllegalArgumentException("Sequence must be non-negative and no greater than " + MAX_SEQUENCE + " actual sequence "+sequence); } return baseId | sequence; } /** * Calculates an id for a {@link LayoutOutput}. See * {@link LayoutStateOutputIdCalculator#calculateLayoutOutputBaseId(LayoutOutput, int, int)} and * {@link LayoutStateOutputIdCalculator#calculateId(long, int)}. */ static long calculateLayoutOutputId( LayoutOutput layoutOutput, int level, @LayoutOutput.LayoutOutputType int type, int sequence) { long baseId = calculateLayoutOutputBaseId(layoutOutput, level, type); return calculateId(baseId, sequence); } /** * Calculates an id for a {@link VisibilityOutput}. See * {@link LayoutStateOutputIdCalculator#calculateVisibilityOutputBaseId(VisibilityOutput, int)} * and {@link LayoutStateOutputIdCalculator#calculateId(long, int)}. */ static long calculateVisibilityOutputId( VisibilityOutput visibilityOutput, int level, int sequence) { final long baseId = calculateVisibilityOutputBaseId(visibilityOutput, level); return calculateId(baseId, sequence); } /** * @return the sequence part of an id. */ static int getSequenceFromId(long id) { return (int) id & 0x00FFFF; } /** * @return the level part of an id. */ static int getLevelFromId(long id) { return (int) ((id >> LEVEL_SHIFT) & 0xFF); } /** * Calculates a base id for an {@link LayoutOutput} based on the {@link Component}, the depth * in the View hierarchy, and the type of output see {@link LayoutOutput.LayoutOutputType}. */ private static long calculateLayoutOutputBaseId( LayoutOutput layoutOutput, int level, @LayoutOutput.LayoutOutputType int type) { if (level < 0 || level > MAX_LEVEL) { throw new IllegalArgumentException( "Level must be non-negative and no greater than " + MAX_LEVEL + " actual level " + level); } long componentId = layoutOutput.getComponent() != null ? layoutOutput.getComponent().getLifecycle().getId() : 0L; long componentShifted = componentId << COMPONENT_ID_SHIFT; long levelShifted = ((long) level) << LEVEL_SHIFT; long typeShifted = ((long) type) << TYPE_SHIFT; return 0L | componentShifted | levelShifted | typeShifted; } /** * Calculates a base id for a {@link VisibilityOutput} based on the {@link Component} and the * depth in the View hierarchy. */ private static long calculateVisibilityOutputBaseId( VisibilityOutput visibilityOutput, int level) { if (level < 0 || level > MAX_LEVEL) { throw new IllegalArgumentException( "Level must be non-negative and no greater than " + MAX_LEVEL + " actual level " + level); } final long componentId = visibilityOutput.getComponent() != null ? visibilityOutput.getComponent().getLifecycle().getId() : 0L; final long componentShifted = componentId << COMPONENT_ID_SHIFT; final long levelShifted = ((long) level) << LEVEL_SHIFT; return 0L | componentShifted | levelShifted; } }