Skip to content

Commit 23f1139

Browse files
author
Benoit Hudson
committed
uni-22630: provide an event that fires after updating the prefab.
This event allows updating PB meshes, for example. Added unit tests; they all pass.
1 parent 302a84d commit 23f1139

File tree

2 files changed

+131
-31
lines changed

2 files changed

+131
-31
lines changed

Assets/FbxExporters/Editor/UnitTests/FbxPrefabTest.cs

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,40 @@ public class FbxPrefabTest : ExporterTestBase
1515
GameObject m_autoPrefab; // prefab that auto-updates
1616
GameObject m_manualPrefab; // prefab that doesn't auto-update
1717

18+
class UpdateListener : System.IDisposable
19+
{
20+
public List<string> Updated { get ; private set; }
21+
public int NumUpdates { get ; private set; }
22+
23+
GameObject m_prefabToAttachTo;
24+
25+
public UpdateListener(GameObject prefabToAttachTo) {
26+
m_prefabToAttachTo = prefabToAttachTo;
27+
Updated = new List<string>();
28+
NumUpdates = 0;
29+
FbxPrefab.OnUpdate += OnUpdate;
30+
}
31+
32+
~UpdateListener() {
33+
FbxPrefab.OnUpdate -= OnUpdate;
34+
}
35+
36+
public void Dispose() {
37+
FbxPrefab.OnUpdate -= OnUpdate;
38+
}
39+
40+
void OnUpdate(FbxPrefab prefabInstance, IEnumerable<GameObject> updated)
41+
{
42+
if (prefabInstance.name != m_prefabToAttachTo.name) {
43+
return;
44+
}
45+
NumUpdates++;
46+
foreach(var go in updated) {
47+
Updated.Add(go.name);
48+
}
49+
}
50+
}
51+
1852
public static void AssertAreIdentical(
1953
FbxPrefab.FbxRepresentation a,
2054
FbxPrefab.FbxRepresentation b) {
@@ -103,6 +137,10 @@ public void Init() {
103137
m_autoPrefab = PrefabUtility.CreatePrefab(
104138
GetRandomPrefabAssetPath(),
105139
prefabInstance);
140+
141+
// Add an event listener to store the name of auto-updated nodes,
142+
// if it's the autoPrefab.
143+
106144
}
107145

108146
// Create an FbxPrefab linked to the same Fbx file. Make it NOT auto-update.
@@ -115,6 +153,8 @@ public void Init() {
115153
GetRandomPrefabAssetPath(),
116154
prefabInstance);
117155
}
156+
157+
118158
}
119159

