using System;
namespace GitMerge
{
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text;
///
/// One instance of this class represents one GameObject with relevance to the merge process.
/// Holds all MergeActions that can be applied to the GameObject or its Components.
/// Is considered as "merged" when all its MergeActions are "merged".
///
public class GameObjectMergeActions
{
///
/// Reference to "our" version of the GameObject.
///
public GameObject ours { private set; get; }
///
/// Reference to "their" versoin of the GameObject.
///
public GameObject theirs { private set; get; }
public bool applied { protected set; get; }
public string name { private set; get; }
public bool merged { private set; get; }
public bool hasActions
{
get { return actions.Count > 0; }
}
///
/// All actions available for solving specific conflicts on the GameObject.
///
private List actions;
public GameObjectMergeActions(GameObject ours, GameObject theirs)
{
actions = new List();
this.ours = ours;
this.theirs = theirs;
GenerateName();
if (theirs && !ours)
{
actions.Add(new MergeActionNewGameObject(ours, theirs));
}
else if (ours && !theirs)
{
actions.Add(new MergeActionDeleteGameObject(ours, theirs));
}
else if (ours && theirs)
{
FindPropertyDifferences();
FindComponentDifferences();
}
// Some Actions have a default and are merged from the beginning.
// If all the others did was to add GameObjects, we're done with merging from the start.
CheckIfMerged();
}
///
/// Generate a title for this object
///
private void GenerateName()
{
name = "";
if (ours)
{
name = "Your[" + ours.GetPath() + "]";
}
if (theirs)
{
if (ours)
{
name += " vs. ";
}
name += "Their[" + theirs.GetPath() + "]";
}
}
///
/// Finds the differences between properties of the two GameObjects.
/// That means the name, layer, tag... everything that's not part of a Component. Also, the parent.
///
private void FindPropertyDifferences()
{
CheckForDifferentParents();
FindPropertyDifferences(ours, theirs);
}
///
/// Since parenting is quite special, here's some dedicated handling.
///
private void CheckForDifferentParents()
{
var transform = ours.GetComponent();
var ourParent = transform.parent;
var theirParent = theirs.GetComponent().parent;
if (!ObjectID.GetFor(ourParent).Equals(ObjectID.GetFor(theirParent)))
{
actions.Add(new MergeActionParenting(transform, ourParent, theirParent));
}
}
///
/// Check for Components that one of the sides doesn't have, and/or for defferent values
/// on Components.
///
private void FindComponentDifferences()
{
var ourComponents = ours.GetComponents();
var theirComponents = theirs.GetComponents();
// Map "their" Components to their respective ids.
var theirDict = new Dictionary();
foreach (var theirComponent in theirComponents)
{
// Ignore null components.
if (theirComponent != null)
{
theirDict.Add(ObjectID.GetFor(theirComponent), theirComponent);
}
}
foreach (var ourComponent in ourComponents)
{
// Ignore null components.
if (ourComponent == null) continue;
// Try to find "their" equivalent to our Components.
var id = ObjectID.GetFor(ourComponent);
Component theirComponent;
theirDict.TryGetValue(id, out theirComponent);
if (theirComponent) // Both Components exist.
{
FindPropertyDifferences(ourComponent, theirComponent);
// Remove "their" Component from the dict to only keep those new to us.
theirDict.Remove(id);
}
else
{
// Component doesn't exist in their version, offer a deletion.
actions.Add(new MergeActionDeleteComponent(ours, ourComponent));
}
}
// Everything left in the dict is a...
foreach (var theirComponent in theirDict.Values)
{
// ...new Component from them.
actions.Add(new MergeActionNewComponent(ours, theirComponent));
}
}
///
/// Find all the values different in "our" and "their" version of a component.
///
private void FindPropertyDifferences(Object ourObject, Object theirObject)
{
var ourSerializedObject = new SerializedObject(ourObject);
var theirSerializedObject = new SerializedObject(theirObject);
var ourProperty = ourSerializedObject.GetIterator();
if (ourProperty.Next(true))
{
var theirProperty = theirSerializedObject.GetIterator();
theirProperty.Next(true);
var shouldEnterChildren = ourProperty.hasVisibleChildren;
while (ourProperty.NextVisible(shouldEnterChildren))
{
theirProperty.NextVisible(shouldEnterChildren);
// If merging a prefab, ignore the gameobject name.
if (ourObject is GameObject
&& MergeManagerBase.isMergingPrefab
&& ourProperty.GetPlainName() == "Name")
{
continue;
}
if (DifferentValues(ourProperty, theirProperty))
{
// We found a difference, accordingly add a MergeAction.
actions.Add(new MergeActionChangeValues(ours, ourProperty.Copy(), theirProperty.Copy()));
}
}
}
}
///
/// Returns true when the two properties have different values, false otherwise.
///
private static bool DifferentValues(SerializedProperty ourProperty, SerializedProperty theirProperty)
{
if (!ourProperty.IsRealArray())
{
// Regular single-value property.
return DifferentValuesFlat(ourProperty, theirProperty);
}
else
{
// Array property.
if (ourProperty.arraySize != theirProperty.arraySize)
{
return true;
}
var op = ourProperty.Copy();
var tp = theirProperty.Copy();
op.Next(true);
op.Next(true);
tp.Next(true);
tp.Next(true);
for (int i = 0; i < ourProperty.arraySize; ++i)
{
op.Next(false);
tp.Next(false);
if (DifferentValuesFlat(op, tp))
{
return true;
}
}
}
return false;
}
private static bool DifferentValuesFlat(SerializedProperty ourProperty, SerializedProperty theirProperty)
{
var our = ourProperty.GetValue();
var their = theirProperty.GetValue();
if (ourProperty.propertyType == SerializedPropertyType.ObjectReference)
{
if (our != null && their != null)
{
our = ObjectID.GetFor(our as Object);
their = ObjectID.GetFor(their as Object);
}
}
return !object.Equals(our, their);
}
private void CheckIfMerged()
{
merged = actions.TrueForAll(action => action.merged);
}
private static void RefreshPrefabInstance()
{
if (MergeManagerBase.isMergingPrefab)
{
PrefabUtility.RevertObjectOverride(MergeManagerPrefab.ourPrefabInstance, InteractionMode.AutomatedAction);
}
}
///
/// Use "our" version for all conflicts.
/// This is used on all GameObjectMergeActions objects when the merge is aborted.
///
public void UseOurs(bool forceApply = false)
{
foreach (var action in actions)
{
action.UseOurs();
}
merged = true;
applied = forceApply;
}
///
/// Use "their" version for all conflicts.
///
public void UseTheirs(bool forceApply = false)
{
foreach (var action in actions)
{
action.UseTheirs();
}
merged = true;
applied = forceApply;
}
//If the foldout is open
private bool open;
public void OnGUI()
{
// Show only the selected ones
var selection = Selection.activeGameObject;
var objectToHighlight = MergeManagerBase.isMergingPrefab ? MergeManagerPrefab.ourPrefabInstance.GetChildWithEqualPath(ours) : ours;
if (applied || selection == null || selection.gameObject != objectToHighlight)
{
return;
}
if (open)
{
GUI.backgroundColor = new Color(0, 0, 0, .8f);
}
else
{
GUI.backgroundColor = merged ? new Color(0, .5f, 0, .8f) : new Color(.5f, 0, 0, .8f);
}
GUILayout.BeginVertical(Resources.styles.mergeActions);
GUI.backgroundColor = Color.white;
GUILayout.BeginHorizontal();
open = EditorGUILayout.Foldout(open, new GUIContent(name));
// if (ours && GUILayout.Button("Focus", EditorStyles.miniButton, GUILayout.Width(100)))
// {
// // Highlight the instance of the prefab, not the prefab itself.
// // Otherwise, "ours".
// objectToHighlight.Highlight();
// }
// Only show when we resolve the conflict for all merge actions
bool resolved = true;
foreach (var action in actions)
{
if (action.merged == false)
{
resolved = false;
}
}
if (resolved)
{
if (ours && GUILayout.Button("Apply", EditorStyles.miniButton, GUILayout.Width(100)))
{
foreach (var action in actions)
{
action.mergeAction?.Invoke();
}
applied = true;
RefreshPrefabInstance();
}
}
GUILayout.EndHorizontal();
if (open)
{
// Display all merge actions.
foreach (var action in actions)
{
if (action.OnGUIMerge())
{
CheckIfMerged();
}
}
}
else
{
GUILayout.BeginHorizontal();
if (GUILayout.Button("Use ours >>>", EditorStyles.miniButton))
{
UseOurs();
}
if (GUILayout.Button("<<< Use theirs", EditorStyles.miniButton))
{
UseTheirs();
}
GUILayout.EndHorizontal();
}
// If "ours" is null, the GameObject doesn't exist in one of the versions.
// Try to get a reference if the object exists in the current merging state.
// If it exists, the new/gelete MergeAction will have a reference.
if (!ours)
{
foreach (var action in actions)
{
ours = action.ours;
}
}
GUILayout.EndVertical();
GUI.backgroundColor = Color.white;
}
}
}