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/BsPrefabUtility.h"
4#include "Scene/BsPrefabDiff.h"
5#include "Scene/BsPrefab.h"
6#include "Scene/BsSceneObject.h"
7#include "Resources/BsResources.h"
8
9namespace bs
10{
11 namespace impl
12 {
13 /** Contains saved Component instance data. */
14 struct ComponentProxy
15 {
16 GameObjectInstanceDataPtr instanceData;
17 UINT32 linkId;
18 UUID uuid;
19 };
20
21 /** Contains saved SceneObject instance data, as well as saved instance data for all its children and components. */
22 struct SceneObjectProxy
23 {
24 GameObjectInstanceDataPtr instanceData;
25 UINT32 linkId;
26 UUID uuid;
27
28 Vector<ComponentProxy> components;
29 Vector<SceneObjectProxy> children;
30 };
31
32 /** Contains linked game-object instance data and UUID. */
33 struct LinkedInstanceData
34 {
35 GameObjectInstanceDataPtr instanceData;
36 UUID uuid;
37 };
38
39 /**
40 * Traverses the object hierarchy, finds all child objects and components and records their instance data, as well
41 * as their original place in the hierarchy. Instance data essentially holds the object's "identity" and by
42 * restoring it we ensure any handles pointing to the object earlier will still point to the new version.
43 *
44 * @param[in] so Object to traverse and record.
45 * @param[out] output Contains the output hierarchy of instance data.
46 * @param[out] linkedInstanceData A map of link IDs to instance data. Objects without link IDs will not be
47 * included here.
48 *
49 * @note Does not recurse into child prefab instances.
50 */
51 void recordInstanceData(const HSceneObject& so, SceneObjectProxy& output,
52 UnorderedMap<UINT32, LinkedInstanceData>& linkedInstanceData)
53 {
54 struct StackData
55 {
56 HSceneObject so;
57 SceneObjectProxy* proxy;
58 };
59
60 Stack<StackData> todo;
61 todo.push({so, &output});
62
63 output.instanceData = so->_getInstanceData();
64 output.uuid = so->getUUID();
65 output.linkId = (UINT32)-1;
66
67 while (!todo.empty())
68 {
69 StackData curData = todo.top();
70 todo.pop();
71
72 const Vector<HComponent>& components = curData.so->getComponents();
73 for (auto& component : components)
74 {
75 curData.proxy->components.push_back(ComponentProxy());
76
77 ComponentProxy& componentProxy = curData.proxy->components.back();
78 componentProxy.instanceData = component->_getInstanceData();
79 componentProxy.uuid = component->getUUID();
80 componentProxy.linkId = component->getLinkId();
81
82 linkedInstanceData[componentProxy.linkId] = { componentProxy.instanceData, componentProxy.uuid };
83 }
84
85 UINT32 numChildren = curData.so->getNumChildren();
86 curData.proxy->children.resize(numChildren);
87
88 for (UINT32 i = 0; i < numChildren; i++)
89 {
90 HSceneObject child = curData.so->getChild(i);
91
92 SceneObjectProxy& childProxy = curData.proxy->children[i];
93
94 childProxy.instanceData = child->_getInstanceData();
95 childProxy.uuid = child->getUUID();
96 childProxy.linkId = child->getLinkId();
97
98 linkedInstanceData[childProxy.linkId] = { childProxy.instanceData, childProxy.uuid };
99
100 if (child->_getPrefabLinkUUID().empty())
101 todo.push({ child, &curData.proxy->children[i] });
102 }
103 }
104 }
105
106 /**
107 * Restores instance data in the provided hierarchy, using link ids to determine what data maps to which objects.
108 *
109 * @param[in] so Object to traverse and restore the instance data.
110 * @param[in] proxy Hierarchy containing instance data for all objects and components, returned by
111 * recordInstanceData() method.
112 * @param[in] linkedInstanceData A map of link IDs to instance data, returned by recordInstanceData() method.
113 *
114 * @note Does not recurse into child prefab instances.
115 */
116 void restoreLinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy,
117 UnorderedMap<UINT32, LinkedInstanceData>& linkedInstanceData)
118 {
119 Stack<HSceneObject> todo;
120 todo.push(so);
121
122 while (!todo.empty())
123 {
124 HSceneObject current = todo.top();
125 todo.pop();
126
127 Vector<HComponent>& components = current->_getComponents();
128 for (auto& component : components)
129 {
130 if (component->getLinkId() != (UINT32)-1)
131 {
132 auto iterFind = linkedInstanceData.find(component->getLinkId());
133 if (iterFind != linkedInstanceData.end())
134 {
135 component->_setInstanceData(iterFind->second.instanceData);
136 component->_setUUID(iterFind->second.uuid);
137 component._setHandleData(component.getInternalPtr());
138 }
139 }
140 }
141
142 UINT32 numChildren = current->getNumChildren();
143 for (UINT32 i = 0; i < numChildren; i++)
144 {
145 HSceneObject child = current->getChild(i);
146
147 if (child->getLinkId() != (UINT32)-1)
148 {
149 auto iterFind = linkedInstanceData.find(child->getLinkId());
150 if (iterFind != linkedInstanceData.end())
151 {
152 child->_setInstanceData(iterFind->second.instanceData);
153 child->_setUUID(iterFind->second.uuid);
154 }
155 }
156
157 if (child->_getPrefabLinkUUID().empty())
158 todo.push(child);
159 }
160 }
161 }
162
163 /**
164 * Restores instance data in the provided hierarchy, but only for objects without a link id. Since the objects do
165 * not have a link ID we rely on their sequential order to find out which instance data belongs to which object.
166 *
167 * @param[in] so Object to traverse and restore the instance data.
168 * @param[in] proxy Hierarchy containing instance data for all objects and components, returned by
169 * recordInstanceData() method.
170 *
171 * @note Does not recurse into child prefab instances.
172 */
173 void restoreUnlinkedInstanceData(const HSceneObject& so, SceneObjectProxy& proxy)
174 {
175 struct StackEntry
176 {
177 HSceneObject so;
178 SceneObjectProxy* proxy;
179 };
180
181 Stack<StackEntry> todo;
182 todo.push(StackEntry());
183
184 StackEntry& topEntry = todo.top();
185 topEntry.so = so;
186 topEntry.proxy = &proxy;
187
188 while (!todo.empty())
189 {
190 StackEntry current = todo.top();
191 todo.pop();
192
193 if (current.proxy->linkId == (UINT32)-1)
194 {
195 current.so->_setInstanceData(current.proxy->instanceData);
196 current.so->_setUUID(current.proxy->uuid);
197 }
198
199 Vector<HComponent>& components = current.so->_getComponents();
200 UINT32 componentProxyIdx = 0;
201 UINT32 numComponentProxies = (UINT32)current.proxy->components.size();
202 for (auto& component : components)
203 {
204 if (component->getLinkId() == (UINT32)-1)
205 {
206 bool foundInstanceData = false;
207 (void)foundInstanceData;
208 for (; componentProxyIdx < numComponentProxies; componentProxyIdx++)
209 {
210 if (current.proxy->components[componentProxyIdx].linkId != (UINT32)-1)
211 continue;
212
213 component->_setInstanceData(current.proxy->components[componentProxyIdx].instanceData);
214 component->_setUUID(current.proxy->components[componentProxyIdx].uuid);
215 component._setHandleData(component.getInternalPtr());
216
217 foundInstanceData = true;
218 break;
219 }
220
221 assert(foundInstanceData);
222 }
223 }
224
225 UINT32 numChildren = current.so->getNumChildren();
226 UINT32 childProxyIdx = 0;
227 UINT32 numChildProxies = (UINT32)current.proxy->children.size();
228 for (UINT32 i = 0; i < numChildren; i++)
229 {
230 HSceneObject child = current.so->getChild(i);
231
232 if (child->getLinkId() == (UINT32)-1)
233 {
234 bool foundInstanceData = false;
235 (void)foundInstanceData;
236 for (; childProxyIdx < numChildProxies; childProxyIdx++)
237 {
238 if (current.proxy->children[childProxyIdx].linkId != (UINT32)-1)
239 continue;
240
241 assert(current.proxy->children[childProxyIdx].linkId == (UINT32)-1);
242 child->_setInstanceData(current.proxy->children[childProxyIdx].instanceData);
243 child->_setUUID(current.proxy->children[childProxyIdx].uuid);
244
245 if (child->_getPrefabLinkUUID().empty())
246 {
247 todo.push(StackEntry());
248
249 StackEntry& newEntry = todo.top();
250 newEntry.so = child;
251 newEntry.proxy = &current.proxy->children[childProxyIdx];
252 }
253
254 foundInstanceData = true;
255 break;
256 }
257
258 assert(foundInstanceData);
259 }
260 else
261 {
262 if (!child->_getPrefabLinkUUID().empty())
263 continue;
264
265 for (UINT32 j = 0; j < numChildProxies; j++)
266 {
267 if (child->getLinkId() == current.proxy->children[j].linkId)
268 {
269 todo.push(StackEntry());
270
271 StackEntry& newEntry = todo.top();
272 newEntry.so = child;
273 newEntry.proxy = &current.proxy->children[j];
274 break;
275 }
276 }
277 }
278 }
279 }
280 }
281 }
282
283 void PrefabUtility::revertToPrefab(const HSceneObject& so)
284 {
285 UUID prefabLinkUUID = so->getPrefabLink();
286 HPrefab prefabLink = static_resource_cast<Prefab>(gResources().loadFromUUID(prefabLinkUUID, false, ResourceLoadFlag::None));
287
288 if (!prefabLink.isLoaded(false))
289 return;
290
291 // Save IDs, destroy original, create new, restore IDs
292 impl::SceneObjectProxy soProxy;
293 UnorderedMap<UINT32, impl::LinkedInstanceData> linkedInstanceData;
294 impl::recordInstanceData(so, soProxy, linkedInstanceData);
295
296 HSceneObject parent = so->getParent();
297
298 // This will destroy the object but keep it in the parent's child list
299 HSceneObject currentSO = so;
300 so->destroyInternal(currentSO, true);
301
302 HSceneObject newInstance = prefabLink->instantiate();
303
304 // Remove default parent, and replace with original one
305 newInstance->mParent->removeChild(newInstance);
306 newInstance->mParent = parent;
307
308 impl::restoreLinkedInstanceData(newInstance, soProxy, linkedInstanceData);
309 }
310
311 void PrefabUtility::updateFromPrefab(const HSceneObject& so)
312 {
313 HSceneObject topLevelObject = so;
314
315 while (topLevelObject != nullptr)
316 {
317 if (!topLevelObject->mPrefabLinkUUID.empty())
318 break;
319
320 if (topLevelObject->mParent != nullptr)
321 topLevelObject = topLevelObject->mParent;
322 else
323 topLevelObject = nullptr;
324 }
325
326 if (topLevelObject == nullptr)
327 topLevelObject = so;
328
329 Stack<HSceneObject> todo;
330 todo.push(topLevelObject);
331
332 // Find any prefab instances
333 Vector<HSceneObject> prefabInstanceRoots;
334
335 while (!todo.empty())
336 {
337 HSceneObject current = todo.top();
338 todo.pop();
339
340 if (!current->mPrefabLinkUUID.empty())
341 prefabInstanceRoots.push_back(current);
342
343 UINT32 childCount = current->getNumChildren();
344 for (UINT32 i = 0; i < childCount; i++)
345 {
346 HSceneObject child = current->getChild(i);
347 todo.push(child);
348 }
349 }
350
351 // Stores data about the new prefab instance and its original parent and link id
352 // (as those aren't stored in the prefab diff)
353 struct RestoredPrefabInstance
354 {
355 HSceneObject newInstance;
356 HSceneObject originalParent;
357 SPtr<PrefabDiff> diff;
358 UINT32 originalLinkId;
359 };
360
361 Vector<RestoredPrefabInstance> newPrefabInstanceData;
362
363 // For each prefab instance load its reference prefab from the disk and check if it changed. If it has changed
364 // instantiate the prefab and destroy the current instance. Then apply instance specific changes stored in a
365 // prefab diff, if any, as well as restore the original parent and link id (link id of the root prefab instance
366 // belongs to the parent prefab if any). Finally fix any handles pointing to the old objects so that they now point
367 // to the newly instantiated objects. To the outside world it should be transparent that we just destroyed and then
368 // re-created from scratch the entire hierarchy.
369
370 // Need to do this bottom up to ensure I don't destroy the parents before children
371 for (auto iter = prefabInstanceRoots.rbegin(); iter != prefabInstanceRoots.rend(); ++iter)
372 {
373 HSceneObject current = *iter;
374 HPrefab prefabLink = static_resource_cast<Prefab>(gResources().loadFromUUID(current->mPrefabLinkUUID, false, ResourceLoadFlag::None));
375
376 if (prefabLink.isLoaded(false) && prefabLink->getHash() != current->mPrefabHash)
377 {
378 // Save IDs, destroy original, create new, restore IDs
379 impl::SceneObjectProxy soProxy;
380 UnorderedMap<UINT32, impl::LinkedInstanceData> linkedInstanceData;
381 impl::recordInstanceData(current, soProxy, linkedInstanceData);
382
383 HSceneObject parent = current->getParent();
384 SPtr<PrefabDiff> prefabDiff = current->mPrefabDiff;
385
386 current->destroy(true);
387 HSceneObject newInstance = prefabLink->_clone();
388
389 // When restoring instance IDs it is important to make all the new handles point to the old GameObjectInstanceData.
390 // This is because old handles will have different GameObjectHandleData and we have no easy way of accessing it to
391 // change to which GameObjectInstanceData it points. But the GameObjectManager ensures that all handles deserialized
392 // at once (i.e. during the ::_clone() call above) will share GameObjectHandleData so we can simply replace
393 // to what they point to, affecting all of the handles to that object. (In another words, we can modify the
394 // new handles at this point, but old ones must keep referencing what they already were.)
395 impl::restoreLinkedInstanceData(newInstance, soProxy, linkedInstanceData);
396 impl::restoreUnlinkedInstanceData(newInstance, soProxy);
397
398 newPrefabInstanceData.push_back({ newInstance, parent, prefabDiff, newInstance->getLinkId() });
399 }
400 }
401
402 // Once everything is cloned, apply diffs, restore old parents & link IDs for root.
403 for (auto& entry : newPrefabInstanceData)
404 {
405 // Diffs must be applied after everything is instantiated and instance data restored since it may contain
406 // game object handles within or external to its prefab instance.
407 if (entry.diff != nullptr)
408 entry.diff->apply(entry.newInstance);
409
410 entry.newInstance->mPrefabDiff = entry.diff;
411
412 entry.newInstance->setParent(entry.originalParent, false);
413 entry.newInstance->mLinkId = entry.originalLinkId;
414 }
415
416 // Finally, instantiate everything if the top scene object is live (instantiated)
417 if (topLevelObject->isInstantiated())
418 {
419 for (auto& entry : newPrefabInstanceData)
420 entry.newInstance->_instantiate(true);
421 }
422
423 gResources().unloadAllUnused();
424 }
425
426 void PrefabUtility::generatePrefabIds(const HSceneObject& sceneObject)
427 {
428 UINT32 startingId = 0;
429
430 Stack<HSceneObject> todo;
431 todo.push(sceneObject);
432
433 while (!todo.empty())
434 {
435 HSceneObject currentSO = todo.top();
436 todo.pop();
437
438 for (auto& component : currentSO->mComponents)
439 {
440 if (component->getLinkId() != (UINT32)-1)
441 startingId = std::max(component->mLinkId + 1, startingId);
442 }
443
444 UINT32 numChildren = (UINT32)currentSO->getNumChildren();
445 for (UINT32 i = 0; i < numChildren; i++)
446 {
447 HSceneObject child = currentSO->getChild(i);
448
449 if (!child->hasFlag(SOF_DontSave))
450 {
451 if (child->getLinkId() != (UINT32)-1)
452 startingId = std::max(child->mLinkId + 1, startingId);
453
454 if (child->mPrefabLinkUUID.empty())
455 todo.push(currentSO->getChild(i));
456 }
457 }
458 }
459
460 UINT32 currentId = startingId;
461 todo.push(sceneObject);
462
463 while (!todo.empty())
464 {
465 HSceneObject currentSO = todo.top();
466 todo.pop();
467
468 for (auto& component : currentSO->mComponents)
469 {
470 if (component->getLinkId() == (UINT32)-1)
471 component->mLinkId = currentId++;
472 }
473
474 UINT32 numChildren = (UINT32)currentSO->getNumChildren();
475 for (UINT32 i = 0; i < numChildren; i++)
476 {
477 HSceneObject child = currentSO->getChild(i);
478
479 if (!child->hasFlag(SOF_DontSave))
480 {
481 if (child->getLinkId() == (UINT32)-1)
482 child->mLinkId = currentId++;
483
484 if(child->mPrefabLinkUUID.empty())
485 todo.push(currentSO->getChild(i));
486 }
487 }
488 }
489
490 if (currentId < startingId)
491 {
492 BS_EXCEPT(InternalErrorException, "Prefab ran out of IDs to assign. " \
493 "Consider increasing the size of the prefab ID data type.");
494 }
495 }
496
497 void PrefabUtility::clearPrefabIds(const HSceneObject& sceneObject, bool recursive, bool clearRoot)
498 {
499 Stack<HSceneObject> todo;
500 todo.push(sceneObject);
501
502 if (clearRoot)
503 sceneObject->mLinkId = (UINT32)-1;
504
505 while (!todo.empty())
506 {
507 HSceneObject currentSO = todo.top();
508 todo.pop();
509
510 for (auto& component : currentSO->mComponents)
511 component->mLinkId = (UINT32)-1;
512
513 if (recursive)
514 {
515 UINT32 numChildren = (UINT32)currentSO->getNumChildren();
516 for (UINT32 i = 0; i < numChildren; i++)
517 {
518 HSceneObject child = currentSO->getChild(i);
519 child->mLinkId = (UINT32)-1;
520
521 if (child->mPrefabLinkUUID.empty())
522 todo.push(child);
523 }
524 }
525 }
526 }
527
528 void PrefabUtility::recordPrefabDiff(const HSceneObject& sceneObject)
529 {
530 HSceneObject topLevelObject = sceneObject;
531
532 while (topLevelObject != nullptr)
533 {
534 if (!topLevelObject->mPrefabLinkUUID.empty())
535 break;
536
537 if (topLevelObject->mParent != nullptr)
538 topLevelObject = topLevelObject->mParent;
539 else
540 topLevelObject = nullptr;
541 }
542
543 if (topLevelObject == nullptr)
544 topLevelObject = sceneObject;
545
546 Stack<HSceneObject> todo;
547 todo.push(topLevelObject);
548
549 while (!todo.empty())
550 {
551 HSceneObject current = todo.top();
552 todo.pop();
553
554 if (!current->mPrefabLinkUUID.empty())
555 {
556 current->mPrefabDiff = nullptr;
557
558 HPrefab prefabLink = static_resource_cast<Prefab>(gResources().loadFromUUID(current->mPrefabLinkUUID, false, ResourceLoadFlag::None));
559 if (prefabLink.isLoaded(false))
560 current->mPrefabDiff = PrefabDiff::create(prefabLink->_getRoot(), current->getHandle());
561 }
562
563 UINT32 childCount = current->getNumChildren();
564 for (UINT32 i = 0; i < childCount; i++)
565 {
566 HSceneObject child = current->getChild(i);
567 todo.push(child);
568 }
569 }
570
571 gResources().unloadAllUnused();
572 }
573}
574