| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "Scene/BsPrefabDiff.h" |
| 4 | #include "Private/RTTI/BsPrefabDiffRTTI.h" |
| 5 | #include "Scene/BsSceneObject.h" |
| 6 | #include "Serialization/BsMemorySerializer.h" |
| 7 | #include "Serialization/BsBinarySerializer.h" |
| 8 | #include "Serialization/BsBinaryDiff.h" |
| 9 | #include "Scene/BsSceneManager.h" |
| 10 | #include "Utility/BsUtility.h" |
| 11 | |
| 12 | namespace bs |
| 13 | { |
| 14 | RTTITypeBase* PrefabComponentDiff::getRTTIStatic() |
| 15 | { |
| 16 | return PrefabComponentDiffRTTI::instance(); |
| 17 | } |
| 18 | |
| 19 | RTTITypeBase* PrefabComponentDiff::getRTTI() const |
| 20 | { |
| 21 | return PrefabComponentDiff::getRTTIStatic(); |
| 22 | } |
| 23 | |
| 24 | RTTITypeBase* PrefabObjectDiff::getRTTIStatic() |
| 25 | { |
| 26 | return PrefabObjectDiffRTTI::instance(); |
| 27 | } |
| 28 | |
| 29 | RTTITypeBase* PrefabObjectDiff::getRTTI() const |
| 30 | { |
| 31 | return PrefabObjectDiff::getRTTIStatic(); |
| 32 | } |
| 33 | |
| 34 | SPtr<PrefabDiff> PrefabDiff::create(const HSceneObject& prefab, const HSceneObject& instance) |
| 35 | { |
| 36 | if (prefab->mPrefabLinkUUID != instance->mPrefabLinkUUID) |
| 37 | return nullptr; |
| 38 | |
| 39 | // Note: If this method is called multiple times in a row then renaming all objects every time is redundant, it |
| 40 | // would be more efficient to do it once outside of this method. I'm keeping it this way for simplicity for now. |
| 41 | |
| 42 | // Rename instance objects so they share the same IDs as the prefab objects (if they link IDs match). This allows |
| 43 | // game object handle diff to work properly, because otherwise handles that point to same objects would be |
| 44 | // marked as different because the instance IDs of the two objects don't match (since one is in prefab and one |
| 45 | // in instance). |
| 46 | Vector<RenamedGameObject> renamedObjects; |
| 47 | renameInstanceIds(prefab, instance, renamedObjects); |
| 48 | |
| 49 | SPtr<PrefabDiff> output = bs_shared_ptr_new<PrefabDiff>(); |
| 50 | output->mRoot = generateDiff(prefab, instance); |
| 51 | |
| 52 | restoreInstanceIds(renamedObjects); |
| 53 | |
| 54 | return output; |
| 55 | } |
| 56 | |
| 57 | void PrefabDiff::apply(const HSceneObject& object) |
| 58 | { |
| 59 | if (mRoot == nullptr) |
| 60 | return; |
| 61 | |
| 62 | CoreSerializationContext serzContext; |
| 63 | serzContext.goState = bs_shared_ptr_new<GameObjectDeserializationState>(GODM_UseNewIds | GODM_RestoreExternal); |
| 64 | serzContext.goDeserializationActive = true; |
| 65 | |
| 66 | applyDiff(mRoot, object, &serzContext); |
| 67 | |
| 68 | serzContext.goState->resolve(); |
| 69 | } |
| 70 | |
| 71 | void PrefabDiff::applyDiff(const SPtr<PrefabObjectDiff>& diff, const HSceneObject& object, |
| 72 | SerializationContext* context) |
| 73 | { |
| 74 | if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Name) != 0) |
| 75 | object->setName(diff->name); |
| 76 | |
| 77 | if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Position) != 0) |
| 78 | object->setPosition(diff->position); |
| 79 | |
| 80 | if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Rotation) != 0) |
| 81 | object->setRotation(diff->rotation); |
| 82 | |
| 83 | if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Scale) != 0) |
| 84 | object->setScale(diff->scale); |
| 85 | |
| 86 | if ((diff->soFlags & (UINT32)SceneObjectDiffFlags::Active) != 0) |
| 87 | object->setActive(diff->isActive); |
| 88 | |
| 89 | // Note: It is important to remove objects and components first, before adding them. |
| 90 | // Some systems rely on the fact that applyDiff added components/objects are |
| 91 | // always at the end. |
| 92 | const Vector<HComponent>& components = object->getComponents(); |
| 93 | for (auto& removedId : diff->removedComponents) |
| 94 | { |
| 95 | for (auto component : components) |
| 96 | { |
| 97 | if (removedId == component->getLinkId()) |
| 98 | { |
| 99 | component->destroy(true); |
| 100 | break; |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | for (auto& removedId : diff->removedChildren) |
| 106 | { |
| 107 | UINT32 childCount = object->getNumChildren(); |
| 108 | for (UINT32 i = 0; i < childCount; i++) |
| 109 | { |
| 110 | HSceneObject child = object->getChild(i); |
| 111 | if (removedId == child->getLinkId()) |
| 112 | { |
| 113 | child->destroy(true); |
| 114 | break; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | for (auto& addedComponentData : diff->addedComponents) |
| 120 | { |
| 121 | SPtr<Component> component = std::static_pointer_cast<Component>(addedComponentData->decode(context)); |
| 122 | |
| 123 | object->addAndInitializeComponent(component); |
| 124 | } |
| 125 | |
| 126 | for (auto& addedChildData : diff->addedChildren) |
| 127 | { |
| 128 | SPtr<SceneObject> sceneObject = std::static_pointer_cast<SceneObject>(addedChildData->decode(context)); |
| 129 | sceneObject->setParent(object); |
| 130 | |
| 131 | if(object->isInstantiated()) |
| 132 | sceneObject->_instantiate(); |
| 133 | } |
| 134 | |
| 135 | for (auto& componentDiff : diff->componentDiffs) |
| 136 | { |
| 137 | for (auto& component : components) |
| 138 | { |
| 139 | if (componentDiff->id == (INT32)component->getLinkId()) |
| 140 | { |
| 141 | IDiff& diffHandler = component->getRTTI()->getDiffHandler(); |
| 142 | diffHandler.applyDiff(component.getInternalPtr(), componentDiff->data, context); |
| 143 | break; |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | for (auto& childDiff : diff->childDiffs) |
| 149 | { |
| 150 | UINT32 childCount = object->getNumChildren(); |
| 151 | for (UINT32 i = 0; i < childCount; i++) |
| 152 | { |
| 153 | HSceneObject child = object->getChild(i); |
| 154 | if (childDiff->id == child->getLinkId()) |
| 155 | { |
| 156 | applyDiff(childDiff, child, context); |
| 157 | break; |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | SPtr<PrefabObjectDiff> PrefabDiff::generateDiff(const HSceneObject& prefab, const HSceneObject& instance) |
| 164 | { |
| 165 | SPtr<PrefabObjectDiff> output; |
| 166 | |
| 167 | if (prefab->getName() != instance->getName()) |
| 168 | { |
| 169 | if (output == nullptr) |
| 170 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 171 | |
| 172 | output->name = instance->getName(); |
| 173 | output->soFlags |= (UINT32)SceneObjectDiffFlags::Name; |
| 174 | } |
| 175 | |
| 176 | const Transform& prefabTfrm = prefab->getLocalTransform(); |
| 177 | const Transform& instanceTfrm = instance->getLocalTransform(); |
| 178 | if (prefabTfrm.getPosition() != instanceTfrm.getPosition()) |
| 179 | { |
| 180 | if (output == nullptr) |
| 181 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 182 | |
| 183 | output->position = instanceTfrm.getPosition(); |
| 184 | output->soFlags |= (UINT32)SceneObjectDiffFlags::Position; |
| 185 | } |
| 186 | |
| 187 | if (prefabTfrm.getRotation() != instanceTfrm.getRotation()) |
| 188 | { |
| 189 | if (output == nullptr) |
| 190 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 191 | |
| 192 | output->rotation = instanceTfrm.getRotation(); |
| 193 | output->soFlags |= (UINT32)SceneObjectDiffFlags::Rotation; |
| 194 | } |
| 195 | |
| 196 | if (prefabTfrm.getScale() != instanceTfrm.getScale()) |
| 197 | { |
| 198 | if (output == nullptr) |
| 199 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 200 | |
| 201 | output->scale = instanceTfrm.getScale(); |
| 202 | output->soFlags |= (UINT32)SceneObjectDiffFlags::Scale; |
| 203 | } |
| 204 | |
| 205 | if (prefab->getActive() != instance->getActive()) |
| 206 | { |
| 207 | if (output == nullptr) |
| 208 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 209 | |
| 210 | output->isActive = instance->getActive(); |
| 211 | output->soFlags |= (UINT32)SceneObjectDiffFlags::Active; |
| 212 | } |
| 213 | |
| 214 | UINT32 prefabChildCount = prefab->getNumChildren(); |
| 215 | UINT32 instanceChildCount = instance->getNumChildren(); |
| 216 | |
| 217 | // Find modified and removed children |
| 218 | for (UINT32 i = 0; i < prefabChildCount; i++) |
| 219 | { |
| 220 | HSceneObject prefabChild = prefab->getChild(i); |
| 221 | |
| 222 | SPtr<PrefabObjectDiff> childDiff; |
| 223 | bool foundMatching = false; |
| 224 | for (UINT32 j = 0; j < instanceChildCount; j++) |
| 225 | { |
| 226 | HSceneObject instanceChild = instance->getChild(j); |
| 227 | |
| 228 | if (prefabChild->getLinkId() == instanceChild->getLinkId()) |
| 229 | { |
| 230 | if (instanceChild->mPrefabLinkUUID.empty()) |
| 231 | childDiff = generateDiff(prefabChild, instanceChild); |
| 232 | |
| 233 | foundMatching = true; |
| 234 | break; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | if (foundMatching) |
| 239 | { |
| 240 | if (childDiff != nullptr) |
| 241 | { |
| 242 | if (output == nullptr) |
| 243 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 244 | |
| 245 | output->childDiffs.push_back(childDiff); |
| 246 | } |
| 247 | } |
| 248 | else |
| 249 | { |
| 250 | if (output == nullptr) |
| 251 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 252 | |
| 253 | output->removedChildren.push_back(prefabChild->getLinkId()); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // Find added children |
| 258 | for (UINT32 i = 0; i < instanceChildCount; i++) |
| 259 | { |
| 260 | HSceneObject instanceChild = instance->getChild(i); |
| 261 | |
| 262 | if (instanceChild->hasFlag(SOF_DontSave)) |
| 263 | continue; |
| 264 | |
| 265 | bool foundMatching = false; |
| 266 | if (instanceChild->getLinkId() != (UINT32)-1) |
| 267 | { |
| 268 | for (UINT32 j = 0; j < prefabChildCount; j++) |
| 269 | { |
| 270 | HSceneObject prefabChild = prefab->getChild(j); |
| 271 | |
| 272 | if (prefabChild->getLinkId() == instanceChild->getLinkId()) |
| 273 | { |
| 274 | foundMatching = true; |
| 275 | break; |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | if (!foundMatching) |
| 281 | { |
| 282 | SPtr<SerializedObject> obj = SerializedObject::create(*instanceChild); |
| 283 | |
| 284 | if (output == nullptr) |
| 285 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 286 | |
| 287 | output->addedChildren.push_back(obj); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | const Vector<HComponent>& prefabComponents = prefab->getComponents(); |
| 292 | const Vector<HComponent>& instanceComponents = instance->getComponents(); |
| 293 | |
| 294 | UINT32 prefabComponentCount = (UINT32)prefabComponents.size(); |
| 295 | UINT32 instanceComponentCount = (UINT32)instanceComponents.size(); |
| 296 | |
| 297 | // Find modified and removed components |
| 298 | for (UINT32 i = 0; i < prefabComponentCount; i++) |
| 299 | { |
| 300 | HComponent prefabComponent = prefabComponents[i]; |
| 301 | |
| 302 | SPtr<PrefabComponentDiff> childDiff; |
| 303 | bool foundMatching = false; |
| 304 | for (UINT32 j = 0; j < instanceComponentCount; j++) |
| 305 | { |
| 306 | HComponent instanceComponent = instanceComponents[j]; |
| 307 | |
| 308 | if (prefabComponent->getLinkId() == instanceComponent->getLinkId()) |
| 309 | { |
| 310 | SPtr<SerializedObject> encodedPrefab = SerializedObject::create(*prefabComponent); |
| 311 | SPtr<SerializedObject> encodedInstance = SerializedObject::create(*instanceComponent); |
| 312 | |
| 313 | IDiff& diffHandler = prefabComponent->getRTTI()->getDiffHandler(); |
| 314 | SPtr<SerializedObject> diff = diffHandler.generateDiff(encodedPrefab, encodedInstance); |
| 315 | |
| 316 | if (diff != nullptr) |
| 317 | { |
| 318 | childDiff = bs_shared_ptr_new<PrefabComponentDiff>(); |
| 319 | childDiff->id = prefabComponent->getLinkId(); |
| 320 | childDiff->data = diff; |
| 321 | } |
| 322 | |
| 323 | foundMatching = true; |
| 324 | break; |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | if (foundMatching) |
| 329 | { |
| 330 | if (childDiff != nullptr) |
| 331 | { |
| 332 | if (output == nullptr) |
| 333 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 334 | |
| 335 | output->componentDiffs.push_back(childDiff); |
| 336 | } |
| 337 | } |
| 338 | else |
| 339 | { |
| 340 | if (output == nullptr) |
| 341 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 342 | |
| 343 | output->removedComponents.push_back(prefabComponent->getLinkId()); |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | // Find added components |
| 348 | for (UINT32 i = 0; i < instanceComponentCount; i++) |
| 349 | { |
| 350 | HComponent instanceComponent = instanceComponents[i]; |
| 351 | |
| 352 | bool foundMatching = false; |
| 353 | if (instanceComponent->getLinkId() != (UINT32)-1) |
| 354 | { |
| 355 | for (UINT32 j = 0; j < prefabComponentCount; j++) |
| 356 | { |
| 357 | HComponent prefabComponent = prefabComponents[j]; |
| 358 | |
| 359 | if (prefabComponent->getLinkId() == instanceComponent->getLinkId()) |
| 360 | { |
| 361 | foundMatching = true; |
| 362 | break; |
| 363 | } |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | if (!foundMatching) |
| 368 | { |
| 369 | SPtr<SerializedObject> obj = SerializedObject::create(*instanceComponent); |
| 370 | |
| 371 | if (output == nullptr) |
| 372 | output = bs_shared_ptr_new<PrefabObjectDiff>(); |
| 373 | |
| 374 | output->addedComponents.push_back(obj); |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | if (output != nullptr) |
| 379 | output->id = instance->getLinkId(); |
| 380 | |
| 381 | return output; |
| 382 | } |
| 383 | |
| 384 | void PrefabDiff::renameInstanceIds(const HSceneObject& prefab, const HSceneObject& instance, Vector<RenamedGameObject>& output) |
| 385 | { |
| 386 | UnorderedMap<UUID, UnorderedMap<UINT32, UINT64>> linkToInstanceId; |
| 387 | |
| 388 | struct StackEntry |
| 389 | { |
| 390 | HSceneObject so; |
| 391 | UUID uuid; |
| 392 | }; |
| 393 | |
| 394 | // When renaming it is important to rename the prefab and not the instance, since the diff will otherwise |
| 395 | // contain prefab's IDs, but will be used for the instance. |
| 396 | |
| 397 | Stack<StackEntry> todo; |
| 398 | todo.push({ instance, UUID::EMPTY }); |
| 399 | |
| 400 | while (!todo.empty()) |
| 401 | { |
| 402 | StackEntry current = todo.top(); |
| 403 | todo.pop(); |
| 404 | |
| 405 | UUID childParentUUID; |
| 406 | if (current.so->mPrefabLinkUUID.empty()) |
| 407 | childParentUUID = current.uuid; |
| 408 | else |
| 409 | childParentUUID = current.so->mPrefabLinkUUID; |
| 410 | |
| 411 | UnorderedMap<UINT32, UINT64>& idMap = linkToInstanceId[childParentUUID]; |
| 412 | |
| 413 | const Vector<HComponent>& components = current.so->getComponents(); |
| 414 | for (auto& component : components) |
| 415 | { |
| 416 | if (component->getLinkId() != (UINT32)-1) |
| 417 | idMap[component->getLinkId()] = component->getInstanceId(); |
| 418 | } |
| 419 | |
| 420 | UINT32 numChildren = current.so->getNumChildren(); |
| 421 | for (UINT32 i = 0; i < numChildren; i++) |
| 422 | { |
| 423 | HSceneObject child = current.so->getChild(i); |
| 424 | |
| 425 | if (child->getLinkId() != (UINT32)-1) |
| 426 | idMap[child->getLinkId()] = child->getInstanceId(); |
| 427 | |
| 428 | todo.push({ child, childParentUUID }); |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | // Root has link ID from its parent so we handle it separately |
| 433 | { |
| 434 | output.push_back(RenamedGameObject()); |
| 435 | RenamedGameObject& renamedGO = output.back(); |
| 436 | renamedGO.instanceData = instance->mInstanceData; |
| 437 | renamedGO.originalId = instance->getInstanceId(); |
| 438 | |
| 439 | prefab->mInstanceData->mInstanceId = instance->getInstanceId(); |
| 440 | } |
| 441 | |
| 442 | todo.push({ prefab, UUID::EMPTY }); |
| 443 | while (!todo.empty()) |
| 444 | { |
| 445 | StackEntry current = todo.top(); |
| 446 | todo.pop(); |
| 447 | |
| 448 | UUID childParentUUID; |
| 449 | if (current.so->mPrefabLinkUUID.empty()) |
| 450 | childParentUUID = current.uuid; |
| 451 | else |
| 452 | childParentUUID = current.so->mPrefabLinkUUID; |
| 453 | |
| 454 | auto iterFind = linkToInstanceId.find(childParentUUID); |
| 455 | if (iterFind != linkToInstanceId.end()) |
| 456 | { |
| 457 | UnorderedMap<UINT32, UINT64>& idMap = iterFind->second; |
| 458 | |
| 459 | const Vector<HComponent>& components = current.so->getComponents(); |
| 460 | for (auto& component : components) |
| 461 | { |
| 462 | auto iterFind2 = idMap.find(component->getLinkId()); |
| 463 | if (iterFind2 != idMap.end()) |
| 464 | { |
| 465 | output.push_back(RenamedGameObject()); |
| 466 | RenamedGameObject& renamedGO = output.back(); |
| 467 | renamedGO.instanceData = component->mInstanceData; |
| 468 | renamedGO.originalId = component->getInstanceId(); |
| 469 | |
| 470 | component->mInstanceData->mInstanceId = iterFind2->second; |
| 471 | } |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | UINT32 numChildren = current.so->getNumChildren(); |
| 476 | for (UINT32 i = 0; i < numChildren; i++) |
| 477 | { |
| 478 | HSceneObject child = current.so->getChild(i); |
| 479 | |
| 480 | if (iterFind != linkToInstanceId.end()) |
| 481 | { |
| 482 | if (child->getLinkId() != (UINT32)-1) |
| 483 | { |
| 484 | UnorderedMap<UINT32, UINT64>& idMap = iterFind->second; |
| 485 | |
| 486 | auto iterFind2 = idMap.find(child->getLinkId()); |
| 487 | if (iterFind2 != idMap.end()) |
| 488 | { |
| 489 | output.push_back(RenamedGameObject()); |
| 490 | RenamedGameObject& renamedGO = output.back(); |
| 491 | renamedGO.instanceData = child->mInstanceData; |
| 492 | renamedGO.originalId = child->getInstanceId(); |
| 493 | |
| 494 | child->mInstanceData->mInstanceId = iterFind2->second; |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | todo.push({ child, childParentUUID }); |
| 500 | } |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | void PrefabDiff::restoreInstanceIds(const Vector<RenamedGameObject>& renamedObjects) |
| 505 | { |
| 506 | for (auto& renamedGO : renamedObjects) |
| 507 | renamedGO.instanceData->mInstanceId = renamedGO.originalId; |
| 508 | } |
| 509 | |
| 510 | RTTITypeBase* PrefabDiff::getRTTIStatic() |
| 511 | { |
| 512 | return PrefabDiffRTTI::instance(); |
| 513 | } |
| 514 | |
| 515 | RTTITypeBase* PrefabDiff::getRTTI() const |
| 516 | { |
| 517 | return PrefabDiff::getRTTIStatic(); |
| 518 | } |
| 519 | } |
| 520 | |