/// <summary> /// Initializes the <see cref="ObjectX"/> class. /// </summary> static ObjectX() { System.Text.RegularExpressions.Regex matchCandlelightClass = new System.Text.RegularExpressions.Regex(@"^Candlelight\b"); using (var types = new HashPool <System.Type> .Scope()) { GetAllTypes(types.HashSet); foreach (var type in types.HashSet) { if ( !type.IsSubclassOf(typeof(UnityEngine.ScriptableObject)) || !matchCandlelightClass.IsMatch(type.Namespace ?? "") ) { continue; } System.Reflection.ConstructorInfo constructor = type.GetConstructor( System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, null, System.Type.EmptyTypes, null ); if (constructor == null || constructor.IsPublic) { UnityEngine.Debug.LogError( string.Format("<b>{0}</b> has no protected, parameterless constructor.", type) ); } } } }
/// <summary> /// Gets an auto-incremented version of the specified base name that does not already exist in the specified /// collection. /// </summary> /// <returns>An auto-incremented version of the specified base name that does not already exist in the specified /// collection.</returns> /// <param name="baseName">Base name.</param> /// <param name="existingNames">Existing names.</param> public static string GetUniqueName(this string baseName, IList <string> existingNames) { using (var allNames = new HashPool <string> .Scope()) { if (existingNames != null) { for (int i = 0; i < existingNames.Count; ++i) { allNames.HashSet.Add(existingNames[i]); } } return(GetUniqueName(baseName, allNames.HashSet)); } }
/// <summary> /// Sets a backing field for a list of identifiable objects that should have unique keys. Use this method when /// you need to serialize something that may be deserialized as a dictionary. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field of identifiable objects.</param> /// <param name="value">Value.</param> /// <param name="mutateIdentifier">A method to mutate the identifier on an object if it is not unique.</param> /// <param name="ignoreCase"> /// If set to <see langword="true"/>, then all string-based keys will be made lowercase. /// </param> /// <param name="intKeyMode">Mode for handling duplicate <see cref="System.Int32"/> keys.</param> /// <typeparam name="T"> /// A type with a <see cref="System.Int32"/>, <see cref="System.String"/>, or <see cref="UnityEngine.Object"/> /// identifier. /// </typeparam> /// <typeparam name="TId"> /// The identifier type (either <see cref="UnityEngine.Object"/>-derived, <see cref="System.Int32"/>, or /// <see cref="System.String"/>). /// </typeparam> private static bool SetKeyedListBackingFieldFromArray <T, TId>( List <T> backingField, IList <T> value, System.Func <TId, T, T> mutateIdentifier, bool ignoreCase, IntKeyMode intKeyMode = IntKeyMode.Increment ) where T : IIdentifiable <TId> { if (value == null || value.Count == 0) { int oldCount = backingField.Count; backingField.Clear(); return(oldCount != 0); } using (ListPool <T> .Scope oldValue = new ListPool <T> .Scope()) { oldValue.List.AddRange(backingField); backingField.Clear(); bool isString = typeof(TId) == typeof(string); bool isInt = !isString && typeof(TId) == typeof(int); if (!isInt && !isString) // NOTE: WinRT cannot use Type.IsAssignableFrom() { int nullHash = 0; int nullIndex = backingField.FindIndex(item => item.Identifier.GetHashCode() == nullHash); foreach (T newValue in value) { if (newValue == null) { if (nullIndex < 0) { backingField.Add(newValue); nullIndex = backingField.Count - 1; } } else if ( !object.ReferenceEquals(newValue.Identifier, default(TId)) && backingField.FindIndex( item => item.Identifier.GetHashCode() == newValue.Identifier.GetHashCode() ) < 0 ) { backingField.Add(newValue); if (newValue.Identifier.GetHashCode() == nullHash) { nullIndex = backingField.Count - 1; } } else if (nullIndex < 0) { backingField.Add(mutateIdentifier(default(TId), newValue)); nullIndex = backingField.Count - 1; } } } else { using (var keyedValues = new DictPool <TId, T> .Scope()) { using (var stringKeys = new HashPool <string> .Scope()) { TId id; for (int i = 0; i < value.Count; ++i) { id = value[i] == null ? default(TId) : value[i].Identifier; if (isString) { string key = (string)(object)id ?? string.Empty; if (ignoreCase && key.ContainsUppercase()) { key = key.ToLower(); } key = key.GetUniqueName(stringKeys.HashSet); stringKeys.HashSet.Add(key); id = (TId)(object)key; } else if (isInt) { switch (intKeyMode) { case IntKeyMode.Increment: while (keyedValues.Dict.ContainsKey(id)) { id = (TId)(object)((int)(object)id + 1); } break; case IntKeyMode.SetToZero: if (keyedValues.Dict.ContainsKey(id)) { id = (TId)(object)0; if (keyedValues.Dict.ContainsKey(id)) { continue; } } break; } } keyedValues.Dict[id] = value[i]; } foreach (KeyValuePair <TId, T> kv in keyedValues.Dict) { backingField.Add(mutateIdentifier(kv.Key, kv.Value)); } } } } return(!oldValue.List.SequenceEqual(backingField)); } }
/// <summary> /// Sets a backing field for a set of objects. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field.</param> /// <param name="value">Value.</param> /// <param name="ignoreCase"> /// If set to <see langword="true"/>, then all string elements keys will be made lowercase. /// </param> /// <param name="intKeyMode">Mode for handling duplicate <see cref="System.Int32"/> keys.</param> /// <typeparam name="T"> /// The element type (either <see cref="UnityEngine.Object"/>-derived, <see cref="System.Int32"/>, or /// <see cref="System.String"/>). /// </typeparam> private static bool SetHashedListBackingFieldFromArray <T>( List <T> backingField, IList <T> value, bool ignoreCase, IntKeyMode intKeyMode = IntKeyMode.Increment ) { if (value == null || value.Count == 0) { int oldCount = backingField.Count; backingField.Clear(); return(oldCount != 0); } T[] oldValue = backingField.ToArray(); backingField.Clear(); bool isString = typeof(T) == typeof(string); bool isInt = !isString && typeof(T) == typeof(int); if (!isInt && !isString) // NOTE: WinRT cannot use Type.IsAssignableFrom() { int nullHash = 0; int nullIndex = backingField.FindIndex(item => item.GetHashCode() == nullHash); foreach (T newValue in value) { if ( !object.ReferenceEquals(newValue, default(T)) && backingField.FindIndex(item => item.GetHashCode() == newValue.GetHashCode()) < 0 ) { backingField.Add(newValue); if (newValue.GetHashCode() == nullHash) { nullIndex = backingField.Count - 1; } } else if (nullIndex < 0) { backingField.Add(default(T)); nullIndex = backingField.Count - 1; } } } else { using (var hashedValues = new HashPool <T> .Scope()) { using (var stringKeys = new HashPool <string> .Scope()) { for (int i = 0; i < value.Count; ++i) { T id = value[i]; if (isString) { string key = (string)(object)id ?? string.Empty; if (ignoreCase && key.ContainsUppercase()) { key = key.ToLower(); } key = key.GetUniqueName(stringKeys.HashSet); stringKeys.HashSet.Add(key); id = (T)(object)key; } else if (isInt) { switch (intKeyMode) { case IntKeyMode.Increment: while (hashedValues.HashSet.Contains(id)) { id = (T)(object)((int)(object)id + 1); } break; case IntKeyMode.SetToZero: if (hashedValues.HashSet.Contains(id)) { id = (T)(object)0; if (hashedValues.HashSet.Contains(id)) { continue; } } break; } } hashedValues.HashSet.Add(id); } foreach (T hashedValue in hashedValues.HashSet) { backingField.Add(hashedValue); } } } } return(!oldValue.SequenceEqual(backingField)); }