UINT32 PrefabUtility::generatePrefabIds(const HSceneObject& sceneObject, UINT32 startingId) { Stack<HSceneObject> todo; todo.push(sceneObject); while (!todo.empty()) { HSceneObject currentSO = todo.top(); todo.pop(); for (auto& component : currentSO->mComponents) { if (component->getLinkId() == (UINT32)-1) component->mLinkId = startingId++; } UINT32 numChildren = (UINT32)currentSO->getNumChildren(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = currentSO->getChild(i); if (!child->hasFlag(SOF_DontSave)) { if (child->getLinkId() == (UINT32)-1) child->mLinkId = startingId++; if(child->mPrefabLinkUUID.empty()) todo.push(currentSO->getChild(i)); } } } return startingId; }
void PrefabUtility::restoreLinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData) { Stack<HSceneObject> todo; todo.push(so); // Root is not in the instance data map because its link ID belongs to the parent prefab, if any so->_setInstanceData(proxy.instanceData); while (!todo.empty()) { HSceneObject current = todo.top(); todo.pop(); Vector<HComponent>& components = current->mComponents; for (auto& component : components) { if (component->getLinkId() != (UINT32)-1) { auto iterFind = linkedInstanceData.find(component->getLinkId()); if (iterFind != linkedInstanceData.end()) { component->_setInstanceData(iterFind->second); component._setHandleData(component.getInternalPtr()); } } } UINT32 numChildren = current->getNumChildren(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = current->getChild(i); if (child->getLinkId() != (UINT32)-1) { auto iterFind = linkedInstanceData.find(child->getLinkId()); if (iterFind != linkedInstanceData.end()) child->_setInstanceData(iterFind->second); } if (child->mPrefabLinkUUID.empty()) todo.push(child); } } }
void PrefabUtility::recordInstanceData(const HSceneObject& so, SceneObjectProxy& output, UnorderedMap<UINT32, GameObjectInstanceDataPtr>& linkedInstanceData) { struct StackData { HSceneObject so; SceneObjectProxy* proxy; }; Stack<StackData> todo; todo.push({so, &output}); output.instanceData = so->_getInstanceData(); output.linkId = (UINT32)-1; while (!todo.empty()) { StackData curData = todo.top(); todo.pop(); const Vector<HComponent>& components = curData.so->getComponents(); for (auto& component : components) { curData.proxy->components.push_back(ComponentProxy()); ComponentProxy& componentProxy = curData.proxy->components.back(); componentProxy.instanceData = component->_getInstanceData(); componentProxy.linkId = component->getLinkId(); linkedInstanceData[componentProxy.linkId] = componentProxy.instanceData; } UINT32 numChildren = curData.so->getNumChildren(); curData.proxy->children.resize(numChildren); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = curData.so->getChild(i); SceneObjectProxy& childProxy = curData.proxy->children[i]; childProxy.instanceData = child->_getInstanceData(); childProxy.linkId = child->getLinkId(); linkedInstanceData[childProxy.linkId] = childProxy.instanceData; if (child->mPrefabLinkUUID.empty()) { todo.push({ child, &curData.proxy->children[i] }); } } } }
void PrefabUtility::updateFromPrefab(const HSceneObject& so) { HSceneObject topLevelObject = so; while (topLevelObject != nullptr) { if (!topLevelObject->mPrefabLinkUUID.empty()) break; if (topLevelObject->mParent != nullptr) topLevelObject = topLevelObject->mParent; else topLevelObject = nullptr; } Stack<HSceneObject> todo; todo.push(topLevelObject); // Find any prefab instances Vector<HSceneObject> prefabInstanceRoots; while (!todo.empty()) { HSceneObject current = todo.top(); todo.pop(); if (!current->mPrefabLinkUUID.empty()) prefabInstanceRoots.push_back(current); UINT32 childCount = current->getNumChildren(); for (UINT32 i = 0; i < childCount; i++) { HSceneObject child = current->getChild(i); todo.push(child); } } // Stores data about the new prefab instance and its original parent and link id // (as those aren't stored in the prefab diff) struct RestoredPrefabInstance { HSceneObject newInstance; HSceneObject originalParent; SPtr<PrefabDiff> diff; UINT32 originalLinkId; }; Vector<RestoredPrefabInstance> newPrefabInstanceData; // For each prefab instance load its reference prefab from the disk and check if it changed. If it has changed // instantiate the prefab and destroy the current instance. Then apply instance specific changes stored in a // prefab diff, if any, as well as restore the original parent and link id (link id of the root prefab instance // belongs to the parent prefab if any). Finally fix any handles pointing to the old objects so that they now point // to the newly instantiated objects. To the outside world it should be transparent that we just destroyed and then // re-created from scratch the entire hierarchy. // Need to do this bottom up to ensure I don't destroy the parents before children for (auto iter = prefabInstanceRoots.rbegin(); iter != prefabInstanceRoots.rend(); ++iter) { HSceneObject current = *iter; HPrefab prefabLink = static_resource_cast<Prefab>(gResources().loadFromUUID(current->mPrefabLinkUUID, false, false)); if (prefabLink.isLoaded(false) && prefabLink->getHash() != current->mPrefabHash) { // Save IDs, destroy original, create new, restore IDs SceneObjectProxy soProxy; UnorderedMap<UINT32, GameObjectInstanceDataPtr> linkedInstanceData; recordInstanceData(current, soProxy, linkedInstanceData); HSceneObject parent = current->getParent(); SPtr<PrefabDiff> prefabDiff = current->mPrefabDiff; current->destroy(true); HSceneObject newInstance = prefabLink->_clone(); // When restoring instance IDs it is important to make all the new handles point to the old GameObjectInstanceData. // This is because old handles will have different GameObjectHandleData and we have no easy way of accessing it to // change to which GameObjectInstanceData it points. But the GameObjectManager ensures that all handles deserialized // at once (i.e. during the ::instantiate() call above) will share GameObjectHandleData so we can simply replace // to what they point to, affecting all of the handles to that object. (In another words, we can modify the // new handles at this point, but old ones must keep referencing what they already were.) restoreLinkedInstanceData(newInstance, soProxy, linkedInstanceData); restoreUnlinkedInstanceData(newInstance, soProxy); newPrefabInstanceData.push_back({ newInstance, parent, prefabDiff, newInstance->getLinkId() }); } } // Once everything is instantiated, apply diffs, restore old parents & link IDs for root. for (auto& entry : newPrefabInstanceData) { // Diffs must be applied after everything is instantiated and instance data restored since it may contain // game object handles within or external to its prefab instance. if (entry.diff != nullptr) entry.diff->apply(entry.newInstance); entry.newInstance->mPrefabDiff = entry.diff; entry.newInstance->setParent(entry.originalParent, false); entry.newInstance->mLinkId = entry.originalLinkId; } gResources().unloadAllUnused(); }
void PrefabUtility::restoreUnlinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy) { struct StackEntry { HSceneObject so; SceneObjectProxy* proxy; }; Stack<StackEntry> todo; todo.push(StackEntry()); StackEntry& topEntry = todo.top(); topEntry.so = so; topEntry.proxy = &proxy; while (!todo.empty()) { StackEntry current = todo.top(); todo.pop(); if (current.proxy->linkId == -1) current.so->_setInstanceData(current.proxy->instanceData); Vector<HComponent>& components = current.so->mComponents; UINT32 componentProxyIdx = 0; UINT32 numComponentProxies = (UINT32)current.proxy->components.size(); for (auto& component : components) { if (component->getLinkId() == (UINT32)-1) { bool foundInstanceData = false; for (; componentProxyIdx < numComponentProxies; componentProxyIdx++) { if (current.proxy->components[componentProxyIdx].linkId != -1) continue; component->_setInstanceData(current.proxy->components[componentProxyIdx].instanceData); component._setHandleData(component.getInternalPtr()); foundInstanceData = true; break; } assert(foundInstanceData); } } UINT32 numChildren = current.so->getNumChildren(); UINT32 childProxyIdx = 0; UINT32 numChildProxies = (UINT32)current.proxy->children.size(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = current.so->getChild(i); if (child->getLinkId() == (UINT32)-1) { bool foundInstanceData = false; for (; childProxyIdx < numChildProxies; childProxyIdx++) { if (current.proxy->children[childProxyIdx].linkId != -1) continue; assert(current.proxy->children[childProxyIdx].linkId == -1); child->_setInstanceData(current.proxy->children[childProxyIdx].instanceData); if (child->mPrefabLinkUUID.empty()) { todo.push(StackEntry()); StackEntry& newEntry = todo.top(); newEntry.so = child; newEntry.proxy = ¤t.proxy->children[childProxyIdx]; } foundInstanceData = true; break; } assert(foundInstanceData); } else { if (!child->mPrefabLinkUUID.empty()) continue; for (UINT32 j = 0; j < numChildProxies; j++) { if (child->getLinkId() == current.proxy->children[j].linkId) { todo.push(StackEntry()); StackEntry& newEntry = todo.top(); newEntry.so = child; newEntry.proxy = ¤t.proxy->children[j]; break; } } } } } }
void CmdBreakPrefab::commit() { clear(); if (mSceneObject == nullptr || mSceneObject.isDestroyed()) return; HSceneObject rootObj = mSceneObject; while (rootObj != nullptr) { if (!rootObj->_getPrefabLinkUUID().empty()) break; if (rootObj->getParent() != nullptr) rootObj = rootObj->getParent(); else rootObj = nullptr; } if (rootObj != nullptr) { mPrefabRoot = rootObj; mPrefabLinkUUID = rootObj->_getPrefabLinkUUID(); mPrefabDiff = rootObj->_getPrefabDiff(); Stack<HSceneObject> todo; todo.push(mPrefabRoot); while (!todo.empty()) { HSceneObject currentSO = todo.top(); todo.pop(); const Vector<HComponent>& components = currentSO->getComponents(); for (auto& component : components) { UINT32 linkId = component->getLinkId(); if (linkId != (UINT32)-1) mLinkIds[component->getInstanceId()] = linkId; mLinkIds[component->getInstanceId()] = component->getLinkId(); } UINT32 numChildren = (UINT32)currentSO->getNumChildren(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = currentSO->getChild(i); UINT32 linkId = child->getLinkId(); if (linkId != (UINT32)-1) mLinkIds[child->getInstanceId()] = linkId; if (child->_getPrefabLinkUUID().empty()) todo.push(child); } } } mSceneObject->breakPrefabLink(); }
void PrefabDiff::applyDiff(const SPtr<PrefabObjectDiff>& diff, const HSceneObject& object) { if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Name) != 0) object->setName(diff->name); if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Position) != 0) object->setPosition(diff->position); if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Rotation) != 0) object->setRotation(diff->rotation); if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Scale) != 0) object->setScale(diff->scale); if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Active) != 0) object->setActive(diff->isActive); // Note: It is important to remove objects and components first, before adding them. // Some systems rely on the fact that applyDiff added components/objects are // always at the end. const Vector<HComponent>& components = object->getComponents(); for (auto& removedId : diff->removedComponents) { for (auto component : components) { if (removedId == component->getLinkId()) { component->destroy(); break; } } } for (auto& removedId : diff->removedChildren) { UINT32 childCount = object->getNumChildren(); for (UINT32 i = 0; i < childCount; i++) { HSceneObject child = object->getChild(i); if (removedId == child->getLinkId()) { child->destroy(); break; } } } for (auto& addedComponentData : diff->addedComponents) { BinarySerializer bs; SPtr<Component> component = std::static_pointer_cast<Component>(bs._decodeFromIntermediate(addedComponentData)); object->addAndInitializeComponent(component); } for (auto& addedChildData : diff->addedChildren) { BinarySerializer bs; SPtr<SceneObject> sceneObject = std::static_pointer_cast<SceneObject>(bs._decodeFromIntermediate(addedChildData)); sceneObject->setParent(object); if(object->isInstantiated()) sceneObject->_instantiate(); } for (auto& componentDiff : diff->componentDiffs) { for (auto& component : components) { if (componentDiff->id == component->getLinkId()) { IDiff& diffHandler = component->getRTTI()->getDiffHandler(); diffHandler.applyDiff(component.getInternalPtr(), componentDiff->data); break; } } } for (auto& childDiff : diff->childDiffs) { UINT32 childCount = object->getNumChildren(); for (UINT32 i = 0; i < childCount; i++) { HSceneObject child = object->getChild(i); if (childDiff->id == child->getLinkId()) { applyDiff(childDiff, child); break; } } } }
void PrefabDiff::renameInstanceIds(const HSceneObject& prefab, const HSceneObject& instance, Vector<RenamedGameObject>& output) { UnorderedMap<String, UnorderedMap<UINT32, UINT64>> linkToInstanceId; struct StackEntry { HSceneObject so; String uuid; }; // When renaming it is important to rename the prefab and not the instance, since the diff will otherwise // contain prefab's IDs, but will be used for the instance. Stack<StackEntry> todo; todo.push({ instance, "root" }); while (!todo.empty()) { StackEntry current = todo.top(); todo.pop(); String childParentUUID; if (current.so->mPrefabLinkUUID.empty()) childParentUUID = current.uuid; else childParentUUID = current.so->mPrefabLinkUUID; UnorderedMap<UINT32, UINT64>& idMap = linkToInstanceId[childParentUUID]; const Vector<HComponent>& components = current.so->getComponents(); for (auto& component : components) { if (component->getLinkId() != (UINT32)-1) idMap[component->getLinkId()] = component->getInstanceId(); } UINT32 numChildren = current.so->getNumChildren(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = current.so->getChild(i); if (child->getLinkId() != (UINT32)-1) idMap[child->getLinkId()] = child->getInstanceId(); todo.push({ child, childParentUUID }); } } // Root has link ID from its parent so we handle it separately { output.push_back(RenamedGameObject()); RenamedGameObject& renamedGO = output.back(); renamedGO.instanceData = instance->mInstanceData; renamedGO.originalId = instance->getInstanceId(); prefab->mInstanceData->mInstanceId = instance->getInstanceId(); } todo.push({ prefab, "root" }); while (!todo.empty()) { StackEntry current = todo.top(); todo.pop(); String childParentUUID; if (current.so->mPrefabLinkUUID.empty()) childParentUUID = current.uuid; else childParentUUID = current.so->mPrefabLinkUUID; auto iterFind = linkToInstanceId.find(childParentUUID); if (iterFind != linkToInstanceId.end()) { UnorderedMap<UINT32, UINT64>& idMap = iterFind->second; const Vector<HComponent>& components = current.so->getComponents(); for (auto& component : components) { auto iterFind2 = idMap.find(component->getLinkId()); if (iterFind2 != idMap.end()) { output.push_back(RenamedGameObject()); RenamedGameObject& renamedGO = output.back(); renamedGO.instanceData = component->mInstanceData; renamedGO.originalId = component->getInstanceId(); component->mInstanceData->mInstanceId = iterFind2->second; } } } UINT32 numChildren = current.so->getNumChildren(); for (UINT32 i = 0; i < numChildren; i++) { HSceneObject child = current.so->getChild(i); if (iterFind != linkToInstanceId.end()) { if (child->getLinkId() != -1) { UnorderedMap<UINT32, UINT64>& idMap = iterFind->second; auto iterFind2 = idMap.find(child->getLinkId()); if (iterFind2 != idMap.end()) { output.push_back(RenamedGameObject()); RenamedGameObject& renamedGO = output.back(); renamedGO.instanceData = child->mInstanceData; renamedGO.originalId = child->getInstanceId(); child->mInstanceData->mInstanceId = iterFind2->second; } } } todo.push({ child, childParentUUID }); } } }
SPtr<PrefabObjectDiff> PrefabDiff::generateDiff(const HSceneObject& prefab, const HSceneObject& instance) { SPtr<PrefabObjectDiff> output; if (prefab->getName() != instance->getName()) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->name = instance->getName(); output->soFlags |= (UINT32)SceneObjectDiffFlags::Name; } if (prefab->getPosition() != instance->getPosition()) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->position = instance->getPosition(); output->soFlags |= (UINT32)SceneObjectDiffFlags::Position; } if (prefab->getRotation() != instance->getRotation()) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->rotation = instance->getRotation(); output->soFlags |= (UINT32)SceneObjectDiffFlags::Rotation; } if (prefab->getScale() != instance->getScale()) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->scale = instance->getScale(); output->soFlags |= (UINT32)SceneObjectDiffFlags::Scale; } if (prefab->getActive() != instance->getActive()) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->isActive = instance->getActive(); output->soFlags |= (UINT32)SceneObjectDiffFlags::Active; } UINT32 prefabChildCount = prefab->getNumChildren(); UINT32 instanceChildCount = instance->getNumChildren(); // Find modified and removed children for (UINT32 i = 0; i < prefabChildCount; i++) { HSceneObject prefabChild = prefab->getChild(i); SPtr<PrefabObjectDiff> childDiff; bool foundMatching = false; for (UINT32 j = 0; j < instanceChildCount; j++) { HSceneObject instanceChild = instance->getChild(j); if (prefabChild->getLinkId() == instanceChild->getLinkId()) { if (instanceChild->mPrefabLinkUUID.empty()) childDiff = generateDiff(prefabChild, instanceChild); foundMatching = true; break; } } if (foundMatching) { if (childDiff != nullptr) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->childDiffs.push_back(childDiff); } } else { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->removedChildren.push_back(prefabChild->getLinkId()); } } // Find added children for (UINT32 i = 0; i < instanceChildCount; i++) { HSceneObject instanceChild = instance->getChild(i); if (instanceChild->hasFlag(SOF_DontSave)) continue; bool foundMatching = false; if (instanceChild->getLinkId() != -1) { for (UINT32 j = 0; j < prefabChildCount; j++) { HSceneObject prefabChild = prefab->getChild(j); if (prefabChild->getLinkId() == instanceChild->getLinkId()) { foundMatching = true; break; } } } if (!foundMatching) { BinarySerializer bs; SPtr<SerializedObject> obj = bs._encodeToIntermediate(instanceChild.get()); if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->addedChildren.push_back(obj); } } const Vector<HComponent>& prefabComponents = prefab->getComponents(); const Vector<HComponent>& instanceComponents = instance->getComponents(); UINT32 prefabComponentCount = (UINT32)prefabComponents.size(); UINT32 instanceComponentCount = (UINT32)instanceComponents.size(); // Find modified and removed components for (UINT32 i = 0; i < prefabComponentCount; i++) { HComponent prefabComponent = prefabComponents[i]; SPtr<PrefabComponentDiff> childDiff; bool foundMatching = false; for (UINT32 j = 0; j < instanceComponentCount; j++) { HComponent instanceComponent = instanceComponents[j]; if (prefabComponent->getLinkId() == instanceComponent->getLinkId()) { BinarySerializer bs; SPtr<SerializedObject> encodedPrefab = bs._encodeToIntermediate(prefabComponent.get()); SPtr<SerializedObject> encodedInstance = bs._encodeToIntermediate(instanceComponent.get()); IDiff& diffHandler = prefabComponent->getRTTI()->getDiffHandler(); SPtr<SerializedObject> diff = diffHandler.generateDiff(encodedPrefab, encodedInstance); if (diff != nullptr) { childDiff = bs_shared_ptr_new<PrefabComponentDiff>(); childDiff->id = prefabComponent->getLinkId(); childDiff->data = diff; } foundMatching = true; break; } } if (foundMatching) { if (childDiff != nullptr) { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->componentDiffs.push_back(childDiff); } } else { if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->removedComponents.push_back(prefabComponent->getLinkId()); } } // Find added components for (UINT32 i = 0; i < instanceComponentCount; i++) { HComponent instanceComponent = instanceComponents[i]; bool foundMatching = false; if (instanceComponent->getLinkId() != -1) { for (UINT32 j = 0; j < prefabComponentCount; j++) { HComponent prefabComponent = prefabComponents[j]; if (prefabComponent->getLinkId() == instanceComponent->getLinkId()) { foundMatching = true; break; } } } if (!foundMatching) { BinarySerializer bs; SPtr<SerializedObject> obj = bs._encodeToIntermediate(instanceComponent.get()); if (output == nullptr) output = bs_shared_ptr_new<PrefabObjectDiff>(); output->addedComponents.push_back(obj); } } if (output != nullptr) output->id = instance->getLinkId(); return output; }