Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PropertyContainer.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;
5 using System.Collections.Generic;
6 using System.Collections.ObjectModel;
7 using System.Reflection;
8 using SiliconStudio.Core.Reflection;
9 using SiliconStudio.Core.Serialization.Serializers;
10 
11 namespace SiliconStudio.Core
12 {
13  /// <summary>
14  /// Represents a container that can hold properties, lightweight to embed (lazy initialized).
15  /// </summary>
16  /// <remarks>
17  /// Tag properties system purpose is to allow binding of properties that aren't logically supposed to be
18  /// in a general class (probably because the property exists only in a higher level part of the engine).
19  /// A typical example includes source mesh, collision data and various bouding volumes for a Geometry object:
20  /// including them directly in the low-level Geometry class would be a terrible design decision !
21  /// And the other well known solution, which consist of maintaining a Dictionary of object to properties
22  /// isn't really nice either (especially with non-deterministic object destruction, it's hard to clean it up, would require lot of events).
23  /// As a result, a specific system has been implemented.
24  /// A class that could hold such tag properties should have an instance of <see cref="PropertyContainer"/> as a mutable field member.
25  /// An cool feature of this system is that if a property doesn't exist, it could be generated during first access from a delegate or come from a default value.
26  /// </remarks>
27  [DataContract]
28  [DataSerializer(typeof(PropertyContainer.Serializer))]
29  public partial struct PropertyContainer : IEnumerable<KeyValuePair<PropertyKey, object>> //IDictionary<PropertyKey, object>
30  {
31  private static readonly Dictionary<Type, List<PropertyKey>> accessorProperties = new Dictionary<Type, List<PropertyKey>>();
32  private Dictionary<PropertyKey, object> properties;
33  private object owner;
34  private static ReadOnlyCollection<PropertyKey> emptyKeys = new ReadOnlyCollection<PropertyKey>(new List<PropertyKey>());
35  private static ReadOnlyCollection<object> emptyValues = new ReadOnlyCollection<object>(new List<object>());
36 
37  /// <summary>
38  /// Property changed delegate.
39  /// </summary>
40  /// <param name="container">The property container.</param>
41  /// <param name="key">The property key.</param>
42  /// <param name="newValue">The property new value.</param>
43  /// <param name="oldValue">The property old value.</param>
44  public delegate void PropertyUpdatedDelegate(ref PropertyContainer propertyContainer, PropertyKey propertyKey, object newValue, object oldValue);
45 
46  /// <summary>
47  /// Occurs when a property is modified.
48  /// </summary>
49  public event PropertyUpdatedDelegate PropertyUpdated;
50 
51  public PropertyContainer(object owner)
52  {
53  properties = null;
54  PropertyUpdated = null;
55  this.owner = owner;
56  }
57 
58  public object Owner
59  {
60  get
61  {
62  return owner;
63  }
64  private set
65  {
66  owner = value;
67  }
68  }
69 
70  /// <summary>
71  /// Gets the key-properties value pairs in this instance.
72  /// </summary>
73  public IEnumerator<KeyValuePair<PropertyKey, object>> GetEnumerator()
74  {
75  if (properties != null)
76  {
77  foreach (var property in properties)
78  {
79  yield return new KeyValuePair<PropertyKey, object>(property.Key, property.Value);
80  }
81  }
82 
83  if (Owner != null)
84  {
85  var currentType = Owner.GetType();
86  while (currentType != null)
87  {
88  List<PropertyKey> typeAccessorProperties;
89  if (accessorProperties.TryGetValue(currentType, out typeAccessorProperties))
90  {
91  foreach (var accessorProperty in typeAccessorProperties)
92  {
93  yield return new KeyValuePair<PropertyKey, object>(accessorProperty, accessorProperty.AccessorMetadata.GetValue(ref this));
94  }
95  }
96 
97  currentType = currentType.GetTypeInfo().BaseType;
98  }
99  }
100  }
101 
102  public void Clear()
103  {
104  if (properties != null) properties.Clear();
105  }
106 
107  /// <summary>
108  /// Gets the number of properties stored in this container.
109  /// </summary>
110  /// <value>The count of properties.</value>
111  public int Count
112  {
113  get
114  {
115  return properties != null ? properties.Count : 0;
116  }
117  }
118 
119  public bool IsReadOnly
120  {
121  get
122  {
123  return false;
124  }
125  }
126 
127  /// <summary>
128  /// Adds the specified key-value pair.
129  /// </summary>
130  /// <typeparam name="T"></typeparam>
131  /// <param name="key">The key.</param>
132  /// <param name="value">The value.</param>
133  public void Add<T>(PropertyKey<T> key, T value)
134  {
135  SetObject(key, value, true);
136  }
137 
138  /// <summary>
139  /// Determines whether the specified instance contains this key.
140  /// </summary>
141  /// <param name="key">The key.</param>
142  /// <returns>
143  /// <c>true</c> if the specified instance contains this key; otherwise, <c>false</c>.
144  /// </returns>
145  public bool ContainsKey(PropertyKey key)
146  {
147  return properties != null && properties.ContainsKey(key);
148  }
149 
150  public void Add(PropertyKey key, object value)
151  {
152  SetObject(key, value, true);
153  }
154 
155  public bool Remove(PropertyKey propertyKey)
156  {
157  bool removed = false;
158 
159  if (PropertyUpdated != null || propertyKey.PropertyUpdateCallback != null)
160  {
161  object previousValue = Get(propertyKey);
162 
163  if (properties != null)
164  removed = properties.Remove(propertyKey);
165  var tagValue = Get(propertyKey);
166 
167  if (!ArePropertyValuesEqual(propertyKey, tagValue, previousValue))
168  {
169  if (propertyKey.PropertyUpdateCallback != null)
170  propertyKey.PropertyUpdateCallback(ref this, propertyKey, tagValue, previousValue);
171  if (PropertyUpdated != null)
172  PropertyUpdated(ref this, propertyKey, tagValue, previousValue);
173  }
174  }
175  else
176  {
177  if (properties != null)
178  removed = properties.Remove(propertyKey);
179  }
180 
181  return removed;
182  }
183 
184  public object this[PropertyKey key]
185  {
186  get
187  {
188  return Get(key);
189  }
190  set
191  {
192  SetObject(key, value);
193  }
194  }
195 
197  {
198  get
199  {
200  if (properties != null) return properties.Keys;
201  return emptyKeys;
202  }
203  }
204 
205  public ICollection<object> Values
206  {
207  get
208  {
209  if (properties != null) return properties.Values;
210  return emptyValues;
211  }
212  }
213 
214  /// <summary>
215  /// Copies properties from this instance to a container.
216  /// </summary>
217  /// <param name="destination">The destination.</param>
218  public void CopyTo(ref PropertyContainer destination)
219  {
220  foreach (var keyValuePair in this)
221  destination.SetObject(keyValuePair.Key, keyValuePair.Value);
222  }
223 
224  /// <summary>
225  /// Gets the specified tag value.
226  /// </summary>
227  /// <param name="propertyKey">The tag property.</param>
228  /// <returns>Value of the tag property</returns>
229  public object Get(PropertyKey propertyKey)
230  {
231  return Get(propertyKey, false);
232  }
233 
234  private object Get(PropertyKey propertyKey, bool forceNotToKeep)
235  {
236  // First, check if there is an accessor
237  if (propertyKey.AccessorMetadata != null)
238  {
239  return propertyKey.AccessorMetadata.GetValue(ref this);
240  }
241 
242  object value;
243 
244  // Get bound value, if any.
245  if (properties != null && properties.TryGetValue(propertyKey, out value))
246  {
247  if (propertyKey.IsValueType)
248  value = ((ValueHolder)value).ObjectValue;
249  return value;
250  }
251 
252  if (propertyKey.DefaultValueMetadata != null)
253  {
254  // Get default value.
255  object defaultValue = propertyKey.DefaultValueMetadata.GetDefaultValue(ref this);
256 
257  // Check if value should be kept.
258  if (propertyKey.DefaultValueMetadata.KeepValue && !forceNotToKeep)
259  {
260  // Register it.
261  SetObject(propertyKey, defaultValue);
262  }
263  return defaultValue;
264  }
265 
266  return null;
267  }
268 
269  /// <summary>
270  /// Gets the specified tag value.
271  /// </summary>
272  /// <typeparam name="T">Type of the tag value</typeparam>
273  /// <param name="propertyKey">The tag property.</param>
274  /// <returns>Typed value of the tag property</returns>
275  public T Get<T>(PropertyKey<T> propertyKey)
276  {
277  if (propertyKey.IsValueType)
278  {
279  // Fast path for value type
280  // First, check if there is an accessor
281  if (propertyKey.AccessorMetadata != null)
282  {
283  // TODO: Not optimal, but not used so far
284  return (T)propertyKey.AccessorMetadata.GetValue(ref this);
285  }
286 
287  object value;
288 
289  // Get bound value, if any.
290  if (properties != null && properties.TryGetValue(propertyKey, out value))
291  return ((ValueHolder<T>)value).Value;
292 
293  if (propertyKey.DefaultValueMetadata != null)
294  {
295  // Get default value.
296  T defaultValue = ((DefaultValueMetadata<T>)propertyKey.DefaultValueMetadata).GetDefaultValueT(ref this);
297 
298  // Check if value should be kept.
299  if (propertyKey.DefaultValueMetadata.KeepValue)
300  {
301  // Register it.
302  Set(propertyKey, defaultValue);
303  }
304 
305  return defaultValue;
306  }
307 
308  return default(T);
309  }
310 
311  var result = Get((PropertyKey)propertyKey, false);
312  return result != null ? (T)result : default(T);
313  }
314 
315  /// <summary>
316  /// Tries to get a tag value.
317  /// </summary>
318  /// <typeparam name="T">Type of the tag value</typeparam>
319  /// <param name="propertyKey">The tag property.</param>
320  /// <param name="value">The value or default vaue if not found</param>
321  /// <returns>Returns <c>true</c> if the was found; <c>false</c> otherwise</returns>
322  public bool TryGetValue(PropertyKey propertyKey, out object value)
323  {
324  // Implem to avoid boxing/unboxing when using object as output value
325  if (ContainsKey(propertyKey))
326  {
327  var result = Get(propertyKey);
328  value = result;
329  return true;
330  }
331 
332  value = null;
333  return false;
334  }
335 
336  /// <summary>
337  /// Tries to get a tag value.
338  /// </summary>
339  /// <typeparam name="T">Type of the tag value</typeparam>
340  /// <param name="propertyKey">The tag property.</param>
341  /// <param name="value">The value or default vaue if not found</param>
342  /// <returns>Returns <c>true</c> if the was found; <c>false</c> otherwise</returns>
343  public bool TryGetValue<T>(PropertyKey<T> propertyKey, out T value)
344  {
345  if (ContainsKey(propertyKey))
346  {
347  value = Get<T>(propertyKey);
348  return true;
349  }
350  value = default(T);
351  return false;
352  }
353 
354  /// <summary>
355  /// Sets the specified tag value.
356  /// </summary>
357  /// <typeparam name="T">Type of the tag value</typeparam>
358  /// <param name="propertyKey">The tag property.</param>
359  /// <param name="tagValue">The tag value.</param>
360  public void Set<T>(PropertyKey<T> propertyKey, T tagValue)
361  {
362  if (propertyKey.IsValueType)
363  {
364  ValueHolder<T> valueHolder = null;
365  T oldValue;
366 
367  // Fast path for value types
368  // Fast path for value type
369  // First, check if there is an accessor
370  if (propertyKey.AccessorMetadata != null)
371  {
372  // TODO: Not optimal, but not used so far
373  oldValue = (T)propertyKey.AccessorMetadata.GetValue(ref this);
374  }
375  else
376  {
377  object value;
378 
379  // Get bound value, if any.
380  if (properties != null && properties.TryGetValue(propertyKey, out value))
381  {
382  valueHolder = (ValueHolder<T>)value;
383  oldValue = valueHolder.Value;
384  }
385  else if (propertyKey.DefaultValueMetadata != null)
386  {
387  // Get default value.
388  oldValue = propertyKey.DefaultValueMetadataT.GetDefaultValueT(ref this);
389  }
390  else
391  {
392  oldValue = default(T);
393  }
394  }
395 
396  // Allow to validate the metadata before storing it.
397  if (propertyKey.ValidateValueMetadata != null)
398  {
399  // TODO: Use typed validate?
400  propertyKey.ValidateValueMetadataT.ValidateValueCallback(ref tagValue);
401  }
402 
403  // First, check if there is an accessor
404  if (propertyKey.AccessorMetadata != null)
405  {
406  // TODO: Not optimal, but not used so far
407  propertyKey.AccessorMetadata.SetValue(ref this, tagValue);
408  return;
409  }
410 
411  if (properties == null)
412  properties = new Dictionary<PropertyKey, object>();
413 
414  if (valueHolder != null)
415  valueHolder.Value = tagValue;
416  else
417  valueHolder = new ValueHolder<T>(tagValue);
418 
419  if (PropertyUpdated != null || propertyKey.PropertyUpdateCallback != null)
420  {
421  object previousValue = GetNonRecursive(propertyKey);
422 
423  properties[propertyKey] = valueHolder;
424 
425  if (!ArePropertyValuesEqual(propertyKey, tagValue, previousValue))
426  {
427  if (PropertyUpdated != null)
428  PropertyUpdated(ref this, propertyKey, tagValue, previousValue);
429  if (propertyKey.PropertyUpdateCallback != null)
430  propertyKey.PropertyUpdateCallback(ref this, propertyKey, tagValue, previousValue);
431  }
432  }
433  else
434  {
435  properties[propertyKey] = valueHolder;
436  }
437 
438  if (propertyKey.ObjectInvalidationMetadata != null)
439  {
440  propertyKey.ObjectInvalidationMetadataT.Invalidate(Owner, propertyKey, ref oldValue);
441  }
442 
443  return;
444  }
445 
446  SetObject((PropertyKey)propertyKey, tagValue, false);
447  }
448 
449  /// <summary>
450  /// Sets the specified tag value.
451  /// </summary>
452  /// <param name="propertyKey">The tag property.</param>
453  /// <param name="tagValue">The tag value.</param>
454  public void SetObject(PropertyKey propertyKey, object tagValue)
455  {
456  SetObject(propertyKey, tagValue, false);
457  }
458 
459  private void SetObject(PropertyKey propertyKey, object tagValue, bool tryToAdd = false)
460  {
461  var oldValue = Get(propertyKey, true);
462 
463  // Allow to validate the metadata before storing it.
464  if (propertyKey.ValidateValueMetadata != null)
465  {
466  propertyKey.ValidateValueMetadata.Validate(ref tagValue);
467  }
468 
469  // First, check if there is an accessor
470  if (propertyKey.AccessorMetadata != null)
471  {
472  propertyKey.AccessorMetadata.SetValue(ref this, tagValue);
473  return;
474  }
475 
476  if (properties == null)
477  properties = new Dictionary<PropertyKey, object>();
478 
479  var valueToSet = propertyKey.IsValueType ? propertyKey.CreateValueHolder(tagValue) : tagValue;
480 
481  if (PropertyUpdated != null || propertyKey.PropertyUpdateCallback != null)
482  {
483  object previousValue = GetNonRecursive(propertyKey);
484 
485  if (tryToAdd)
486  properties.Add(propertyKey, valueToSet);
487  else
488  properties[propertyKey] = valueToSet;
489 
490  if (!ArePropertyValuesEqual(propertyKey, tagValue, previousValue))
491  {
492  if (PropertyUpdated != null)
493  PropertyUpdated(ref this, propertyKey, tagValue, previousValue);
494  if (propertyKey.PropertyUpdateCallback != null)
495  propertyKey.PropertyUpdateCallback(ref this, propertyKey, tagValue, previousValue);
496  }
497  }
498  else
499  {
500  if (tryToAdd)
501  properties.Add(propertyKey, valueToSet);
502  else
503  properties[propertyKey] = valueToSet;
504  }
505 
506  if (propertyKey.ObjectInvalidationMetadata != null)
507  {
508  propertyKey.ObjectInvalidationMetadata.Invalidate(Owner, propertyKey, oldValue);
509  }
510  }
511 
512  public static void AddAccessorProperty(Type type, PropertyKey propertyKey)
513  {
514  if (!type.GetTypeInfo().IsClass)
515  throw new ArgumentException("Class type is expected.", "type");
516 
517  if (propertyKey.AccessorMetadata == null)
518  throw new ArgumentException("Given PropertyKey doesn't have accessor metadata.", "propertyKey");
519 
520  List<PropertyKey> typeAccessorProperties;
521  if (!accessorProperties.TryGetValue(type, out typeAccessorProperties))
522  accessorProperties.Add(type, typeAccessorProperties = new List<PropertyKey>());
523 
524  typeAccessorProperties.Add(propertyKey);
525  }
526 
527  internal void RaisePropertyContainerUpdated(PropertyKey propertyKey, object newValue, object oldValue)
528  {
529  if (PropertyUpdated != null)
530  PropertyUpdated(ref this, propertyKey, newValue, oldValue);
531  }
532 
533  private object GetNonRecursive(PropertyKey propertyKey)
534  {
535  object value;
536 
537  // Get bound value, if any.
538  if (properties != null && properties.TryGetValue(propertyKey, out value))
539  {
540  if (propertyKey.IsValueType)
541  return ((ValueHolder)value).ObjectValue;
542  return value;
543  }
544 
545  if (propertyKey.DefaultValueMetadata != null)
546  {
547  // Get default value.
548  return propertyKey.DefaultValueMetadata.GetDefaultValue(ref this);
549  }
550 
551  return null;
552  }
553 
554  private static bool ArePropertyValuesEqual(PropertyKey propertyKey, object propertyValue1, object propertyValue2)
555  {
556  var propertyType = propertyKey.PropertyType;
557 
558  if (!propertyType.GetTypeInfo().IsValueType && propertyType != typeof(string))
559  {
560  return object.ReferenceEquals(propertyValue1, propertyValue2);
561  }
562 
563  return object.Equals(propertyValue1, propertyValue2);
564  }
565 
566  IEnumerator IEnumerable.GetEnumerator()
567  {
568  return GetEnumerator();
569  }
570 
571  /*void ICollection<KeyValuePair<PropertyKey, object>>.Add(KeyValuePair<PropertyKey, object> item)
572  {
573  Add(item.Key, item.Value);
574  }
575 
576  bool ICollection<KeyValuePair<PropertyKey, object>>.Contains(KeyValuePair<PropertyKey, object> item)
577  {
578  object value;
579  if (TryGetValue(item.Key, out value))
580  {
581  return Equals(value, item.Value);
582  }
583  return false;
584  }
585 
586  void ICollection<KeyValuePair<PropertyKey, object>>.CopyTo(KeyValuePair<PropertyKey, object>[] array, int arrayIndex)
587  {
588  if (properties != null)
589  {
590  ((IDictionary<PropertyKey, object>)properties).CopyTo(array, arrayIndex);
591  }
592  }
593 
594  bool ICollection<KeyValuePair<PropertyKey, object>>.Remove(KeyValuePair<PropertyKey, object> item)
595  {
596  object value;
597  if (TryGetValue(item.Key, out value))
598  {
599  if (Equals(value, item.Value))
600  {
601  Remove(item.Key);
602  }
603  }
604  return false;
605  }
606  */
607 
608  internal abstract class ValueHolder
609  {
610  public abstract object ObjectValue { get; }
611  }
612 
613  internal class ValueHolder<T> : ValueHolder
614  {
615  public T Value;
616 
617  public ValueHolder(T value)
618  {
619  Value = value;
620  }
621 
622  public override object ObjectValue
623  {
624  get { return Value; }
625  }
626  }
627  }
628 }
AccessorMetadata AccessorMetadata
Gets the accessor metadata (may be null).
Definition: PropertyKey.cs:91
static void AddAccessorProperty(Type type, PropertyKey propertyKey)
Keys
Enumeration for keys.
Definition: Keys.cs:8
DefaultValueMetadata DefaultValueMetadata
Gets the default value metadata.
Definition: PropertyKey.cs:66
ValidateValueMetadata ValidateValueMetadata
Gets the validate value metadata (may be null).
Definition: PropertyKey.cs:79
Represents a container that can hold properties, lightweight to embed (lazy initialized).
SiliconStudio.Paradox.Input.Keys Keys
void CopyTo(ref PropertyContainer destination)
Copies properties from this instance to a container.
object GetValue(ref PropertyContainer obj)
bool ContainsKey(PropertyKey key)
Determines whether the specified instance contains this key.
IEnumerator< KeyValuePair< PropertyKey, object > > GetEnumerator()
Gets the key-properties value pairs in this instance.
void SetObject(PropertyKey propertyKey, object tagValue)
Sets the specified tag value.
ObjectInvalidationMetadata ObjectInvalidationMetadata
Gets the object invalidation metadata (may be null).
Definition: PropertyKey.cs:85
virtual bool KeepValue
Gets a value indicating whether this value is kept.
A class that represents a typed tag propety.
Definition: PropertyKey.cs:138
void Add(PropertyKey key, object value)
bool TryGetValue(PropertyKey propertyKey, out object value)
Tries to get a tag value.
bool Remove(PropertyKey propertyKey)
A class that represents a tag propety.
Definition: PropertyKey.cs:17
object Get(PropertyKey propertyKey)
Gets the specified tag value.
PropertyUpdatedDelegate PropertyUpdated
Occurs when a property is modified.