120160
FbxPrefab.FbxRepresentation Rep(GameObject go) {
@@ -155,21 +195,33 @@ public void BasicTest() {
155195
AssertAreIdentical(m_originalRep, History(m_manualPrefab));
156196
AssertAreIdentical(m_originalRep, History(m_autoPrefab));
157197

158-
Debug.Log("Testing auto update");
159-
var newHierarchy = Rep(ModifySourceFbx());
160-
AssertAreDifferent(m_originalRep, newHierarchy);
161-
162-
// Make sure the fbx source changed.
163-
AssertAreDifferent(m_originalRep, Rep(m_source));
164-
AssertAreIdentical(newHierarchy, Rep(m_source));
165-
166-
// Make sure the auto-update prefab changed.
167-
AssertAreIdentical(newHierarchy, Rep(m_autoPrefab));
168-
AssertAreIdentical(newHierarchy, History(m_autoPrefab));
169-
170-
// Make sure the manual-update prefab didn't.
171-
AssertAreIdentical(m_originalRep, Rep(m_manualPrefab));
172-
AssertAreIdentical(m_originalRep, History(m_manualPrefab));
198+
FbxPrefab.FbxRepresentation newHierarchy;
199+
using(var updateSet = new UpdateListener(m_autoPrefab)) {
200+
Debug.Log("Testing auto update");
201+
newHierarchy = Rep(ModifySourceFbx());
202+
AssertAreDifferent(m_originalRep, newHierarchy);
203+
204+
// Make sure the fbx source changed.
205+
AssertAreDifferent(m_originalRep, Rep(m_source));
206+
AssertAreIdentical(newHierarchy, Rep(m_source));
207+
208+
// Make sure the auto-update prefab changed.
209+
AssertAreIdentical(newHierarchy, Rep(m_autoPrefab));
210+
AssertAreIdentical(newHierarchy, History(m_autoPrefab));
211+
212+
// Make sure the manual-update prefab didn't.
213+
AssertAreIdentical(m_originalRep, Rep(m_manualPrefab));
214+
AssertAreIdentical(m_originalRep, History(m_manualPrefab));
215+
216+
// Make sure we got the right changes.
217+
Assert.That (updateSet.Updated, Is.EquivalentTo (new string [] {
218+
// TODO: UNI-24579 - we should only be seeing Parent3 here,
219+
// the other two are for transform changes, but
220+
// they shouldn't have changed at all
221+
"Parent2", "Parent3", "Child3"
222+
}
223+
));
224+
}
173225

174226
// Manual update, make sure it updated.
175227
Debug.Log("Testing manual update");

Assets/FbxExporters/FbxPrefab.cs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -603,11 +603,7 @@ void ClassifyReparenting()
603603
continue;
604604
}
605605
if (m_nodesToDestroy.Contains(name)) {
606-
// Reparent to the root. This is to avoid the nuisance of
607-
// trying to destroy objects that are already destroyed
608-
// because a parent got there first. Maybe there's a
609-
// faster way to do it, but performance seems OK.
610-
m_reparentings.Add(name, "");
606+
// Don't bother reparenting, we'll be destroying this anyway.
611607
continue;
612608
}
613609

@@ -754,17 +750,23 @@ public bool NeedsUpdates() {
754750
/// 1. Create all the new nodes we need to create.
755751
/// 2. Reparent as needed.
756752
/// 3. Delete the nodes that are no longer needed.
757-
/// todo 4. Update the components:
753+
/// 4. Update the components:
758754
/// 4a. delete components no longer used
759755
/// 4b. create new components
760756
/// 4c. update component values
761757
/// (A) and (B) are largely about meshfilter/meshrenderer,
762758
/// (C) is about transforms (and materials?)
759+
///
760+
/// Return the set of GameObject that were created or reparented
761+
/// in 1 and 2; or that were updated in 4. Does not return the destroyed
762+
/// GameObjects -- they've been destroyed!
763763
/// </summary>
764-
public void ImplementUpdates(FbxPrefab prefabInstance)
764+
public HashSet<GameObject> ImplementUpdates(FbxPrefab prefabInstance)
765765
{
766766
Log("{0}: performing updates", prefabInstance.name);
767767

768+
var updatedNodes = new HashSet<GameObject>();
769+
768770
// Gather up all the nodes in the prefab so we can look up
769771
// nodes. We use the empty string for the root node.
770772
var prefabRoot = prefabInstance.transform;
@@ -779,8 +781,11 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
779781

780782
// Create new nodes.
781783
foreach(var name in m_nodesToCreate) {
782-
prefabNodes.Add(name, new GameObject(name).transform);
784+
var newNode = new GameObject(name);
785+
prefabNodes.Add(name, newNode.transform);
786+
783787
Log("{0}: created new GameObject", name);
788+
updatedNodes.Add(newNode);
784789
}
785790

786791
// Implement the reparenting in two phases to avoid making loops, e.g.
@@ -803,14 +808,22 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
803808
} else {
804809
parentNode = prefabNodes[parent];
805810
}
806-
prefabNodes[name].parent = parentNode;
811+
var childNode = prefabNodes[name];
812+
childNode.parent = parentNode;
813+
807814
Log("changed {0} parent to {1}", name, parentNode.name);
815+
updatedNodes.Add(childNode.gameObject);
808816
}
809817

810-
// Destroy the old nodes.
811-
foreach(var toDestroy in m_nodesToDestroy) {
812-
GameObject.DestroyImmediate(prefabNodes[toDestroy].gameObject);
813-
Log("destroyed {0}", toDestroy);
818+
// Destroy the old nodes. Remember that DestroyImmediate recursively
819+
// destroys, so avoid errors.
820+
foreach(var nameToDestroy in m_nodesToDestroy) {
821+
var xfoToDestroy = prefabNodes[nameToDestroy];
822+
if (xfoToDestroy) {
823+
GameObject.DestroyImmediate(xfoToDestroy.gameObject);
824+
}
825+
Log("destroyed {0}", nameToDestroy);
826+
prefabNodes.Remove(nameToDestroy);
814827
}
815828

816829
// Destroy the old components.
@@ -819,6 +832,7 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
819832
var nodeName = kvp.Key;
820833
var typesToDestroy = kvp.Value;
821834
var prefabXfo = prefabNodes[nodeName];
835+
updatedNodes.Add(prefabXfo.gameObject);
822836

823837
foreach(var componentType in typesToDestroy) {
824838
var component = prefabXfo.GetComponent(componentType);
@@ -834,6 +848,7 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
834848
var nodeName = kvp.Key;
835849
var fbxComponents = kvp.Value;
836850
var prefabXfo = prefabNodes[nodeName];
851+
updatedNodes.Add(prefabXfo.gameObject);
837852

838853
// Copy the components once so we can match them up even if there's multiple fbxComponents.
839854
List<Component> prefabComponents = new List<Component>(prefabXfo.GetComponents<Component>());
@@ -856,7 +871,9 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
856871
UnityEditor.EditorJsonUtility.FromJsonOverwrite(fbxComponent.jsonValue, prefabComponent);
857872
}
858873
}
874+
return updatedNodes;
859875
}
876+
860877
}
861878

