/// <summary> /// Creates the default range when no additional range data is available for the <paramref name="obj" />'s /// <paramref name="field" />. /// </summary> internal static RangeMetadata CreateDefaultRange(object obj, FieldInfo field) { // Check if the serializer knows a range RangeAttribute range; if (SerializationRegistry.Default.GetSerializer(obj).TryGetRange(obj, field, out range)) { return(RangeMetadata.Create(obj, field, range)); } // Otherwise, maybe we can deduce a range for the type return(RangeMetadata.Create(obj, field, CreateDefaultRange(field.FieldType))); }
/// <summary> /// Compacts the state vector. /// </summary> /// <param name="model">The model whose state vector is compacted.</param> /// <param name="mode">The serialization mode the state vector is compacted for.</param> internal void Compact(ModelBase model, SerializationMode mode) { if (mode == SerializationMode.Optimized) { // Check if we can use the range to compress the data even further foreach (var slot in _slots) { if (slot.ContainedInStruct) slot.Range = Range.GetMetadata(slot.FieldChain.Last()); else if (slot.Field != null) slot.Range = Range.GetMetadata(model, slot.Object, slot.Field); else slot.Range = RangeMetadata.Create(slot.Object, slot.DataType, Range.CreateDefaultRange(slot.DataType)); if (slot.Range != null) slot.CompressedDataType = slot.Range.GetRangeType(); } } // Organize all slots with the same element size into groups Groups = _slots .GroupBy(slot => slot.ElementSizeInBits) .OrderBy(group => group.Key) .Select(group => new CompactedStateGroup { Slots = group .OrderBy(slot => slot.ObjectIdentifier) .ThenBy(slot => slot.Field?.Name ?? "array") .ThenBy(slot => slot.Field?.DeclaringType?.FullName ?? "array") .ToArray() }) .ToArray(); // Compute the total state vector size and ensure alignment of the individual groups // Ensure that the state vector size is a multiple of 4 for correct alignment in state vector arrays for (var i = 0; i < Groups.Length; ++i) { Groups[i].OffsetInBytes = SizeInBytes; SizeInBytes += Groups[i].GroupSizeInBytes; var alignment = i + 1 >= Groups.Length ? 4 : Math.Min(4, Math.Max(1, Groups[i + 1].ElementSizeInBits / 8)); var remainder = SizeInBytes % alignment; Groups[i].PaddingBytes = remainder == 0 ? 0 : alignment - remainder; SizeInBytes += Groups[i].PaddingBytes; } // We never generate 0-sized state vectors to avoid special casing if (SizeInBytes <= 0) SizeInBytes = 4; }
/// <summary> /// Gets the range metadata for the <paramref name="obj" />'s <paramref name="field" />. Returns <c>null</c> when the range is /// unrestricted. /// </summary> /// <param name="model">The model that stores the <paramref name="obj" />'s range metadata.</param> /// <param name="obj">The object the range metadata should be returned for.</param> /// <param name="field">The field the range metadata should be returned for.</param> internal static RangeMetadata GetMetadata(ModelBase model, object obj, FieldInfo field) { // For backing fields of auto-implemented properties, check the property instead // TODO: Remove this workaround once C# supports [field:Attribute] on properties var range = field.GetCustomAttribute <RangeAttribute>() ?? field?.GetAutoProperty()?.GetCustomAttribute <RangeAttribute>(); if (range != null) { return(RangeMetadata.Create(obj, field, range)); } var metadata = model.RangeMetadata.FirstOrDefault(m => m.DescribesField(obj, field)); return(metadata ?? CreateDefaultRange(obj, field)); }
/// <summary> /// Restricts values that can be stored in the field referenced by the <paramref name="fieldExpression" /> to the range of /// <paramref name="lowerBound" /> and <paramref name="upperBound" />, both inclusive, using the /// <paramref name="overflowBehavior" /> to handle range overflows. /// </summary> /// <typeparam name="T">The type of the field that is restricted.</typeparam> /// <param name="fieldExpression">The expression referencing the field whose range should be restricted.</param> /// <param name="lowerBound">The inclusive lower bound.</param> /// <param name="upperBound">The inclusive upper bound.</param> /// <param name="overflowBehavior">The overflow behavior.</param> public static void Restrict <T>(Expression <Func <T> > fieldExpression, object lowerBound, object upperBound, OverflowBehavior overflowBehavior) where T : struct, IComparable { Requires.NotNull(fieldExpression, nameof(fieldExpression)); Requires.InRange(overflowBehavior, nameof(overflowBehavior)); Requires.That(typeof(T).IsNumericType(), nameof(fieldExpression), "Expected a field of numeric type."); Requires.OfType <MemberExpression>(fieldExpression.Body, nameof(fieldExpression), "Expected a non-nested reference to a field."); var range = new RangeAttribute(lowerBound, upperBound, overflowBehavior); var memberExpression = (MemberExpression)fieldExpression.Body; var propertyInfo = memberExpression.Member as PropertyInfo; var fieldInfo = propertyInfo?.GetBackingField() ?? memberExpression.Member as FieldInfo; var objectExpression = memberExpression.Expression as ConstantExpression; Requires.That(fieldInfo != null, nameof(fieldExpression), "Expected a non-nested reference to a field or an auto-property."); Requires.That(objectExpression != null, nameof(fieldExpression), "Expected a non-nested reference to non-static field of primitive type."); Requires.That(((IComparable)range.LowerBound).CompareTo(range.UpperBound) <= 0, nameof(lowerBound), $"lower bound '{range.LowerBound}' is not smaller than upper bound '{range.UpperBound}'."); List <RangeMetadata> fields; if (_ranges.TryGetValue(objectExpression.Value, out fields)) { var metadata = fields.FirstOrDefault(m => m.DescribesField(objectExpression.Value, fieldInfo)); if (metadata != null) { fields.Remove(metadata); } fields.Add(RangeMetadata.Create(objectExpression.Value, fieldInfo, range)); } else { _ranges.Add(objectExpression.Value, new List <RangeMetadata> { RangeMetadata.Create(objectExpression.Value, fieldInfo, range) }); } }
/// <summary> /// Gets the range metadata for the <paramref name="field" />. Returns <c>null</c> when the range is unrestricted. /// </summary> /// <param name="field">The field the range metadata should be returned for.</param> internal static RangeMetadata GetMetadata(FieldInfo field) { var range = field.GetCustomAttribute <RangeAttribute>(); return(range == null ? null : RangeMetadata.Create(null, field, range)); }