Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/SimConnect.NET/SimVar/Internal/IFieldWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// <copyright file="IFieldWriter.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>

using System;

namespace SimConnect.NET.SimVar.Internal
{
/// <summary>
/// Writes a single annotated field from a struct into a contiguous unmanaged buffer.
/// </summary>
/// <typeparam name="T">Struct type containing SimVar-annotated fields.</typeparam>
internal interface IFieldWriter<T>
where T : struct
{
/// <summary>Gets or sets the byte offset of this field's payload within the packed buffer.</summary>
int OffsetBytes { get; set; }

/// <summary>Gets or sets the size in bytes of this field's payload in the packed buffer.</summary>
int Size { get; set; }

/// <summary>Gets or sets the effective SimConnect data type used for marshaling this field.</summary>
SimConnectDataType DataType { get; set; }

/// <summary>
/// Writes the field value from the given struct into the buffer at OffsetBytes.
/// </summary>
/// <param name="source">Struct source value.</param>
/// <param name="basePtr">Base pointer to the packed buffer.</param>
void WriteFrom(in T source, IntPtr basePtr);
}
}
125 changes: 125 additions & 0 deletions src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// <copyright file="SimVarFieldWriter.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>

using System;
using System.Runtime.InteropServices;

namespace SimConnect.NET.SimVar.Internal
{
internal sealed class SimVarFieldWriter<T, TSrc> : IFieldWriter<T>
where T : struct
{
public int OffsetBytes { get; set; }

public int Size { get; set; }

public SimConnectDataType DataType { get; set; }

// Holds a typed extractor matching the field type, e.g. Func<T, double>
public Delegate Extractor { get; set; } = default!;

public void WriteFrom(in T source, IntPtr basePtr)
{
var addr = IntPtr.Add(basePtr, this.OffsetBytes);
switch (this.DataType)
{
case SimConnectDataType.FloatDouble:
{
var getter = (Func<T, double>)this.Extractor;
double v = getter(source);
var bytes = BitConverter.GetBytes(v);
Marshal.Copy(bytes, 0, addr, 8);
break;
}

case SimConnectDataType.FloatSingle:
{
var getter = (Func<T, float>)this.Extractor;
float v = getter(source);
var bytes = BitConverter.GetBytes(v);
Marshal.Copy(bytes, 0, addr, 4);
break;
}

case SimConnectDataType.Integer64:
{
var getter = (Func<T, long>)this.Extractor;
long v = getter(source);
Marshal.WriteInt64(addr, v);
break;
}

case SimConnectDataType.Integer32:
{
var getter = (Func<T, int>)this.Extractor;
int v = getter(source);
Marshal.WriteInt32(addr, v);
break;
}

case SimConnectDataType.String8:
case SimConnectDataType.String32:
case SimConnectDataType.String64:
case SimConnectDataType.String128:
case SimConnectDataType.String256:
case SimConnectDataType.String260:
{
var getter = (Func<T, string>)this.Extractor;
string s = getter(source) ?? string.Empty;
var bytes = System.Text.Encoding.ASCII.GetBytes(s);

// zero-initialize then copy up to Size
Span<byte> tmp = stackalloc byte[this.Size];
var copyLen = Math.Min(bytes.Length, this.Size);
bytes.AsSpan(0, copyLen).CopyTo(tmp);
Marshal.Copy(tmp.ToArray(), 0, addr, this.Size);
break;
}

case SimConnectDataType.InitPosition:
{
var getter = (Func<T, SimConnectDataInitPosition>)this.Extractor;
var v = getter(source);
Marshal.StructureToPtr(v, addr, fDeleteOld: false);
break;
}

case SimConnectDataType.MarkerState:
{
var getter = (Func<T, SimConnectDataMarkerState>)this.Extractor;
var v = getter(source);
Marshal.StructureToPtr(v, addr, fDeleteOld: false);
break;
}

case SimConnectDataType.Waypoint:
{
var getter = (Func<T, SimConnectDataWaypoint>)this.Extractor;
var v = getter(source);
Marshal.StructureToPtr(v, addr, fDeleteOld: false);
break;
}

case SimConnectDataType.LatLonAlt:
{
var getter = (Func<T, SimConnectDataLatLonAlt>)this.Extractor;
var v = getter(source);
Marshal.StructureToPtr(v, addr, fDeleteOld: false);
break;
}

case SimConnectDataType.Xyz:
{
var getter = (Func<T, SimConnectDataXyz>)this.Extractor;
var v = getter(source);
Marshal.StructureToPtr(v, addr, fDeleteOld: false);
break;
}

default:
throw new NotSupportedException($"Unsupported SimConnectDataType {this.DataType}");
}
}
}
}
212 changes: 212 additions & 0 deletions src/SimConnect.NET/SimVar/Internal/SimVarFieldWriterFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// <copyright file="SimVarFieldWriterFactory.cs" company="BARS">
// Copyright (c) BARS. All rights reserved.
// </copyright>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;