862879
/// <summary>
@@ -903,14 +920,20 @@ void CompareAndUpdate()
903920
throw new System.Exception(string.Format("Failed to instantiate {0}; is it really a prefab?",
904921
this.gameObject));
905922
}
906-
var fbxPrefab = prefabInstance.GetComponent<FbxPrefab>();
923+
var fbxPrefabInstance = prefabInstance.GetComponent<FbxPrefab>();
924+
925+
// Do ALL the things!
926+
var updatedObjects = updates.ImplementUpdates(fbxPrefabInstance);
907927

908-
updates.ImplementUpdates(fbxPrefab);
928+
// Tell listeners about it. They're free to make adjustments now.
929+
if (OnUpdate != null) {
930+
OnUpdate(fbxPrefabInstance, updatedObjects);
931+
}
909932

910933
// Update the representation of the history to match the new fbx.
911934
var newFbxRep = new FbxRepresentation(m_fbxModel.transform);
912935
var newFbxRepString = newFbxRep.ToJson();
913-
fbxPrefab.m_fbxHistory = newFbxRepString;
936+
fbxPrefabInstance.m_fbxHistory = newFbxRepString;
914937

915938
// Save the changes back to the prefab.
916939
UnityEditor.PrefabUtility.ReplacePrefab(prefabInstance, this.transform);
@@ -1006,6 +1029,31 @@ public void SetSourceModel(GameObject fbxModel) {
10061029
CompareAndUpdate();
10071030
}
10081031
}
1032+
1033+
//////////////////////////////////////////////////////////////////////////
1034+
// Event handling for updates.
1035+
1036+
/// <summary>
1037+
/// Handler for an OnUpdate event.
1038+
///
1039+
/// The update is performed on a temporary instance, which, shortly after
1040+
/// this handler is invoked, will be applied to the prefab.
1041+
///
1042+
/// The event handler can make changes to any objects in the hierarchy rooted
1043+
/// by the updatedInstance. Those changes will be applied to the prefab.
1044+
///
1045+
/// The updatedObjects include all objects in the temporary instance
1046+
/// that were created, plus all objects which had a component get
1047+
/// created, destroyed, or updated. You get notification for objects
1048+
/// that were destroyed.
1049+
/// </summary>
1050+
public delegate void HandleUpdate(FbxPrefab updatedInstance, IEnumerable<GameObject> updatedObjects);
1051+
1052+
/// <summary>
1053+
/// OnUpdate is raised once when an FbxPrefab gets updated, after all the changes
1054+
/// have been done.
1055+
/// </summary>
1056+
public static event HandleUpdate OnUpdate;
10091057
#endif
10101058
}
10111059
}

0 commit comments

Comments
 (0)