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
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public static List<IFieldReader<T>> Build<T>(
{
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<IFieldReader<T>>(fields.Count);
Expand Down
23 changes: 18 additions & 5 deletions src/SimConnect.NET/SimVar/Internal/SimVarFieldWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,26 @@ public void WriteFrom(in T source, IntPtr basePtr)
{
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
// 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<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);
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static (List<IFieldWriter<T>> Writers, int TotalSize) Build<T>(
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<IFieldWriter<T>>(fields.Count);
Expand Down
35 changes: 27 additions & 8 deletions src/SimConnect.NET/SimVar/SimVarManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed class SimVarManager : IDisposable
private readonly ConcurrentDictionary<(string Name, string Unit, SimConnectDataType DataType), uint> dataDefinitions = new();
private readonly ConcurrentDictionary<Type, uint> typeToDefIndex = new();
private readonly ConcurrentDictionary<uint, Action<IntPtr, ISimVarRequest>> defToParser = new();
private readonly ConcurrentDictionary<uint, (Delegate Write, int TotalSize)> defToWriter = new();
private readonly ConcurrentDictionary<uint, SimVarSubscription> subscriptions = new();
private readonly object typeDefinitionSync = new();

Expand Down Expand Up @@ -100,7 +101,7 @@ public async Task SetAsync<T>(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);
Expand Down Expand Up @@ -430,6 +431,18 @@ internal uint EnsureTypeDefinition<T>(CancellationToken cancellationToken)
}
};
this.typeToDefIndex[typeof(T)] = definitionId;

var writerBuild = SimVarFieldWriterFactory.Build<T>(addToDefinition: null);
Action<IntPtr, T> write = (basePtr, v) =>
{
foreach (var w in writerBuild.Writers)
{
w.WriteFrom(in v, basePtr);
}
};

this.defToWriter[definitionId] = (write, writerBuild.TotalSize);

return definitionId;
}
}
Expand Down Expand Up @@ -484,6 +497,7 @@ private static SimConnectDataType InferDataType<T>()
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}"),
Expand Down Expand Up @@ -938,28 +952,33 @@ private async Task SetStructAsync<T>(uint definitionId, T value, uint objectId,
{
cancellationToken.ThrowIfCancellationRequested();

// Build writers without re-adding to definition; EnsureTypeDefinition already registered it.
var (writers, totalSize) = SimVarFieldWriterFactory.Build<T>(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<IntPtr, T> 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)
Expand Down