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