Skip to content

Commit 6e506e1

Browse files
chore: bump version to 0.1.16-beta and add changelog
- Add CHANGELOG entry for 0.1.16-beta and update Directory.Build.props version. - Introduce SimConnectLogger.IsLevelEnabled (and internal helper) and guard hot-path Debug calls to avoid string allocations and GC churn when debug logging is disabled. - Apply logging guards across SimObjectManager, SimConnectClient, and SimVarManager. - Stabilize tests: - Use "CoffeeCup" model in AIObjectTests to avoid missing-asset timeouts. - Ensure PerformanceTests' short-timeout token fires before issuing request (explicit delay/cancel). - Minor cleanup to pull request template formatting and checklist spacing.
1 parent 9fd2f53 commit 6e506e1

9 files changed

Lines changed: 111 additions & 30 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
## Summary
2-
<!-- Provide a brief description of what this PR does -->
32

3+
<!-- Provide a brief description of what this PR does -->
44

55
## Changes Made
6+
67
<!-- List the main changes in this pull request -->
7-
-
8-
-
9-
-
8+
9+
-
10+
-
11+
-
1012

1113
## Additional Information
12-
<!-- Any other relevant information, screenshots, or context -->
1314

15+
<!-- Any other relevant information, screenshots, or context -->
1416

1517
## Author Information
18+
1619
**Discord Username:** <!-- Your Discord username (if different from GitHub) -->
17-
**VATSIM CID:** <!-- Your VATSIM Controller ID -->
1820

1921
---
2022

2123
### Checklist:
2224

23-
* [ ] Have you followed the guidelines in our Contributing document?
24-
* [ ] Have you checked to ensure there aren't other open Pull Requests for the same update/change?
25+
- [ ] Have you followed the guidelines in our Contributing document?
26+
- [ ] Have you checked to ensure there aren't other open Pull Requests for the same update/change?

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.1.16-beta] - 2025-10-20
11+
12+
### Added
13+
14+
- Set multiple SimVars in one shot by passing a struct to `SimVars.SetAsync<T>()`, mirroring the existing struct-based reader pipeline.
15+
- Internal field-writer pipeline that packs annotated struct fields into the unmanaged buffer used for SimConnect writes.
16+
- Test coverage that exercises struct-based sets, validates altitude adjustments, and restores the original values afterward.
17+
18+
### Performance
19+
20+
- Wrapped verbose debug logging in hot paths with `SimConnectLogger.IsLevelEnabled` checks to avoid string allocations and GC churn when debug logging is disabled.
21+
22+
### Fixed
23+
24+
- Updated the AI object lifecycle test to spawn the `CoffeeCup` model so it no longer times out on missing assets.
25+
- Stabilized the performance timeout test by ensuring the cancellation token fires before issuing the request.
26+
27+
### Notes
28+
29+
- Thanks to @bstudtma for the contribution in PR #11.
30+
1031
## [0.1.15-beta] - 2025-09-15
1132

1233
### Added

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.1.15-beta</Version>
3+
<Version>0.1.16-beta</Version>
44
<Authors>BARS</Authors>
55
<Company>BARS</Company>
66
<Product>SimConnect.NET</Product>

src/SimConnect.NET/AI/SimObjectManager.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ public async Task<SimObject> CreateObjectAsync(
8383
(SimConnectError)result);
8484
}
8585

86-
SimConnectLogger.Debug($"SimObjectManager: Requested creation of '{containerTitle}' with requestId {requestId}");
86+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
87+
{
88+
SimConnectLogger.Debug($"SimObjectManager: Requested creation of '{containerTitle}' with requestId {requestId}");
89+
}
8790

