Skip to content

Commit 8211ecf

Browse files
author
Benoit Hudson
committed
Simplify the logic and make it way more robust.
- get the types to destroy from the prefab (not from reflection), so we can handle deleting MonoBehaviours. - simplify the logic a bunch and handle* multiplicity > 1. - use EditorJsonUtility to copy values rather than an internal API. multiplicity-handling is untested yet
1 parent ef2f384 commit 8211ecf

File tree

1 file changed

+93
-123
lines changed

1 file changed

+93
-123
lines changed

Assets/FbxExporters/FbxPrefab.cs

Lines changed: 93 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,16 @@ public List<string> GetComponentValues(string name, string typename)
530530
/// </summary>
531531
Dictionary<string, List<System.Type>> m_componentsToDestroy;
532532

533+
struct ComponentValue {
534+
public System.Type t;
535+
public string jsonValue;
536+
537+
public ComponentValue(System.Type t, string jsonValue) {
538+
this.t = t;
539+
this.jsonValue = jsonValue;
540+
}
541+
}
542+
533543
/// <summary>
534544
/// List of components to update or create in steps 4b and 4c.
535545
/// The string is the name in the prefab.
@@ -538,7 +548,7 @@ public List<string> GetComponentValues(string name, string typename)
538548
/// If the component exists in the prefab, we update the first
539549
/// match (without repetition).
540550
/// </summary>
541-
Dictionary<string, List<Component>> m_componentsToUpdate;
551+
Dictionary<string, List<ComponentValue>> m_componentsToUpdate;
542552

543553
void ClassifyDestroyCreateNodes()
544554
{
@@ -617,119 +627,84 @@ void ClassifyReparenting()
617627
}
618628
}
619629

