// Copyright 2004-present Facebook. All Rights Reserved. #include "Differentiator.h" namespace facebook { namespace react { void calculateMutationInstructions( TreeMutationInstructionList &instructions, SharedShadowNode parentNode, SharedShadowNodeSharedList oldChildNodes, SharedShadowNodeSharedList newChildNodes ) { // The current version of the algorithm is otimized for simplicity, // not for performance of optimal result. // TODO(shergin): Consider to use Minimal Edit Distance algorithm to produce // optimal set of instructions and improve mounting performance. // https://en.wikipedia.org/wiki/Edit_distance // https://www.geeksforgeeks.org/dynamic-programming-set-5-edit-distance/ if (oldChildNodes == newChildNodes) { return; } if (oldChildNodes->size() == 0 && newChildNodes->size() == 0) { return; } std::unordered_set insertedTags = {}; int index = 0; TreeMutationInstructionList createInstructions = {}; TreeMutationInstructionList deleteInstructions = {}; TreeMutationInstructionList insertInstructions = {}; TreeMutationInstructionList removeInstructions = {}; TreeMutationInstructionList replaceInstructions = {}; TreeMutationInstructionList downwardInstructions = {}; // Stage 1: Collectings Updates for (index = 0; index < oldChildNodes->size() && index < newChildNodes->size(); index++) { SharedShadowNode oldChildNode = oldChildNodes->at(index); SharedShadowNode newChildNode = newChildNodes->at(index); if (oldChildNode->getTag() != newChildNode->getTag()) { // Totally different nodes, updating is impossible. break; } if (*oldChildNode != *newChildNode) { replaceInstructions.push_back( TreeMutationInstruction::Replace( parentNode, oldChildNode, newChildNode, index ) ); } calculateMutationInstructions( downwardInstructions, oldChildNode, oldChildNode->getChildren(), newChildNode->getChildren() ); } int lastIndexAfterFirstStage = index; // Stage 2: Collectings Insertions for (; index < newChildNodes->size(); index++) { SharedShadowNode newChildNode = newChildNodes->at(index); insertInstructions.push_back( TreeMutationInstruction::Insert( parentNode, newChildNodes->at(index), index ) ); insertedTags.insert(newChildNode->getTag()); SharedShadowNode newChildSourceNode = newChildNode->getSourceNode(); SharedShadowNodeSharedList newChildSourceChildNodes = newChildSourceNode ? newChildSourceNode->getChildren() : ShadowNode::emptySharedShadowNodeSharedList(); calculateMutationInstructions( downwardInstructions, newChildNode, newChildSourceChildNodes, newChildNode->getChildren() ); } // Stage 3: Collectings Deletions and Removals for (index = lastIndexAfterFirstStage; index < oldChildNodes->size(); index++) { SharedShadowNode oldChildNode = oldChildNodes->at(index); auto numberOfRemovedTags = insertedTags.erase(oldChildNode->getTag()); assert(numberOfRemovedTags == 0 || numberOfRemovedTags == 1); if (numberOfRemovedTags != 0) { // The old node *was* (re)inserted, // so we have to generate `remove` instruction. removeInstructions.push_back( TreeMutationInstruction::Remove( parentNode, oldChildNode, index ) ); } else { // The old node was *not* (re)inserted, // so we have to generate `delete` instruction and apply the algorithm // recursively. deleteInstructions.push_back( TreeMutationInstruction::Delete( oldChildNode ) ); calculateMutationInstructions( downwardInstructions, oldChildNode, oldChildNode->getChildren(), ShadowNode::emptySharedShadowNodeSharedList() ); } } // Stage 4: Collectings Creations for (index = lastIndexAfterFirstStage; index < newChildNodes->size(); index++) { SharedShadowNode newChildNode = newChildNodes->at(index); if (insertedTags.find(newChildNode->getTag()) == insertedTags.end()) { // The new node was (re)inserted, so there is no need to create it. continue; } createInstructions.push_back( TreeMutationInstruction::Create( newChildNode ) ); } // All instructions in an optimal order: instructions.insert(instructions.end(), replaceInstructions.begin(), replaceInstructions.end()); instructions.insert(instructions.end(), removeInstructions.begin(), removeInstructions.end()); instructions.insert(instructions.end(), deleteInstructions.begin(), deleteInstructions.end()); instructions.insert(instructions.end(), createInstructions.begin(), createInstructions.end()); instructions.insert(instructions.end(), insertInstructions.begin(), insertInstructions.end()); instructions.insert(instructions.end(), downwardInstructions.begin(), downwardInstructions.end()); } } // namespace react } // namespace facebook