Skip to content

Commit aef18b1

Browse files
authored
Merge pull request #118 from Unity-Technologies/uni-22630-event-on-update
uni-22630: provide an event that fires after updating the prefab.
2 parents f380732 + c959481 commit aef18b1

File tree

2 files changed

+127
-31
lines changed

2 files changed

+127
-31
lines changed

Assets/FbxExporters/Editor/UnitTests/FbxPrefabTest.cs

Lines changed: 62 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) {
@@ -155,21 +189,34 @@ public void BasicTest() {
155189
AssertAreIdentical(m_originalRep, History(m_manualPrefab));
156190
AssertAreIdentical(m_originalRep, History(m_autoPrefab));
157191

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));
192+
FbxPrefab.FbxRepresentation newHierarchy;
193+
using(var updateSet = new UpdateListener(m_autoPrefab)) {
194+
Debug.Log("Testing auto update");
195+
newHierarchy = Rep(ModifySourceFbx());
196+
AssertAreDifferent(m_originalRep, newHierarchy);
197+
198+
// Make sure the fbx source changed.
199+
AssertAreDifferent(m_originalRep, Rep(m_source));
200+
AssertAreIdentical(newHierarchy, Rep(m_source));
201+
202+
// Make sure the auto-update prefab changed.
203+
AssertAreIdentical(newHierarchy, Rep(m_autoPrefab));
204+
AssertAreIdentical(newHierarchy, History(m_autoPrefab));
205+
206+
// Make sure the manual-update prefab didn't.
207+
AssertAreIdentical(m_originalRep, Rep(m_manualPrefab));
208+
AssertAreIdentical(m_originalRep, History(m_manualPrefab));
209+
210+
// Make sure we got the right changes.
211+
Assert.AreEqual (1, updateSet.NumUpdates);
212+
Assert.That (updateSet.Updated, Is.EquivalentTo (new string [] {
213+
// TODO: UNI-24579 - we should only be seeing Parent3 here,
214+
// the other two are for transform changes, but
215+
// they shouldn't have changed at all
216+
"Parent2", "Parent3", "Child3"
217+
}
218+
));
219+
}
173220

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

Assets/FbxExporters/FbxPrefab.cs

Lines changed: 65 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,6 +871,7 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
856871
UnityEditor.EditorJsonUtility.FromJsonOverwrite(fbxComponent.jsonValue, prefabComponent);
857872
}
858873
}
874+
return updatedNodes;
859875
}
860876
}
861877

@@ -903,14 +919,20 @@ void CompareAndUpdate()
903919
throw new System.Exception(string.Format("Failed to instantiate {0}; is it really a prefab?",
904920
this.gameObject));
905921
}
906-
var fbxPrefab = prefabInstance.GetComponent<FbxPrefab>();
922+
var fbxPrefabInstance = prefabInstance.GetComponent<FbxPrefab>();
907923

908-
updates.ImplementUpdates(fbxPrefab);
924+
// Do ALL the things!
925+
var updatedObjects = updates.ImplementUpdates(fbxPrefabInstance);
926+
927+
// Tell listeners about it. They're free to make adjustments now.
928+
if (OnUpdate != null) {
929+
OnUpdate(fbxPrefabInstance, updatedObjects);
930+
}
909931

910932
// Update the representation of the history to match the new fbx.
911933
var newFbxRep = new FbxRepresentation(m_fbxModel.transform);
912934
var newFbxRepString = newFbxRep.ToJson();
913-
fbxPrefab.m_fbxHistory = newFbxRepString;
935+
fbxPrefabInstance.m_fbxHistory = newFbxRepString;
914936

915937
// Save the changes back to the prefab.
916938
UnityEditor.PrefabUtility.ReplacePrefab(prefabInstance, this.transform);
@@ -1006,6 +1028,33 @@ public void SetSourceModel(GameObject fbxModel) {
10061028
CompareAndUpdate();
10071029
}
10081030
}
1031+
1032+
//////////////////////////////////////////////////////////////////////////
1033+
// Event handling for updates.
1034+
1035+
/// <summary>
1036+
/// Handler for an OnUpdate event.
1037+
///
1038+
/// The update is performed on a temporary instance, which, shortly after
1039+
/// this handler is invoked, will be applied to the prefab.
1040+
///
1041+
/// The event handler can make changes to any objects in the hierarchy rooted
1042+
/// by the updatedInstance. Those changes will be applied to the prefab.
1043+
///
1044+
/// The updatedObjects include all objects in the temporary instance
1045+
/// that were:
1046+
/// - created, or
1047+
/// - changed parent, or
1048+
/// - had a component that was created, destroyed, or updated.
1049+
/// There is no notification for entire objects that were destroyed.
1050+
/// </summary>
1051+
public delegate void HandleUpdate(FbxPrefab updatedInstance, IEnumerable<GameObject> updatedObjects);
1052+
1053+
/// <summary>
1054+
/// OnUpdate is raised once when an FbxPrefab gets updated, after all the changes
1055+
/// have been done.
1056+
/// </summary>
1057+
public static event HandleUpdate OnUpdate;
10091058
#endif
10101059
}
10111060
}

0 commit comments

Comments
 (0)