8891
// Wait for the object creation to complete with shorter timeout
8992
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -123,7 +126,11 @@ public Task RemoveObjectAsync(SimObject simObject, CancellationToken cancellatio
123126

124127
if (!simObject.IsActive)
125128
{
126-
SimConnectLogger.Debug($"SimObjectManager: Object {simObject.ObjectId} is already inactive");
129+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
130+
{
131+
SimConnectLogger.Debug($"SimObjectManager: Object {simObject.ObjectId} is already inactive");
132+
}
133+
127134
return Task.CompletedTask;
128135
}
129136

@@ -332,7 +339,10 @@ public void Dispose()
332339

333340
this.managedObjects.Clear();
334341

335-
SimConnectLogger.Debug("SimObjectManager: Disposed");
342+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
343+
{
344+
SimConnectLogger.Debug("SimObjectManager: Disposed");
345+
}
336346
}
337347
finally
338348
{

src/SimConnect.NET/SimConnectClient.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,10 @@ public async Task<bool> ProcessNextMessageAsync(CancellationToken cancellationTo
362362
// Filter out the common "no messages available" error to reduce log spam
363363
if (result != -2147467259)
364364
{
365-
SimConnectLogger.Debug($"SimConnect_GetNextDispatch returned: {(SimConnectError)result}");
365+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
366+
{
367+
SimConnectLogger.Debug($"SimConnect_GetNextDispatch returned: {(SimConnectError)result}");
368+
}
366369
}
367370

368371
return false;
@@ -373,7 +376,10 @@ public async Task<bool> ProcessNextMessageAsync(CancellationToken cancellationTo
373376
var recv = Marshal.PtrToStructure<SimConnectRecv>(ppData);
374377
var recvId = (SimConnectRecvId)recv.Id;
375378

376-
SimConnectLogger.Debug($"Received SimConnect message: Id={recv.Id}, Size={recv.Size}");
379+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
380+
{
381+
SimConnectLogger.Debug($"Received SimConnect message: Id={recv.Id}, Size={recv.Size}");
382+
}
377383

378384
try
379385
{

src/SimConnect.NET/SimConnectLogger.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ public static void Configure(LogLevel minimumLevel = LogLevel.Debug, string? log
129129
}
130130
}
131131

132+
/// <summary>
133+
/// Determines whether logging is currently enabled for the specified level.
134+
/// Helps avoid expensive string formatting on disabled levels.
135+
/// </summary>
136+
/// <param name="level">The log level to check.</param>
137+
/// <returns><c>true</c> if the level is enabled; otherwise, <c>false</c>.</returns>
138+
public static bool IsLevelEnabled(LogLevel level) => Instance.IsLevelEnabledInternal(level);
139+
132140
/// <summary>
133141
/// Logs a debug message.
134142
/// </summary>
@@ -220,7 +228,7 @@ private void Enqueue(LogLevel level, string message)
220228
return;
221229
}
222230

223-
if (level < this.MinimumLevel)
231+
if (!this.IsLevelEnabledInternal(level))
224232
{
225233
return;
226234
}
@@ -235,6 +243,8 @@ private void Enqueue(LogLevel level, string message)
235243
}
236244
}
237245

246+
private bool IsLevelEnabledInternal(LogLevel level) => level >= this.MinimumLevel;
247+
238248
private void ProcessQueue()
239249
{
240250
try

src/SimConnect.NET/SimVar/SimVarManager.cs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,10 @@ public void Dispose()
355355
}
356356
catch (Exception ex)
357357
{
358-
SimConnectLogger.Debug($"Suppressing error disposing subscription {kvp.Key}: {ex.Message}");
358+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
359+
{
360+
SimConnectLogger.Debug($"Suppressing error disposing subscription {kvp.Key}: {ex.Message}");
361+
}
359362
}
360363
}
361364

@@ -451,12 +454,19 @@ internal void CancelRequest(ISimVarRequest request)
451454
{
452455
try
453456
{
454-
SimConnectLogger.Debug($"Canceling recurring request {request.RequestId} (Def={request.DefinitionId}, Obj={request.ObjectId})");
457+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
458+
{
459+
SimConnectLogger.Debug($"Canceling recurring request {request.RequestId} (Def={request.DefinitionId}, Obj={request.ObjectId})");
460+
}
461+
455462
this.RequestDataOnSimObject(request.RequestId, request.DefinitionId, request.ObjectId, SimConnectPeriod.Never);
456463
}
457464
catch (Exception ex)
458465
{
459-
SimConnectLogger.Debug($"Suppressing error during CancelRequest for {request.RequestId}: {ex.Message}");
466+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
467+
{
468+
SimConnectLogger.Debug($"Suppressing error during CancelRequest for {request.RequestId}: {ex.Message}");
469+
}
460470
}
461471
}
462472

@@ -593,15 +603,15 @@ private static string ParseString(IntPtr dataPtr, SimConnectDataType dataType)
593603
return SimVarMemoryReader.ReadFixedString(dataPtr, maxLength);
594604
}
595605

