Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
DataConverterGenerator.Members.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 
7 using Mono.Cecil;
8 
9 using SiliconStudio.AssemblyProcessor;
10 using SiliconStudio.Core.Serialization.Converters;
11 using SiliconStudio.Paradox.Data;
12 
13 namespace SiliconStudio.Paradox.VisualStudio.Commands.DataGenerator
14 {
15  public partial class DataConverterGenerator
16  {
17  private Dictionary<TypeDefinition, DataTypeInfo> processedTypes = new Dictionary<TypeDefinition, DataTypeInfo>();
18  private Dictionary<TypeDefinition, DataConverterInfo> processedConverterTypes = new Dictionary<TypeDefinition, DataConverterInfo>();
19  private DataPropertyTypeVisitor dataPropertyTypeVisitor;
20  private AssemblyDefinition assembly;
21 
22  private TypeDefinition listType;
23  private TypeDefinition dictionaryType;
24 
25  private string currentNamespace = string.Empty;
26 
27  public DataConverterGenerator(IAssemblyResolver assemblyResolver, AssemblyDefinition assembly)
28  {
29  this.assembly = assembly;
30 
31  // Early exit if Serialization assembly is not referenced.
32  AssemblyDefinition mscorlibAssembly;
33  try
34  {
35  // In case assembly has been modified,
36  // add AssemblyProcessedAttribute to assembly so that it doesn't get processed again
37  mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly);
38  if (mscorlibAssembly == null)
39  throw new InvalidOperationException("Missing mscorlib.dll from assembly");
40  }
41  catch
42  {
43  return;
44  }
45 
46  listType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(List<>).FullName);
47  dictionaryType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Dictionary<,>).FullName);
48 
49  this.dataPropertyTypeVisitor = new DataPropertyTypeVisitor(this);
50 
51  // Process types with DataConverterAttribute
52  foreach (var type in assembly.EnumerateTypes())
53  {
54  // Generic types are not supported.
55  if(type.HasGenericParameters)
56  continue;
57 
58  // Do not allow nested type to be generic (not properly supported, since it could be the enclosing type which is generic)
59  if (!(type.IsPublic || (type.IsNestedPublic && !type.HasGenericParameters)))
60  continue;
61 
62  var dataInfo = this.GenerateDataTypeInfo(type);
63  if (dataInfo != null)
64  {
65  var dataConverterInfo = new DataConverterInfo
66  {
67  Generate = (dataInfo.Flags & DataTypeFlags.Generated) != 0,
68  DataInfo = dataInfo,
69  Type = this.GetDataConverterType(type),
70  };
71 
72  this.processedTypes.Add(type, dataInfo);
73  this.processedConverterTypes.Add(type, dataConverterInfo);
74  }
75 
76  var additionalConverterAttribute = GetAdditionalDataAttribute(type);
77  if (additionalConverterAttribute != null)
78  {
79  var baseTypeArgument = GetAdditionalBaseType(type);
80  var dataConverterInfo = new DataConverterInfo
81  {
82  DataInfo = this.GenerateDataTypeInfo(baseTypeArgument),
83  Type = this.GetDataConverterType(type),
84  };
85 
86  foreach (var namedArgument in additionalConverterAttribute.Properties)
87  {
88  switch (namedArgument.Name)
89  {
90  case "AutoGenerate":
91  dataConverterInfo.Generate = (bool)namedArgument.Argument.Value;
92  break;
93  }
94  }
95 
96  this.processedConverterTypes.Add(type, dataConverterInfo);
97  }
98  }
99  }
100 
101  private DataTypeInfo GenerateDataTypeInfo(TypeDefinition type)
102  {
103  DataTypeInfo existingDataTypeInfo;
104  if (this.processedTypes.TryGetValue(type, out existingDataTypeInfo))
105  return existingDataTypeInfo;
106 
107  var flags = GetDataTypeFlags(type);
108  if (flags == null || ((flags.Value & DataTypeFlags.NoDataType) != 0))
109  return null;
110 
111  // Add properties
112  var properties = new List<DataProperty>();
113  if ((flags.Value & DataTypeFlags.Generated) != 0)
114  {
115  properties.AddRange(this.EnumerateProperties(type));
116  }
117 
118  return new DataTypeInfo
119  {
120  Type = this.GetDataType(type),
121  BaseType = (flags.Value & DataTypeFlags.Generated) != 0 ? this.GetDataType(type.BaseType.Resolve()) : null,
122  Properties = properties,
123  Flags = flags.Value,
124  };
125  }
126 
127  /// <summary>
128  /// Properly opens/closes namespace.
129  /// </summary>
130  /// <param name="newNamespace">The new namespace.</param>
131  private void ChangeNamespace(string newNamespace)
132  {
133  if (this.currentNamespace != newNamespace)
134  {
135  if (this.currentNamespace != string.Empty)
136  {
137  this.Write("}\r\n");
138  this.Write("\r\n");
139  }
140 
141  this.currentNamespace = newNamespace;
142 
143  if (this.currentNamespace != string.Empty)
144  {
145  this.Write("namespace {0}\r\n", this.currentNamespace);
146  this.Write("{\r\n");
147  }
148  }
149  }
150 
151  private TypeReference GetDataConverterType(TypeDefinition type)
152  {
153  // Since we don't write the IL code back but use a C# code writer, no need for it to have real module/scopes.
154  return new TypeReference(type.Namespace + ".Data", type.Name + "DataConverter", type.Module, type.Scope, false);
155  }
156 
157  private TypeReference GetDataType(TypeDefinition type)
158  {
159  var dataConverterAttribute = GetDataConverterAttribute(type);
160  if (dataConverterAttribute == null)
161  return null;
162 
163  var flags = GetDataTypeFlags(type);
164  if (flags != null && ((flags.Value & DataTypeFlags.NoDataType) != 0))
165  return type;
166 
167  var namedArgument = dataConverterAttribute.Properties.FirstOrDefault(x => x.Name == "DataTypeName");
168  var name = (namedArgument.Name != null) ? (string)namedArgument.Argument.Value : type.Name + "Data";
169 
170  var lastDot = name.LastIndexOf('.');
171  var @namespace = lastDot != -1 ? name.Substring(lastDot + 1) : type.Namespace + ".Data";
172 
173  // Since we don't write the IL code back but use a C# code writer, no need for it to have real module/scopes.
174  return new TypeReference(@namespace, name, type.Module, type.Scope, false);
175  }
176 
177  private CustomAttribute GetAdditionalDataAttribute(TypeDefinition type)
178  {
179  // Is it a DataAdditionalConverterAttribute?
180  // If yes, resolve through its BaseType.
181  return GetDataConverterAttribute(type, typeof(DataAdditionalConverterAttribute).FullName);
182  }
183 
184  private TypeDefinition GetAdditionalBaseType(TypeDefinition type)
185  {
186  // Is it a DataAdditionalConverterAttribute?
187  // If yes, resolve through its BaseType.
188  var additionalConverterAttribute = GetAdditionalDataAttribute(type);
189  if (additionalConverterAttribute != null)
190  {
191  var baseTypeArgumentReference = (TypeReference)additionalConverterAttribute.ConstructorArguments[0].Value;
192  var baseTypeArgument = baseTypeArgumentReference.Resolve();
193 
194  return baseTypeArgument;
195  }
196  return null;
197  }
198 
199  private static CustomAttribute GetDataConverterAttribute(TypeDefinition type, string name = "SiliconStudio.Core.Serialization.Converters.DataConverterAttribute")
200  {
201  // Check if DataConverter is set on this class, without any argument
202  // We either include only DataConverterAttribute without arguments (if defined == false) or all of them (defined == true)
203  var dataConverterAttribute =
204  type.CustomAttributes.FirstOrDefault(
205  x => x.AttributeType.FullName == name);
206  if (dataConverterAttribute != null)
207  return dataConverterAttribute;
208 
209  return null;
210  }
211 
212  private static DataTypeFlags? GetDataTypeFlags(TypeDefinition type)
213  {
214  var dataConverterAttribute = GetDataConverterAttribute(type);
215 
216  if (dataConverterAttribute == null)
217  return null;
218 
219  var flags = DataTypeFlags.None;
220 
221  foreach (var namedArgument in dataConverterAttribute.Properties)
222  {
223  switch (namedArgument.Name)
224  {
225  case "DataType":
226  if (!(bool)namedArgument.Argument.Value)
227  flags |= DataTypeFlags.NoDataType;
228  break;
229  case "AutoGenerate":
230  if ((bool)namedArgument.Argument.Value)
231  flags |= DataTypeFlags.Generated;
232  break;
233  case "ContentReference":
234  if ((bool)namedArgument.Argument.Value)
235  flags |= DataTypeFlags.ContentReference;
236  break;
237  case "CustomConvertToData":
238  if ((bool)namedArgument.Argument.Value)
239  flags |= DataTypeFlags.CustomConvertToData;
240  break;
241  case "CustomConvertFromData":
242  if ((bool)namedArgument.Argument.Value)
243  flags |= DataTypeFlags.CustomConvertFromData;
244  break;
245  }
246  }
247 
248  // Check if type is an EntityComponent (or inherits from one
249  while (type != null)
250  {
251  if (type.FullName == typeof(EntityModel.EntityComponent).FullName)
252  flags |= DataTypeFlags.EntityComponent;
253 
254  // TODO: Resolve with ResolveGenericsVisitor
255  type = type.BaseType != null ? type.BaseType.Resolve() : null;
256  }
257 
258  return flags;
259  }
260 
261  private IEnumerable<DataProperty> EnumerateProperties(TypeDefinition type)
262  {
263  // Only deal with writable public properties
264  foreach (var property in type.Properties)
265  {
266  if (!property.CustomAttributes.Any(x => x.AttributeType.Name == typeof(DataMemberConvertAttribute).Name))
267  continue;
268 
269  var dataProperty = CreateDataProperty(property.Name, property.PropertyType, property.SetMethod != null && (property.SetMethod.IsPublic || property.SetMethod.IsAssembly));
270  if (dataProperty != null)
271  yield return dataProperty;
272  }
273 
274  // and fields
275  foreach (var field in type.Fields)
276  {
277  if (!field.CustomAttributes.Any(x => x.AttributeType.Name == typeof(DataMemberConvertAttribute).Name))
278  continue;
279 
280  var dataProperty = CreateDataProperty(field.Name, field.FieldType, true);
281  if (dataProperty != null)
282  yield return dataProperty;
283  }
284  }
285 
286  private DataProperty CreateDataProperty(string memberName, TypeReference memberType, bool hasPublicSetAccessor)
287  {
288  var originalTypeReference = memberType;
289  var dataType = this.dataPropertyTypeVisitor.VisitDynamic(originalTypeReference);
290 
291  var dataProperty = new DataProperty
292  {
293  HasPublicSetAccessor = hasPublicSetAccessor,
294  OriginalType = originalTypeReference,
295  DataType = dataType,
296  Name = memberName,
297  };
298 
299  // In some case (property has no public setter and its type is IList/IDictionary,
300  // we want to create it automatically in the Data type as well).
301  // Example: IList<A> Member { get; private set; } will result in IList<AData> Member = new List<AData>();
302  if (!dataProperty.HasPublicSetAccessor
303  && dataType.IsGenericInstance)
304  {
305  var dataTypeGenericInstance = (GenericInstanceType)dataType;
306  var elementType = dataType.GetElementType().Resolve();
307 
308  foreach (var genericPattern in new[]
309  {
310  new {GenericType = typeof(IList<>), InterfaceInitializerType = listType},
311  new {GenericType = typeof(IDictionary<,>), InterfaceInitializerType = dictionaryType}
312  })
313  {
314  // Is it IList<> or does it implement IList<>?
315  if (elementType.FullName == genericPattern.GenericType.FullName)
316  {
317  // IList<T> => use List<T>
318  dataProperty.InitializerType =
319  dataTypeGenericInstance.ChangeGenericInstanceType(genericPattern.InterfaceInitializerType,
320  dataTypeGenericInstance.GenericArguments);
321  break;
322  }
323  if (
324  elementType.Interfaces.Any(
325  x => x.IsGenericInstance && x.GetElementType().FullName == genericPattern.GenericType.FullName))
326  {
327  // Implements IList<T>, if it has an empty constructor let's use it
328  if (elementType.Methods.Any(x => x.IsConstructor && x.Parameters.Count == 0))
329  {
330  dataProperty.InitializerType = dataType;
331  break;
332  }
333  }
334  }
335  }
336 
337  return dataProperty;
338  }
339 
340  class DataProperty
341  {
342  public bool HasPublicSetAccessor;
343  public TypeReference InitializerType;
344  public TypeReference OriginalType;
345  public TypeReference DataType;
346  public string Name;
347  }
348 
349  class DataPropertyTypeVisitor : CecilTypeReferenceVisitor
350  {
351  private DataConverterGenerator dataConverterGenerator;
352  private TypeReference contentReferenceType;
353  private TypeReference entityComponentReferenceType;
354 
355  public DataPropertyTypeVisitor(DataConverterGenerator dataConverterGenerator)
356  {
357  this.dataConverterGenerator = dataConverterGenerator;
358  var coreSerializationAssembly = dataConverterGenerator.assembly.MainModule.AssemblyResolver.Resolve("SiliconStudio.Core.Serialization");
359  this.contentReferenceType = dataConverterGenerator.assembly.MainModule.Import(coreSerializationAssembly.MainModule.GetTypeResolved("SiliconStudio.Core.Serialization.ContentReference`1"));
360  }
361 
362  public TypeReference TryTransformToDataType(TypeReference type)
363  {
364  var resolvedType = type.Resolve();
365  var dataType = this.dataConverterGenerator.GetDataType(resolvedType);
366  if (dataType == null)
367  {
368  // Try to go through DataAdditionalConverterAttribute
369  resolvedType = this.dataConverterGenerator.GetAdditionalBaseType(resolvedType);
370  if (resolvedType == null)
371  return type;
372  dataType = this.dataConverterGenerator.GetDataType(resolvedType);
373  }
374 
375  var flags = GetDataTypeFlags(resolvedType).Value;
376 
377  // Special case: EntityComponentReference<>
378  // TODO: Make this system more flexible instead of hardcoding it here
379  if ((flags & DataTypeFlags.EntityComponent) != 0)
380  {
381  if (entityComponentReferenceType == null)
382  {
383  var engineAssembly = dataConverterGenerator.assembly.MainModule.AssemblyResolver.Resolve("SiliconStudio.Paradox.Engine");
384  entityComponentReferenceType = dataConverterGenerator.assembly.MainModule.Import(engineAssembly.MainModule.GetTypeResolved(typeof(EntityComponentReference<>).FullName));
385  }
386 
387  // Create a EntityComponentReference<> of the source type (not the data type, since we only care about the PropertyKey<>)
388  var result = new GenericInstanceType(entityComponentReferenceType);
389  result.GenericArguments.Add(type);
390  dataType = result;
391  }
392  else if ((flags & DataTypeFlags.ContentReference) != 0)
393  {
394  // If there is a data type, transform it to a ContentReference<> of this data type.
395  var result = new GenericInstanceType(this.contentReferenceType);
396  result.GenericArguments.Add(dataType);
397  dataType = result;
398  }
399 
400  return dataType;
401  }
402 
403  public override TypeReference Visit(TypeReference type)
404  {
405  type = this.TryTransformToDataType(type);
406 
407  return base.Visit(type);
408  }
409  }
410 
411  class DataTypeInfo
412  {
413  public TypeReference Type;
414  public TypeReference BaseType;
415  public DataTypeFlags Flags;
416  public List<DataProperty> Properties;
417  }
418 
419  class DataConverterInfo
420  {
421  public bool Generate;
422  public DataTypeInfo DataInfo;
423  public TypeReference Type;
424  }
425 
426  [Flags]
427  enum DataTypeFlags
428  {
429  None = 0,
430  Generated = 1,
431  ContentReference = 2,
432  CustomConvertToData = 4,
433  CustomConvertFromData = 8,
434  NoDataType = 16, // No need for a separate data type (useful to only embed object in a ContentReference as is)
435  EntityComponent = 32,
436  }
437  }
438 }
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ DXGI_FORMAT _In_ DWORD flags
Definition: DirectXTexP.h:170
DataConverterGenerator(IAssemblyResolver assemblyResolver, AssemblyDefinition assembly)
Flags
Enumeration of the new Assimp's flags.
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}.
Visit Cecil types recursively, and replace them if requested.