Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssetMerge.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Diagnostics;
5 using System.IO;
6 using System.Linq;
7 using Microsoft.Win32;
8 using SiliconStudio.Assets.Visitors;
9 using SiliconStudio.Core.Diagnostics;
10 
11 namespace SiliconStudio.Assets.Diff
12 {
13  /// <summary>
14  /// Merges asset differences.
15  /// </summary>
16  public static class AssetMerge
17  {
18  private static readonly Logger Log = GlobalLogger.GetLogger("AssetMerge");
19 
20  /// <summary>
21  /// A policy that returns the change to apply to the current <see cref="Diff3Node"/>.
22  /// </summary>
23  /// <param name="node">The node.</param>
24  /// <returns>The type of merge for the node.</returns>
25  public delegate Diff3ChangeType MergePolicyDelegate(Diff3Node node);
26 
27  static AssetMerge()
28  {
29  try
30  {
31  // TODO We need to discover different diff tools
32  var key = Registry.CurrentUser.OpenSubKey("Software\\KDiff3");
33  if (key != null)
34  {
35  var kDiffPath = key.GetValue(null) as string;
36  if (kDiffPath != null)
37  {
38  DefaultMergeTool = Path.Combine(kDiffPath, "kdiff3.exe");
39  }
40  key.Close();
41  }
42  if (DefaultMergeTool == null)
43  {
44  Log.Error("Unable to find a default merge tool");
45  }
46  }
47  catch (Exception ex)
48  {
49  Log.Error("Unable to find a default merge tool", ex);
50  }
51  }
52 
53  /// <summary>
54  /// Gets or sets the default merge tool exe filepath.
55  /// </summary>
56  /// <value>The default merge tool exe filepath.</value>
57  public static string DefaultMergeTool { get; set; }
58 
59  /// <summary>
60  /// Merges the specified assets from <c>base</c> and <c>from2</c> into <c>from1</c>.
61  /// </summary>
62  /// <param name="assetBase">The asset base.</param>
63  /// <param name="assetFrom1">The asset from1.</param>
64  /// <param name="assetFrom2">The asset from2.</param>
65  /// <param name="mergePolicy">The merge policy. See <see cref="AssetMergePolicies" /> for default policies.</param>
66  /// <returns>The result of the merge.</returns>
67  /// <exception cref="System.ArgumentNullException">
68  /// assetFrom1
69  /// or
70  /// mergePolicy
71  /// </exception>
72  public static MergeResult Merge(Asset assetBase, Asset assetFrom1, Asset assetFrom2, MergePolicyDelegate mergePolicy)
73  {
74  if (assetFrom1 == null) throw new ArgumentNullException("assetFrom1");
75  if (mergePolicy == null) throw new ArgumentNullException("mergePolicy");
76  return Merge(new AssetDiff((Asset)AssetCloner.Clone(assetBase), (Asset)AssetCloner.Clone(assetFrom1), (Asset)AssetCloner.Clone(assetFrom2)), mergePolicy);
77  }
78 
79  /// <summary>
80  /// Merges the specified assets from <c>base</c> and <c>from2</c> into <c>from1</c>.
81  /// </summary>
82  /// <param name="assetDiff">A precomputed asset difference.</param>
83  /// <param name="mergePolicy">The merge policy.</param>
84  /// <param name="previewOnly">if set to <c>true</c> then the merge will not change the object.</param>
85  /// <returns>MergePreviewResult.</returns>
86  /// <exception cref="System.ArgumentNullException">assetDiff
87  /// or
88  /// mergePolicy</exception>
89  public static MergeResult Merge(AssetDiff assetDiff, MergePolicyDelegate mergePolicy, bool previewOnly = false)
90  {
91  if (assetDiff == null) throw new ArgumentNullException("assetDiff");
92  if (mergePolicy == null) throw new ArgumentNullException("mergePolicy");
93 
94  var allDiffs = assetDiff.Compute();
95  var diff3 = allDiffs.FindDifferences().ToList();
96 
97  var result = new MergeResult(assetDiff.Asset1);
98 
99  // Try to merge
100  foreach (var diff3Node in diff3)
101  {
102  Diff3ChangeType changeType;
103  try
104  {
105  changeType = mergePolicy(diff3Node);
106 
107  if (changeType >= Diff3ChangeType.Conflict)
108  {
109  result.Error("Unresolved conflict [{0}] on node [{1}/{2}/{3}]", diff3Node.ChangeType, diff3Node.BaseNode, diff3Node.Asset1Node, diff3Node.Asset2Node);
110  continue;
111  }
112 
113  // If we are in preview only mode, just skip the update
114  if (previewOnly)
115  {
116  continue;
117  }
118 
119  // As we are merging into asset1, the only relevant changes can only come from asset2
120  if (changeType != Diff3ChangeType.MergeFromAsset2)
121  {
122  continue;
123  }
124 
125  var dataInstance = diff3Node.Asset2Node != null ? diff3Node.Asset2Node.Instance : null;
126 
127  // Sets the value on the node
128  diff3Node.ReplaceValue(dataInstance, node => node.Asset1Node, diff3Node.Asset2Node == null);
129  }
130  catch (Exception ex)
131  {
132  result.Error("Unexpected error while merging [{0}] on node [{1}]", ex, diff3Node.ChangeType, diff3Node.InstanceType);
133  break;
134  }
135  }
136 
137  return result;
138  }
139 
140  /// <summary>
141  /// 3-way merge assets using an external diff tool.
142  /// </summary>
143  /// <param name="assetBase0">The asset base0.</param>
144  /// <param name="assetFrom1">The asset from1.</param>
145  /// <param name="assetFrom2">The asset from2.</param>
146  /// <returns>The result of the merge.</returns>
147  public static MergeResult MergeWithExternalTool(Asset assetBase0, Asset assetFrom1, Asset assetFrom2)
148  {
149  var result = new MergeResult();
150 
151  // If they are all null, nothing to do
152  if (assetBase0 == null && assetFrom1 == null && assetFrom2 == null)
153  {
154  return result;
155  }
156 
157  if (!File.Exists(DefaultMergeTool))
158  {
159  result.Error("Unable to use external diff3 merge tool [{0}]. File not found", DefaultMergeTool);
160  return result;
161  }
162 
163  var assetBase = (Asset)AssetCloner.Clone(assetBase0);
164  var asset1 = (Asset)AssetCloner.Clone(assetFrom1);
165  var asset2 = (Asset)AssetCloner.Clone(assetFrom2);
166 
167  // Clears base as we are not expecting to work with them directly
168  // The real base must be passed by the assetBase0 parameter
169  if (assetBase != null)
170  {
171  assetBase.Base = null;
172  }
173  if (asset1 != null)
174  {
175  asset1.Base = null;
176  }
177  if (asset2 != null)
178  {
179  asset2.Base = null;
180  }
181 
182  var assetBasePath = Path.GetTempFileName();
183  var asset1Path = Path.GetTempFileName();
184  var asset2Path = Path.GetTempFileName();
185  try
186  {
187  AssetSerializer.Save(assetBasePath, assetBase);
188  AssetSerializer.Save(asset1Path, asset1);
189  AssetSerializer.Save(asset2Path, asset2);
190  }
191  catch (Exception exception)
192  {
193  result.Error("Unexpected error while serializing assets on disk before using diff tool", exception);
194  return result;
195  }
196 
197  var outputPath = Path.GetTempFileName();
198  try
199  {
200  // TODO We need to access different diff tools command line
201  // kdiff3.exe file1 file2 file3 -o outputfile
202  var process = Process.Start(DefaultMergeTool, string.Format("{0} {1} {2} -o {3}", assetBasePath, asset1Path, asset2Path, outputPath));
203  if (process == null)
204  {
205  result.Error("Unable to launch diff3 tool exe from [{0}]", DefaultMergeTool);
206  }
207  else
208  {
209  process.WaitForExit();
210 
211  if (process.ExitCode != 0)
212  {
213  result.Error("Error, failed to merge files");
214  }
215  }
216  }
217  catch (Exception ex)
218  {
219  result.Error("Unable to launch diff3 tool exe from [{0}]", ex, DefaultMergeTool);
220  }
221 
222  if (!result.HasErrors)
223  {
224  try
225  {
226  var mergedAsset = (Asset)AssetSerializer.Load(outputPath);
227 
228  if (mergedAsset != null)
229  {
230  if (assetFrom1 == null)
231  {
232  mergedAsset.Base = assetFrom2 == null ? assetBase0.Base : assetFrom2.Base;
233  }
234  else
235  {
236  mergedAsset.Base = assetFrom1.Base;
237  }
238  }
239  result.Asset = mergedAsset;
240  }
241  catch (Exception ex)
242  {
243  result.Error("Unexpected exception while loading merged assets from [{0}]", ex, outputPath);
244  }
245 
246  }
247 
248  return result;
249  }
250  }
251 }
static MergeResult Merge(Asset assetBase, Asset assetFrom1, Asset assetFrom2, MergePolicyDelegate mergePolicy)
Merges the specified assets from base and from2 into from1.
Definition: AssetMerge.cs:72
Result of a merge. Contains Asset != null if there are no errors.
Definition: MergeResult.cs:10
Merges asset differences.
Definition: AssetMerge.cs:16
Base class for Asset.
Definition: Asset.cs:14
static MergeResult Merge(AssetDiff assetDiff, MergePolicyDelegate mergePolicy, bool previewOnly=false)
Merges the specified assets from base and from2 into from1.
Definition: AssetMerge.cs:89
static MergeResult MergeWithExternalTool(Asset assetBase0, Asset assetFrom1, Asset assetFrom2)
3-way merge assets using an external diff tool.
Definition: AssetMerge.cs:147
static object Load(string filePath)
Deserializes an Asset from the specified stream.
Base implementation for ILogger.
Definition: Logger.cs:10
System.IO.File File
Main entry point for serializing/deserializing Asset.
object Clone()
Clones the current value of this cloner with the specified new shadow registry (optional) ...
Definition: AssetCloner.cs:43
Class AssetDiff. This class cannot be inherited.
Definition: AssetDiff.cs:15
Output message to log right away.
Allows to clone an asset or values stored in an asset.
Definition: AssetCloner.cs:14