596-
/// <summary>
597-
/// Wrapper to call SimConnect_RequestDataOnSimObject with consistent error handling.
598-
/// A local context string is generated from the parameters for logging and exception messages.
599-
/// </summary>
606+
/// <summary>
607+
/// Wrapper to call SimConnect_RequestDataOnSimObject with consistent error handling.
608+
/// A local context string is generated from the parameters for logging and exception messages.
609+
/// </summary>
600610
/// <param name="requestId">The SimConnect request identifier.</param>
601611
/// <param name="definitionId">The data definition identifier.</param>
602612
/// <param name="objectId">The target object identifier.</param>
603-
/// <param name="period">The request period.</param>
604-
/// <remarks>Throws a SimConnectException on error (except when period == Never which is used internally for cancellation).</remarks>
613+
/// <param name="period">The request period.</param>
614+
/// <remarks>Throws a SimConnectException on error (except when period == Never which is used internally for cancellation).</remarks>
605615
private void RequestDataOnSimObject(
606616
uint requestId,
607617
uint definitionId,
@@ -737,7 +747,11 @@ private uint EnsureDataDefinition(SimVarDefinition definition, CancellationToken
737747

738748
if (this.dataDefinitions.TryGetValue(key, out var existingId))
739749
{
740-
SimConnectLogger.Debug($"Reusing existing definition ID {existingId} for {key.Name}|{key.Unit}");
750+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
751+
{
752+
SimConnectLogger.Debug($"Reusing existing definition ID {existingId} for {key.Name}|{key.Unit}");
753+
}
754+
741755
return existingId;
742756
}
743757

@@ -775,7 +789,11 @@ private uint EnsureScalarDefinition(string name, string? unit = null, SimConnect
775789

776790
if (this.dataDefinitions.TryGetValue(key, out var existingId))
777791
{
778-
SimConnectLogger.Debug($"Reusing existing definition ID {existingId} for {key.Name}|{key.Unit}");
792+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
793+
{
794+
SimConnectLogger.Debug($"Reusing existing definition ID {existingId} for {key.Name}|{key.Unit}");
795+
}
796+
779797
return existingId;
780798
}
781799

@@ -788,7 +806,10 @@ private uint EnsureScalarDefinition(string name, string? unit = null, SimConnect
788806
}
789807

790808
var definitionId = Interlocked.Increment(ref this.nextDefinitionId);
791-
SimConnectLogger.Debug($"Creating new definition ID {definitionId} for {name}|{unit}");
809+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
810+
{
811+
SimConnectLogger.Debug($"Creating new definition ID {definitionId} for {name}|{unit}");
812+
}
792813

793814
var result = SimConnectNative.SimConnect_AddToDataDefinition(
794815
this.simConnectHandle,

tests/SimConnect.NET.Tests.Net8/Tests/AIObjectTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace SimConnect.NET.Tests.Net8.Tests
1212
/// </summary>
1313
public class AIObjectTests : ISimConnectTest
1414
{
15+
private const string TestSimObjectModel = "CoffeeCup";
16+
1517
/// <inheritdoc/>
1618
public string Name => "AI Object Management";
1719

@@ -83,7 +85,7 @@ private static async Task<bool> TestSingleObjectLifecycle(SimConnectClient clien
8385

8486
Console.WriteLine($" 🎯 Creating AI object at {position.Latitude:F6}, {position.Longitude:F6}");
8587

86-
var aiObject = await client.AIObjects.CreateObjectAsync("BARS_Stopbar_On", position, "Test Object", cancellationToken);
88+
var aiObject = await client.AIObjects.CreateObjectAsync(TestSimObjectModel, position, "Test Object", cancellationToken);
8789
Console.WriteLine($" ✅ AI Object created with ID: {aiObject.ObjectId}");
8890

8991
if (!aiObject.IsActive)
@@ -130,7 +132,7 @@ private static async Task<bool> TestMultipleObjects(SimConnectClient client, Can
130132
Airspeed = 0,
131133
};
132134

133-
var obj = await client.AIObjects.CreateObjectAsync("BARS_Stopbar_On", position, $"Test Object {i}", cancellationToken);
135+
var obj = await client.AIObjects.CreateObjectAsync(TestSimObjectModel, position, $"Test Object {i}", cancellationToken);
134136
objects.Add(obj);
135137
Console.WriteLine($" ✅ Created object {i + 1} with ID: {obj.ObjectId}");
136138
}
@@ -190,7 +192,7 @@ private static async Task<bool> TestObjectTracking(SimConnectClient client, Canc
190192
Airspeed = 0,
191193
};
192194

193-
var aiObject = await client.AIObjects.CreateObjectAsync("BARS_Stopbar_On", position, "Tracking Test", cancellationToken);
195+
var aiObject = await client.AIObjects.CreateObjectAsync(TestSimObjectModel, position, "Tracking Test", cancellationToken);
194196

195197
try
196198
{

tests/SimConnect.NET.Tests.Net8/Tests/PerformanceTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ private static async Task<bool> TestTimeoutHandling(SimConnectClient client, Can
172172
using var shortTimeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));
173173
using var combined = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, shortTimeout.Token);
174174

175+
// Allow the timeout token to fire before issuing the request so cancellation is guaranteed.
176+
await Task.Delay(TimeSpan.FromMilliseconds(15), cancellationToken).ConfigureAwait(false);
177+
178+
if (!shortTimeout.IsCancellationRequested)
179+
{
180+
// In the unlikely event the timer hasn't fired yet (timer resolution quirks), cancel explicitly.
181+
shortTimeout.Cancel();
182+
}
183+
175184
// This should timeout quickly
176185
await client.SimVars.GetAsync<double>("PLANE LATITUDE", "degrees", cancellationToken: combined.Token);
177186

0 commit comments

Comments
 (0)