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 | |
9 | namespace 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 = ¤t.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 = ¤t.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 | |