Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
Node.Clone.Extension.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.Collections.Generic;
5 using System.Linq;
6 using System.Linq.Expressions;
7 using System.Reflection;
8 using SiliconStudio.Shaders.Utility;
9 using LinqExpression = System.Linq.Expressions.Expression;
10 
11 namespace SiliconStudio.Shaders.Ast
12 {
13  /// <summary>
14  /// Provides a dictionary of cloned values, where the [key] is the original object
15  /// and [value] the new object cloned associated to the original object.
16  /// </summary>
17  public class CloneContext : Dictionary<object, object>
18  {
19  /// <summary>
20  /// Internal cache of delegates used on calling DeepClone.
21  /// It avoids to use directly the ThreadStatic field in <see cref="DeepCloner"/>
22  /// </summary>
23  internal Dictionary<DeepCloner.CacheKey, Delegate> Cache;
24 
25  internal bool IsSelfClone;
26 
27  /// <summary>
28  /// Initializes a new instance of the <see cref="CloneContext"/> class.
29  /// </summary>
30  public CloneContext()
31  : this(null)
32  {
33  }
34 
35  /// <summary>
36  /// Initializes a new instance of the <see cref="CloneContext"/> class.
37  /// </summary>
38  /// <param name="parent">The parent context use for chaining several clone context.</param>
39  public CloneContext(CloneContext parent) : base(new ReferenceEqualityComparer<object>())
40  {
41  Parent = parent;
42  }
43 
44  /// <summary>
45  /// Gets the parent.
46  /// </summary>
47  /// <value>The parent.</value>
48  public CloneContext Parent { get; private set; }
49 
50  public new bool ContainsKey(object key)
51  {
52  return base.ContainsKey(key) || (Parent != null && Parent.ContainsKey(key));
53  }
54 
55  public new bool TryGetValue(object key, out object value)
56  {
57  return base.TryGetValue(key, out value) || (Parent != null && Parent.TryGetValue(key, out value));
58  }
59 
60  public new object this[object key]
61  {
62  get
63  {
64  return base[key] ?? (Parent != null ? Parent[key] : null);
65  }
66 
67  set
68  {
69  base[key] = value;
70  }
71  }
72  }
73 
74  /// <summary>
75  /// DeepClone extension.
76  /// </summary>
77  public static class DeepCloner
78  {
79  #region Constants and Fields
80 
81  [ThreadStatic]
82  private static Dictionary<CacheKey, Delegate> deepcache;
83 
84  [ThreadStatic]
85  private static Dictionary<CacheKey, Delegate> deepCacheSelf;
86 
87  private static readonly MethodInfo DeepcloneArray = MethodReflector(() => DeepClone<object>(new object[0], out tempObjects, null));
88 
89  private static readonly MethodInfo DeepcloneObject = MethodReflector(() => DeepClone(ref tempObject, out tempObject, null));
90 
91  private static readonly MethodInfo MethodAdd = typeof(Dictionary<object, object>).GetMethod("Add");
92 
93  private static readonly MethodInfo MethodTryGetValue = typeof(CloneContext).GetMethod("TryGetValue");
94 
95  private static object tempObject = new object();
96 
97  private static object[] tempObjects = new object[0];
98 
99  #endregion
100 
101  #region Delegates
102 
103  private delegate void CloneDelegate<T>(ref T toClone, out T output, CloneContext context);
104 
105  #endregion
106 
107  #region Public Methods
108 
109  private static Dictionary<CacheKey, Delegate> GetCache(CloneContext context)
110  {
111  if (context.IsSelfClone)
112  {
113  return deepCacheSelf ?? (deepCacheSelf = new Dictionary<CacheKey, Delegate>());
114  }
115 
116  return deepcache ?? (deepcache = new Dictionary<CacheKey, Delegate>());
117  }
118 
119  /// <summary>
120  /// Clones deeply the specified object. See remarks.
121  /// </summary>
122  /// <typeparam name="T">Type of the object to clone</typeparam>
123  /// <param name="obj">The object instance to clone recursively.</param>
124  /// <param name="context">The context dictionary to use while cloning this object.</param>
125  /// <returns>A cloned instance of the object</returns>
126  /// <remarks>
127  /// In order to be cloneable, all objects and sub-objects are required to provide a public parameter-less constructor.
128  /// </remarks>
129  public static T DeepClone<T>(this T obj, CloneContext context = null)
130  {
131  if (ReferenceEquals(obj, null))
132  return default(T);
133 
134  // Create a new context if no context was given
135  if (context == null)
136  context = new CloneContext();
137 
138  context.Cache = GetCache(context);
139  T result;
140  DeepClone(ref obj, out result, context);
141  return result;
142  }
143 
144  /// <summary>
145  /// Recursively collects all object references and fill the context with [key] object => [value] object (self referencing).
146  /// </summary>
147  /// <typeparam name="T">Type of the object to collect all object references</typeparam>
148  /// <param name="obj">The object instance to collect object references recursively.</param>
149  /// <param name="context">The context dictionary to fill with collected object references while cloning this object.</param>
150  /// <exception cref="System.ArgumentNullException">context</exception>
151  public static void DeepCollect<T>(T obj, CloneContext context)
152  {
153  if (context == null)
154  throw new ArgumentNullException("context");
155 
156  // Just run the DeepClone in IsSelfClone mode.
157  context.IsSelfClone = true;
158  DeepClone(obj, context);
159  context.IsSelfClone = false;
160  }
161 
162  #endregion
163 
164  #region Methods
165 
166  private static void DeepClone<T>(ref T obj, out T dest, CloneContext context)
167  {
168  if (ReferenceEquals(obj, null))
169  dest = default(T);
170  else
171  {
172  var t = obj.GetType();
173  if (IsPrimitive(t) || obj is Type)
174  {
175  dest = obj;
176  }
177  else if (typeof(T) == t && t.IsValueType)
178  {
179  GetObjectCloner<T>(typeof(T), context)(ref obj, out dest, context);
180  }
181  else
182  {
183  object localDest = null;
184  if (!context.TryGetValue(obj, out localDest))
185  {
186  GetObjectCloner<T>(t, context)(ref obj, out dest, context);
187  }
188  else
189  {
190  dest = (T)localDest;
191  }
192  }
193  }
194  }
195 
196  private static void DeepClone<T>(T[] obj, out T[] dest, CloneContext context)
197  {
198  if (ReferenceEquals(obj, null))
199  dest = null;
200  else
201  {
202  object arrayObj;
203  if (context.TryGetValue(obj, out arrayObj))
204  dest = (T[])arrayObj;
205  else
206  {
207  var t = typeof(T);
208  if (IsPrimitive(t) || t == typeof(Type))
209  {
210  dest = context.IsSelfClone ? obj : (T[])obj.Clone();
211  }
212  else
213  {
214  dest = context.IsSelfClone ? obj : new T[obj.Length];
215  context.Add(obj, dest);
216  for (var i = 0; i < dest.Length; i++)
217  DeepClone(ref obj[i], out dest[i], context);
218  }
219  }
220  }
221  }
222 
223  private static List<FieldInfo> GetFields(Type type)
224  {
225  var fields = new List<FieldInfo>();
226  var t = type;
227  while (t != null)
228  {
229  fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
230  t = t.BaseType;
231  }
232 
233  return fields;
234  }
235 
236  private static CloneDelegate<T> GetObjectCloner<T>(Type type, CloneContext context)
237  {
238  Delegate result;
239  var cache = context.Cache;
240 
241  var key = new CacheKey(typeof(T), type);
242  if (!cache.TryGetValue(key, out result))
243  {
244  var statements = new List<LinqExpression>();
245 
246  var param = LinqExpression.Parameter(typeof(T).MakeByRefType(), "input");
247  var output = LinqExpression.Parameter(typeof(T).MakeByRefType(), "output");
248  var contextParam = LinqExpression.Parameter(typeof(CloneContext), "context");
249 
250  ParameterExpression localCast;
251  ParameterExpression localOutput;
252  ParameterExpression localObj;
253  bool isStruct = type.IsValueType && typeof(T) == type;
254 
255  if (isStruct)
256  {
257  localCast = param;
258  localOutput = output;
259  localObj = null;
260  }
261  else
262  {
263  localObj = LinqExpression.Variable(typeof(object), "localObj");
264  localCast = LinqExpression.Variable(type, "localCast");
265  localOutput = LinqExpression.Variable(type, "localOut");
266 
267  statements.Add(LinqExpression.Assign(localCast, LinqExpression.Convert(param, type)));
268 
269  if (context.IsSelfClone)
270  {
271  statements.Add(LinqExpression.Call(contextParam, MethodAdd, param, param));
272  statements.Add(LinqExpression.Assign(output, localCast));
273  }
274  else
275  {
276  // NOTE: this can fail if there is no default constructor for type.
277  // for example Dictionary<K,T>.ValueCollection, which is created when a access to members Keys or Values is performed on the dicitonary.
278  statements.Add(LinqExpression.Assign(localOutput, LinqExpression.New(type)));
279 
280 
281  statements.Add(LinqExpression.Assign(output, localOutput));
282  statements.Add(LinqExpression.Call(contextParam, MethodAdd, param, localOutput));
283  }
284  }
285 
286  var fields = GetFields(type);
287 
288  // If we have a struct with primitive only
289  if (isStruct && fields.All(field => IsPrimitive(field.FieldType)))
290  {
291  statements.Add(LinqExpression.Assign(localOutput, param));
292  }
293  else
294  {
295  foreach (var field in fields)
296  {
297  if (field.IsInitOnly)
298  throw new InvalidOperationException(String.Format("Field [{0}] in [{1}] is readonly, which is not supported in DeepClone", field.Name, type));
299 
300  var t = field.FieldType;
301  if (t.IsSubclassOf(typeof(Delegate)))
302  continue;
303 
304  var value = LinqExpression.Field(localCast, field);
305 
306  LinqExpression cloneField = null;
307  if (IsPrimitive(t))
308  {
309  if (!context.IsSelfClone)
310  {
311  cloneField = LinqExpression.Assign(LinqExpression.Field(localOutput, field), value);
312  }
313  }
314  else
315  {
316  MethodInfo genericCloneMethod;
317  if (field.FieldType.IsArray)
318  {
319  genericCloneMethod = DeepcloneArray.GetGenericMethodDefinition();
320  genericCloneMethod = genericCloneMethod.MakeGenericMethod(new[] {field.FieldType.GetElementType()});
321  }
322  else
323  {
324  genericCloneMethod = DeepcloneObject.GetGenericMethodDefinition();
325  genericCloneMethod = genericCloneMethod.MakeGenericMethod(new[] {field.FieldType});
326  }
327 
328  cloneField = LinqExpression.Call(genericCloneMethod, value, context.IsSelfClone ? value : LinqExpression.Field(localOutput, field), contextParam);
329  }
330 
331  if (cloneField != null)
332  {
333  statements.Add(cloneField);
334  }
335  }
336  }
337 
338  LinqExpression body;
339  if (isStruct)
340  {
341  if (statements.Count == 0)
342  {
343  body = LinqExpression.Empty();
344  }
345  else
346  {
347  body = LinqExpression.Block(statements);
348  }
349  }
350  else
351  {
352  var innerBody = LinqExpression.Block(new[] { localCast, localOutput }, statements);
353 
354  body = LinqExpression.Block(
355  new[] { localObj },
356  LinqExpression.IfThenElse(
357  LinqExpression.Call(contextParam, MethodTryGetValue, param, localObj), LinqExpression.Assign(output, LinqExpression.Convert(localObj, typeof(T))), innerBody));
358  }
359 
360  var tempLambda = LinqExpression.Lambda<CloneDelegate<T>>(body, param, output, contextParam);
361  result = tempLambda.Compile();
362  cache.Add(key, result);
363  }
364 
365  return (CloneDelegate<T>)result;
366  }
367 
368  private static bool IsPrimitive(Type type)
369  {
370  return type.IsPrimitive || type.IsEnum || type == typeof(string);
371  }
372 
373  private static MethodInfo MethodReflector(Expression<Action> access)
374  {
375  return ((MethodCallExpression)access.Body).Method;
376  }
377 
378  internal struct CacheKey : IEquatable<CacheKey>
379  {
380  private readonly Type type;
381  private readonly Type realType;
382 
383  public CacheKey(Type type, Type realType)
384  : this()
385  {
386  this.type = type;
387  this.realType = realType;
388  }
389 
390  public bool Equals(CacheKey other)
391  {
392  return type == other.type && realType == other.realType;
393  }
394 
395  public override bool Equals(object obj)
396  {
397  if (ReferenceEquals(null, obj)) return false;
398  return obj is CacheKey && Equals((CacheKey)obj);
399  }
400 
401  public override int GetHashCode()
402  {
403  return (type.GetHashCode() * 397) ^ realType.GetHashCode();
404  }
405  }
406 
407 
408  #endregion
409  }
410 }
new bool TryGetValue(object key, out object value)
CloneContext()
Initializes a new instance of the CloneContext class.
CloneContext(CloneContext parent)
Initializes a new instance of the CloneContext class.
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
Provides a dictionary of cloned values, where the [key] is the original object and [value] the new ob...
char * dest
Definition: lz4.h:61
System.Linq.Expressions.Expression LinqExpression