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 | |