diff --git a/src/SimConnect.NET/SimVar/Internal/SimVarFieldReaderFactory.cs b/src/SimConnect.NET/SimVar/Internal/SimVarFieldReaderFactory.cs index 748d583..1ba9891 100644 --- a/src/SimConnect.NET/SimVar/Internal/SimVarFieldReaderFactory.cs +++ b/src/SimConnect.NET/SimVar/Internal/SimVarFieldReaderFactory.cs @@ -23,12 +23,12 @@ public static List> Build( { var t = typeof(T); - // Collect and order fields with [SimVar] + // Collect and order fields with [SimConnect] var fields = GetOrderedSimVarFields(t); if (fields.Count == 0) { - throw new InvalidOperationException($"Type {t.FullName} has no fields with [SimVar]."); + throw new InvalidOperationException($"Type {t.FullName} has no fields with [SimConnect]."); } var readers = new List>(fields.Count); diff --git a/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs b/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs index edc9a23..941efec 100644 --- a/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs +++ b/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs @@ -67,13 +67,26 @@ public void WriteFrom(in T source, IntPtr basePtr) { var getter = (Func)this.Extractor; string s = getter(source) ?? string.Empty; - var bytes = System.Text.Encoding.ASCII.GetBytes(s); - // zero-initialize then copy up to Size + // zero-initialize then encode into span up to Size-1, ensure explicit null-termination + // SimConnect expects fixed-size, null-terminated ANSI strings. + // We reserve the last byte for '\0' when Size > 0 to avoid losing the terminator. Span 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); + if (this.Size > 0) + { + var dest = tmp[..(this.Size - 1)]; + _ = System.Text.Encoding.Latin1.GetBytes(s.AsSpan(), dest); + + // Explicitly set terminator even though tmp is zeroed by default + tmp[this.Size - 1] = 0; + } + + // Copy without allocating an intermediate array + for (int i = 0; i < this.Size; i++) + { + Marshal.WriteByte(addr, i, tmp[i]); + } + break; } diff --git a/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriterFactory.cs b/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriterFactory.cs index 90e74c0..169eb9e 100644 --- a/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriterFactory.cs +++ b/src/SimConnect.NET/SimVar/Internal/SimVarFieldWriterFactory.cs @@ -25,7 +25,7 @@ public static (List> Writers, int TotalSize) Build( var fields = GetOrderedSimVarFields(t); if (fields.Count == 0) { - throw new InvalidOperationException($"Type {t.FullName} has no fields with [SimVar]."); + throw new InvalidOperationException($"Type {t.FullName} has no fields with [SimConnect]."); } var writers = new List>(fields.Count); diff --git a/src/SimConnect.NET/SimVar/SimVarManager.cs b/src/SimConnect.NET/SimVar/SimVarManager.cs index c590489..db61491 100644 --- a/src/SimConnect.NET/SimVar/SimVarManager.cs +++ b/src/SimConnect.NET/SimVar/SimVarManager.cs @@ -25,6 +25,7 @@ public sealed class SimVarManager : IDisposable private readonly ConcurrentDictionary<(string Name, string Unit, SimConnectDataType DataType), uint> dataDefinitions = new(); private readonly ConcurrentDictionary typeToDefIndex = new(); private readonly ConcurrentDictionary> defToParser = new(); + private readonly ConcurrentDictionary defToWriter = new(); private readonly ConcurrentDictionary subscriptions = new(); private readonly object typeDefinitionSync = new(); @@ -100,7 +101,7 @@ public async Task SetAsync(string simVarName, string unit, T value, uint obje { ObjectDisposedException.ThrowIf(this.disposed, nameof(SimVarManager)); ArgumentException.ThrowIfNullOrEmpty(simVarName); - ArgumentException.ThrowIfNullOrEmpty(unit); + ArgumentNullException.ThrowIfNull(unit); // Try to get definition from registry first var definition = SimVarRegistry.Get(simVarName); @@ -430,6 +431,18 @@ internal uint EnsureTypeDefinition(CancellationToken cancellationToken) } }; this.typeToDefIndex[typeof(T)] = definitionId; + + var writerBuild = SimVarFieldWriterFactory.Build(addToDefinition: null); + Action write = (basePtr, v) => + { + foreach (var w in writerBuild.Writers) + { + w.WriteFrom(in v, basePtr); + } + }; + + this.defToWriter[definitionId] = (write, writerBuild.TotalSize); + return definitionId; } } @@ -484,6 +497,7 @@ private static SimConnectDataType InferDataType() Type t when t == typeof(float) => SimConnectDataType.FloatSingle, Type t when t == typeof(double) => SimConnectDataType.FloatDouble, Type t when t == typeof(string) => SimConnectDataType.String256, // Default string size + Type t when t == typeof(SimConnectDataInitPosition) => SimConnectDataType.InitPosition, Type t when t == typeof(SimConnectDataLatLonAlt) => SimConnectDataType.LatLonAlt, Type t when t == typeof(SimConnectDataXyz) => SimConnectDataType.Xyz, _ => throw new ArgumentException($"Unsupported type for SimVar: {type.Name}"), @@ -938,28 +952,33 @@ private async Task SetStructAsync(uint definitionId, T value, uint objectId, { cancellationToken.ThrowIfCancellationRequested(); - // Build writers without re-adding to definition; EnsureTypeDefinition already registered it. - var (writers, totalSize) = SimVarFieldWriterFactory.Build(addToDefinition: null); + // Use cached write delegate/layout keyed by definitionId (must exist if EnsureTypeDefinition was used) + if (!this.defToWriter.TryGetValue(definitionId, out var cache)) + { + throw new InvalidOperationException($"No struct writer found for DefinitionId={definitionId}. EnsureTypeDefinition must be called first."); + } await Task.Run( () => { - var dataPtr = Marshal.AllocHGlobal(totalSize); + var dataPtr = Marshal.AllocHGlobal(cache.TotalSize); try { - // Fill the buffer in the same order/sizes as the definition - foreach (var w in writers) + // Fill the buffer using the cached writer delegate for this definition + if (cache.Write is not Action write) { - w.WriteFrom(in value, dataPtr); + throw new InvalidOperationException($"Cached writer has unexpected type for DefinitionId={definitionId} and T={typeof(T).Name}."); } + write(dataPtr, value); + var hr = SimConnectNative.SimConnect_SetDataOnSimObject( this.simConnectHandle, definitionId, objectId, 0, 1, - (uint)totalSize, + (uint)cache.TotalSize, dataPtr); if (hr != (int)SimConnectError.None)