620-
void ClassifyComponents(Transform newFbx)
630+
void ClassifyComponents(Transform newFbx, Transform prefab)
621631
{
622632
Initialize(ref m_componentsToDestroy);
623633
Initialize(ref m_componentsToUpdate);
624634

625-
// Flatten the list of components in the transform hierarchy so we can remember what to copy.
626-
var components = new Dictionary<string, Dictionary<string, List<Component>>>();
627-
var builder = new System.Text.StringBuilder();
635+
// Figure out how to map from type names to System.Type values,
636+
// without going through reflection APIs. This allows us to handle
637+
// components from various assemblies.
638+
//
639+
// We're going to be adding from the newFbx, and deleting from the prefab,
640+
// so we don't need to know about all the types that oldFbx might have.
641+
var componentTypes = new Dictionary<string, System.Type>();
628642
foreach(var component in newFbx.GetComponentsInChildren<Component>()) {
629-
string name;
630-
if (component.transform == newFbx) {
631-
name = "";
632-
} else {
633-
name = component.name;
634-
}
635-
var typename = component.GetType().ToString();
636-
builder.AppendFormat("\t{0}:{1}\n", name, typename);
637-
Append(ref components, name, typename, component);
643+
var componentType = component.GetType();
644+
componentTypes[componentType.ToString()] = componentType;
645+
}
646+
foreach(var component in prefab.GetComponentsInChildren<Component>()) {
647+
var componentType = component.GetType();
648+
componentTypes[componentType.ToString()] = componentType;
638649
}
639650

640-
// What's the logic?
641-
// First check if a component is present or absent. It's
642-
// present if the node exists and has that component; it's
643-
// absent if the node doesn't exist, or the node exists but
644-
// doesn't have that component:
645-
// old new prefab
646-
// x x x => ignore
647-
// x x y => ignore
648-
// x y x => create a new component, copy from new
649-
// x y y => ignore
650-
// y x x => ignore
651-
// y x y => destroy the component
652-
// y y x => ignore
653-
// y y y => check if we need to update
651+
// For each node in the prefab (after adding any new nodes):
652+
// 1. If a component is in the old but not the new, remove it from
653+
// the prefab.
654+
// 2. If it's in the new but not the old, add it to the prefab.
655+
// 3. If it's in both the old and new, but with different values,
656+
// update the prefab values.
657+
// If a component type is repeated (e.g. two BoxCollider on the
658+
// same node), we line up the components in the order they
659+
// appear. This never happens in stock Unity, someone must have
660+
// added an AssetPostprocessor for it to occur.
661+
// TODO: do something smarter.
654662
//
655-
// In that last case, check whether we need to update the values:
656-
// a a a => ignore
657-
// a a b => ignore
658-
// a a c => ignore (indistinguishable from aab case)
659-
// a b a => update to b
660-
// a b b => ignore (already matches)
661-
// a b c => conflict, update to b
662-
//
663-
// Big question: how do we handle multiplicity? I'll skip it for today...
664-
665-
// We only care about nodes in the prefab after creating/destroying.
663+
// If the node isn't going to be in the prefab, we don't care
664+
// about what components might be on it.
666665
foreach(var name in m_nodesInUpdatedPrefab)
667666
{
668667
if (!m_new.HasNode(name)) {
669668
// It's not in the FBX, so clearly we're not updating any components.
669+
// We don't need to check if it's in m_prefab because
670+
// we're only iterating over those.
670671
continue;
671672
}
672673
var allTypes = m_old.GetComponentTypes(name).Union(
673-
m_new.GetComponentTypes(name).Union(
674-
m_prefab.GetComponentTypes(name)));
675-
676-
List<string> typesToDestroy = null;
677-
List<string> typesToUpdate = null;
674+
m_new.GetComponentTypes(name));
678675

679676
foreach(var typename in allTypes) {
680677
var oldValues = m_old.GetComponentValues(name, typename);
681678
var newValues = m_new.GetComponentValues(name, typename);
682-
var prefabValues = m_prefab.GetComponentValues(name, typename);
683-
684-
// TODO: handle multiplicity! The algorithm is eluding me right now...
685-
// We'll need to do some kind of 3-way matching.
686-
if (oldValues.Count > 1) { Debug.LogError("TODO: handle multiplicity " + oldValues.Count); }
687-
if (newValues.Count > 1) { Debug.LogError("TODO: handle multiplicity " + newValues.Count); }
688-
if (prefabValues.Count > 1) { Debug.LogError("TODO: handle multiplicity " + prefabValues.Count); }
689-
690-
if (oldValues.Count == 0 && newValues.Count != 0 && prefabValues.Count == 0) {
691-
Append(ref typesToUpdate, typename);
692-
}
693-
else if (oldValues.Count != 0 && newValues.Count == 0 && prefabValues.Count != 0) {
694-
Append(ref typesToDestroy, typename);
695-
}
696-
else if (oldValues.Count != 0 && newValues.Count != 0 && prefabValues.Count != 0) {
697-
// Check whether we need to update.
698-
var oldValue = oldValues[0];
699-
var newValue = newValues[0];
700-
var prefabValue = prefabValues[0];
701-
702-
if (oldValue != newValue) {
703-
// if oldValue == prefabValue, update.
704-
// if oldValue != prefabValue, conflict =>
705-
// resolve in favor of Chris, so update
706-
// anyway.
707-
Append(ref typesToUpdate, typename);
708-
}
709-
}
710-
}
711-
712-
// Figure out the types so we can destroy them.
713-
if (typesToDestroy != null) {
714-
// TODO: handle monobehaviour in other assemblies
715-
// Sample use: using custom attributes in fbx to drive
716-
// unity behaviour by adding a monobehaviour in an
717-
// assetpostprocessor.
718-
var unityEngine = typeof(Component).Assembly;
719-
foreach (var typename in typesToDestroy) {
720-
var thetype = unityEngine.GetType (typename);
721-
Append (ref m_componentsToDestroy, name, thetype);
722-
}
723-
}
724-
725-
// Find the actual components in the new fbx so we can copy them.
726-
if (typesToUpdate != null) {
727-
foreach (var typename in typesToUpdate) {
728-
if (components [name] [typename].Count > 1) {
729-
Debug.LogError (string.Format("todo: multiplicity {0} on {1}:{2}",
730-
components [name] [typename].Count, name, typename));
679+
List<string> prefabValues = null; // get them only if we need them.
680+
681+
// If we have multiple identical-type components, match them up by index.
682+
// TODO: match them up to minimize the diff instead.
683+
int oldN = oldValues.Count;
684+
int newN = newValues.Count;
685+
for(int i = 0, n = System.Math.Max(oldN, newN); i < n; ++i) {
686+
if (/* isNew */ i < newN) {
687+
var newValue = newValues[i];
688+
if (/* isOld */ i < oldN && oldValues[i] == newValue) {
689+
// No change from the old => skip.
690+
continue;
691+
}
692+
if (prefabValues == null) { prefabValues = m_prefab.GetComponentValues(name, typename); }
693+
if (i < prefabValues.Count && prefabValues[i] == newValue) {
694+
// Already updated => skip.
695+
continue;
696+
}
697+
Append (m_componentsToUpdate, name,
698+
new ComponentValue(componentTypes[typename], newValue));
699+
} else {
700+
// Not in the new, but is in the old, so delete
701+
// it if it's not already deleted from the
702+
// prefab.
703+
if (prefabValues == null) { prefabValues = m_prefab.GetComponentValues(name, typename); }
704+
if (i < prefabValues.Count) {
705+
Append (m_componentsToDestroy, name, componentTypes[typename]);
706+
}
731707
}
732-
Append (ref m_componentsToUpdate, name, components [name] [typename] [0]);
733708
}
734709
}
735710
}
@@ -757,7 +732,7 @@ public UpdateList(
757732

758733
ClassifyDestroyCreateNodes();
759734
ClassifyReparenting();
760-
ClassifyComponents(newFbx);
735+
ClassifyComponents(newFbx, prefab.transform);
761736
}
762737

763738
public bool NeedsUpdates() {
@@ -842,35 +817,30 @@ public void ImplementUpdates(FbxPrefab prefabInstance)
842817
}
843818

844819
// Create or update the new components.
845-
foreach(var kvp in prefabNodes) {
846-
var name = kvp.Key;
847-
var prefabXfo = kvp.Value;
848-
List<Component> fbxComponents;
849-
if (m_componentsToUpdate.TryGetValue(name, out fbxComponents)) {
850-
// Copy the components once so we can match them up even if there's multiple fbxComponents.
851-
List<Component> prefabComponents = new List<Component>(prefabXfo.GetComponents<Component>());
852-
853-
foreach(var fbxComponent in fbxComponents) {
854-
int index = prefabComponents.FindIndex(x => x.GetType() == fbxComponent.GetType());
855-
if (index >= 0) {
856-
// Don't match this index again.
857-
Component prefabComponent = prefabComponents[index];
858-
prefabComponents.RemoveAt(index);
859-
860-
// Now update it.
861-
if (UnityEditorInternal.ComponentUtility.CopyComponent(fbxComponent)) {
862-
UnityEditorInternal.ComponentUtility.PasteComponentValues(prefabComponent);
863-
Log("updated component {0}:{1}", name, fbxComponent.GetType());
864-
865-
}
866-
} else {
867-
// We didn't find a match, so create the component as new.
868-
if (UnityEditorInternal.ComponentUtility.CopyComponent(fbxComponent)) {
869-
UnityEditorInternal.ComponentUtility.PasteComponentAsNew(prefabXfo.gameObject);
870-
Log("added new component {0}:{1}", name, fbxComponent.GetType());
871-
}
872-
}
820+
foreach(var kvp in m_componentsToUpdate) {
821+
var nodeName = kvp.Key;
822+
var fbxComponents = kvp.Value;
823+
var prefabXfo = prefabNodes[nodeName];
824+
825+
// Copy the components once so we can match them up even if there's multiple fbxComponents.
826+
List<Component> prefabComponents = new List<Component>(prefabXfo.GetComponents<Component>());
827+
828+
foreach(var fbxComponent in fbxComponents) {
829+
// Find or create the component to update.
830+
int index = prefabComponents.FindIndex(x => x.GetType() == fbxComponent.t);
831+
Component prefabComponent;
832+
if (index >= 0) {
833+
// Don't match this index again.
834+
prefabComponent = prefabComponents[index];
835+
prefabComponents.RemoveAt(index);
836+
Log("updated component {0}:{1}", nodeName, fbxComponent.t);
837+
} else {
838+
prefabComponent = prefabXfo.gameObject.AddComponent(fbxComponent.t);
839+
Log("created component {0}:{1}", nodeName, fbxComponent.t);
873840
}
841+
842+
// Now set the values.
843+
UnityEditor.EditorJsonUtility.FromJsonOverwrite(fbxComponent.jsonValue, prefabComponent);
874844
}
875845
}
876846
}

0 commit comments

Comments
 (0)