using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using SimConnect.Attribute; namespace SimConnect.Concrete { /// /// Defines a SimConnect variable. /// public struct SimConnectVariable { /// /// The name of the SimConnect variable /// public string VariableName; /// /// The SimConnect data type. If null, the SimConnect data type will be determined from the .NET type. /// public SimConnectDataType DataType; /// /// The requested units for the value. See the SimConnect documentation for available units /// public string UnitsName; /// /// For integer and floating point types, determines how much the value must change before an update is sent /// public float Epsilon; }; /// /// Creates a SimConnect definition from properties annotated with the SimConnectVariable attribute. /// public class SimConnectDefinition { private readonly Type type; private delegate void SetterProc(object instance, BinaryReader data); private struct SimConnectVariableProperty { public SimConnectVariable Variable; public SetterProc Setter; } /// /// Provides access to the parsed variables. /// public IEnumerable Variables => variables.Select(v => v.Variable); private readonly List variables; /// /// Creates an instance of a SimConnectDefinition based on the provided class. /// /// The class type used for the definition public SimConnectDefinition(Type type) { if (!type.IsClass) throw new InvalidOperationException($"{type.FullName} is not a class type"); this.type = type; variables = ParseVariables(type); if (variables.Count == 0) throw new InvalidOperationException($"At least one property of {type.FullName} should be annotated with the SimConnectVariable attribute"); } /// /// Parses the SimConnect data stream to an object according to this definition. /// /// The SimConnect data stream /// An instance of the type for this definition public object ParseData(Stream data) { var reader = new BinaryReader(data); var instance = Activator.CreateInstance(type); foreach (var variable in variables) variable.Setter(instance, reader); return instance; } private static List ParseVariables(IReflect type) { var result = new List(); foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property)) { var variableAttribute = member.GetCustomAttribute(); if (variableAttribute == null) continue; var dataType = variableAttribute.DataType; var setter = GetSetter(member, ref dataType); if (!dataType.HasValue) throw new InvalidOperationException($"DataType could not be determined for member {member.Name}"); result.Add(new SimConnectVariableProperty { Variable = new SimConnectVariable { VariableName = variableAttribute.VariableName, DataType = dataType.Value, UnitsName = variableAttribute.UnitsName, Epsilon = variableAttribute.Epsilon }, Setter = setter }); } return result; } private readonly struct SimConnectTypeMapping { public readonly Type Type; public readonly SimConnectDataType SimConnectDataType; public readonly bool IsDefaultForTypeForType; public readonly Func Converter; public SimConnectTypeMapping(Type type, SimConnectDataType simConnectDataType, bool isDefaultForType, Func converter) { Type = type; SimConnectDataType = simConnectDataType; IsDefaultForTypeForType = isDefaultForType; Converter = converter; } } private static readonly List SimConnectTypeMappings = new List { { new SimConnectTypeMapping(typeof(double), SimConnectDataType.Float64, true, reader => reader.ReadDouble()) }, { new SimConnectTypeMapping(typeof(float), SimConnectDataType.Float32, true, reader => reader.ReadSingle()) }, { new SimConnectTypeMapping(typeof(int), SimConnectDataType.Int32, true, reader => reader.ReadInt32()) }, { new SimConnectTypeMapping(typeof(uint), SimConnectDataType.Int32, true, reader => reader.ReadUInt32()) }, { new SimConnectTypeMapping(typeof(long), SimConnectDataType.Int64, true, reader => reader.ReadInt64()) }, { new SimConnectTypeMapping(typeof(ulong), SimConnectDataType.Int64, true, reader => reader.ReadUInt64()) } }; private static SetterProc GetSetter(MemberInfo member, ref SimConnectDataType? dataType) { Type valueType; Action valueSetter; if (member.MemberType == MemberTypes.Field) { var fieldInfo = (FieldInfo)member; valueType = fieldInfo.FieldType; valueSetter = (instance, data) => fieldInfo.SetValue(instance, data); } else { var propertyInfo = (PropertyInfo)member; valueType = propertyInfo.PropertyType; valueSetter = (instance, data) => propertyInfo.SetValue(instance, data); } foreach (var mapping in SimConnectTypeMappings.Where(mapping => mapping.Type == valueType)) { if (dataType.HasValue && mapping.SimConnectDataType != dataType) continue; if (!dataType.HasValue && !mapping.IsDefaultForTypeForType) continue; if (!dataType.HasValue) dataType = mapping.SimConnectDataType; return (instance, reader) => { valueSetter(instance, mapping.Converter(reader)); }; } throw new InvalidOperationException($"No mapping exists for type {valueType.FullName} with SimConnect DataType {dataType} for member {member.Name}"); } } }