198 lines
7.2 KiB
C#
198 lines
7.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using SimConnect.Attribute;
|
|
|
|
namespace SimConnect.Concrete
|
|
{
|
|
/// <summary>
|
|
/// Defines a SimConnect variable.
|
|
/// </summary>
|
|
public struct SimConnectVariable
|
|
{
|
|
/// <summary>
|
|
/// The name of the SimConnect variable
|
|
/// </summary>
|
|
public string VariableName;
|
|
|
|
/// <summary>
|
|
/// The SimConnect data type. If null, the SimConnect data type will be determined from the .NET type.
|
|
/// </summary>
|
|
public SimConnectDataType DataType;
|
|
|
|
/// <summary>
|
|
/// The requested units for the value. See the SimConnect documentation for available units
|
|
/// </summary>
|
|
public string UnitsName;
|
|
|
|
/// <summary>
|
|
/// For integer and floating point types, determines how much the value must change before an update is sent
|
|
/// </summary>
|
|
public float Epsilon;
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a SimConnect definition from properties annotated with the SimConnectVariable attribute.
|
|
/// </summary>
|
|
public class SimConnectDefinition
|
|
{
|
|
private readonly Type type;
|
|
|
|
private delegate void SetterProc(object instance, BinaryReader data);
|
|
|
|
private struct SimConnectVariableProperty
|
|
{
|
|
public SimConnectVariable Variable;
|
|
public SetterProc Setter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides access to the parsed variables.
|
|
/// </summary>
|
|
public IEnumerable<SimConnectVariable> Variables => variables.Select(v => v.Variable);
|
|
|
|
private readonly List<SimConnectVariableProperty> variables;
|
|
|
|
|
|
/// <summary>
|
|
/// Creates an instance of a SimConnectDefinition based on the provided class.
|
|
/// </summary>
|
|
/// <param name="type">The class type used for the definition</param>
|
|
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");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Parses the SimConnect data stream to an object according to this definition.
|
|
/// </summary>
|
|
/// <param name="data">The SimConnect data stream</param>
|
|
/// <returns>An instance of the type for this definition</returns>
|
|
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<SimConnectVariableProperty> ParseVariables(IReflect type)
|
|
{
|
|
var result = new List<SimConnectVariableProperty>();
|
|
|
|
foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
|
.Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property))
|
|
{
|
|
var variableAttribute = member.GetCustomAttribute<SimConnectVariableAttribute>();
|
|
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<BinaryReader, object> Converter;
|
|
|
|
public SimConnectTypeMapping(Type type, SimConnectDataType simConnectDataType, bool isDefaultForType, Func<BinaryReader, object> converter)
|
|
{
|
|
Type = type;
|
|
SimConnectDataType = simConnectDataType;
|
|
IsDefaultForTypeForType = isDefaultForType;
|
|
Converter = converter;
|
|
}
|
|
}
|
|
|
|
|
|
private static readonly List<SimConnectTypeMapping> SimConnectTypeMappings = new List<SimConnectTypeMapping>
|
|
{
|
|
{ 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<object, object> 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}");
|
|
}
|
|
}
|
|
}
|