namespace SimConnect.NET.SimVar.Internal
{
internal static class SimVarFieldWriterFactory
{
/// <summary>
/// Builds field writers for a struct T and optionally adds each field to a SimConnect definition.
/// Mirrors the reader factory logic to ensure identical packing order and sizes.
/// </summary>
public static (List<IFieldWriter<T>> Writers, int TotalSize) Build<T>(
Action<string /*name*/, string? /*unit*/, SimConnectDataType /*type*/>? addToDefinition = null)
where T : struct
{
var t = typeof(T);
var fields = GetOrderedSimVarFields(t);
if (fields.Count == 0)
{
throw new InvalidOperationException($"Type {t.FullName} has no fields with [SimVar].");
}

var writers = new List<IFieldWriter<T>>(fields.Count);
int offset = 0;

foreach (var (field, simVar) in fields)
{
if (field == null)
{
throw new InvalidOperationException("FieldInfo is null in SimVarFieldWriterFactory.Build.");
}

if (simVar == null)
{
throw new InvalidOperationException($"SimConnectAttribute is null for field '{field.Name}' in SimVarFieldWriterFactory.Build.");
}

// Determine effective data type matching the reader factory rules
SimConnectDataType effectiveDataType;
if (simVar.DataType.HasValue)
{
effectiveDataType = simVar.DataType.Value;
}
else
{
var ft = field.FieldType;
var nullableUnderlying = Nullable.GetUnderlyingType(ft);
if (nullableUnderlying != null)
{
ft = nullableUnderlying;
}

if (ft.IsEnum)
{
ft = Enum.GetUnderlyingType(ft);
}

effectiveDataType = ft switch
{
_ when ft == typeof(double) => SimConnectDataType.FloatDouble,
_ when ft == typeof(float) => SimConnectDataType.FloatSingle,
_ when ft == typeof(long) || ft == typeof(ulong) => SimConnectDataType.Integer64,
_ when ft == typeof(int) || ft == typeof(uint) ||
ft == typeof(short) || ft == typeof(ushort) ||
ft == typeof(byte) || ft == typeof(sbyte) ||
ft == typeof(bool) => SimConnectDataType.Integer32,
_ when ft == typeof(SimConnectDataInitPosition) => SimConnectDataType.InitPosition,
_ when ft == typeof(SimConnectDataMarkerState) => SimConnectDataType.MarkerState,
_ when ft == typeof(SimConnectDataWaypoint) => SimConnectDataType.Waypoint,
_ when ft == typeof(SimConnectDataLatLonAlt) => SimConnectDataType.LatLonAlt,
_ when ft == typeof(SimConnectDataXyz) => SimConnectDataType.Xyz,
_ when ft == typeof(string) => SimConnectDataType.String256,
_ => throw new NotSupportedException($"Cannot infer SimConnectDataType for field '{field.Name}' of type {ft.FullName}."),
};
}

addToDefinition?.Invoke(simVar.Name, simVar.Unit, effectiveDataType);

var (dataType, rawType, size) = Classify(field, effectiveDataType);

var writerType = typeof(SimVarFieldWriter<,>).MakeGenericType(t, field.FieldType);
var writer = Activator.CreateInstance(writerType)!;

dynamic d = writer;
d.OffsetBytes = offset;
d.DataType = dataType;
d.Size = size;
d.Extractor = BuildExtractor(t, field, rawType);

writers.Add((IFieldWriter<T>)writer);
offset += size;
}

return (writers, offset);
}

private static List<(FieldInfo Field, SimConnectAttribute? Attr)> GetOrderedSimVarFields(Type t)
{
var fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public)
.Select(f => (Field: f, Attr: f.GetCustomAttribute<SimConnectAttribute>()))
.Where(x => x.Attr != null)
.OrderBy(x => x!.Attr!.Order)
.ThenBy(x => x.Field.MetadataToken)
.ToList();

return fields;
}

private static (SimConnectDataType DataType, Type RawType, int SizeBytes) Classify(FieldInfo field, SimConnectDataType dt)
{
switch (dt)
{
case SimConnectDataType.FloatDouble:
return (SimConnectDataType.FloatDouble, typeof(double), 8);
case SimConnectDataType.FloatSingle:
return (SimConnectDataType.FloatSingle, typeof(float), 4);
case SimConnectDataType.Integer32:
return (SimConnectDataType.Integer32, typeof(int), 4);
case SimConnectDataType.Integer64:
return (SimConnectDataType.Integer64, typeof(long), 8);
case SimConnectDataType.String8:
return (SimConnectDataType.String8, typeof(string), 8);
case SimConnectDataType.String32:
return (SimConnectDataType.String32, typeof(string), 32);
case SimConnectDataType.String64:
return (SimConnectDataType.String64, typeof(string), 64);
case SimConnectDataType.String128:
return (SimConnectDataType.String128, typeof(string), 128);
case SimConnectDataType.String256:
return (SimConnectDataType.String256, typeof(string), 256);
case SimConnectDataType.String260:
return (SimConnectDataType.String260, typeof(string), 260);
case SimConnectDataType.InitPosition:
return (SimConnectDataType.InitPosition, typeof(SimConnectDataInitPosition), Marshal.SizeOf<SimConnectDataInitPosition>());
case SimConnectDataType.MarkerState:
return (SimConnectDataType.MarkerState, typeof(SimConnectDataMarkerState), Marshal.SizeOf<SimConnectDataMarkerState>());
case SimConnectDataType.Waypoint:
return (SimConnectDataType.Waypoint, typeof(SimConnectDataWaypoint), Marshal.SizeOf<SimConnectDataWaypoint>());
case SimConnectDataType.LatLonAlt:
return (SimConnectDataType.LatLonAlt, typeof(SimConnectDataLatLonAlt), Marshal.SizeOf<SimConnectDataLatLonAlt>());
case SimConnectDataType.Xyz:
return (SimConnectDataType.Xyz, typeof(SimConnectDataXyz), Marshal.SizeOf<SimConnectDataXyz>());
default:
throw new NotSupportedException($"{field.DeclaringType!.FullName}.{field.Name}: unsupported SimConnectDataType {dt}");
}
}

/// <summary>
/// Builds an extractor that returns the field value converted to the requested raw type.
/// </summary>
private static Delegate BuildExtractor(Type structType, FieldInfo fi, Type rawType)
{
// param: T s
var s = Expression.Parameter(structType, "s");

// access field: s.Field
var fieldExpr = Expression.Field(s, fi);

// If the field type equals rawType -> identity
if (fi.FieldType == rawType)
{
var lambdaTypeId = typeof(Func<,>).MakeGenericType(structType, rawType);
return Expression.Lambda(lambdaTypeId, fieldExpr, s).Compile();
}

// If field is Nullable<U>, unwrap .Value or default
Type destFieldType = fi.FieldType;
var nullableUnderlying = Nullable.GetUnderlyingType(destFieldType);
Expression valueExpr = fieldExpr;
if (nullableUnderlying != null)
{
// coalesce: field.HasValue ? field.Value : default(U)
var valueProp = Expression.Property(fieldExpr, "Value");
var defaultValue = Expression.Default(nullableUnderlying);
valueExpr = Expression.Condition(
Expression.Property(fieldExpr, "HasValue"),
valueProp,
defaultValue);
destFieldType = nullableUnderlying;
}

// Enums -> convert to underlying integral type first
if (destFieldType.IsEnum)
{
var underlying = Enum.GetUnderlyingType(destFieldType);
if (valueExpr.Type != underlying)
{
valueExpr = Expression.Convert(valueExpr, underlying);
}

destFieldType = underlying;
}

// Finally convert to rawType
if (valueExpr.Type != rawType)
{
valueExpr = Expression.Convert(valueExpr, rawType);
}

var lambdaType = typeof(Func<,>).MakeGenericType(structType, rawType);
return Expression.Lambda(lambdaType, valueExpr, s).Compile();
}
}
}
Loading