Skip to content

Commit f8279bd

Browse files
refactor(audience): enable nullable reference types on Slice 3 files
- AudienceConfig.cs: PublishableKey, DistributionPlatform, OnError, PersistentDataPath, HttpHandler → nullable. PackageVersion stays non-null (has default). Keep PublishableKey nullable at the property since Init throws the actual guard. - ConsentLevel.cs: directive only (no annotations needed). - Transport/EventQueue.cs: Enqueue / EnqueueChecked msg param → nullable (existing guard `if (_disposed || msg == null) return`); stillAllowed param on EnqueueChecked → Func<bool>?. - Transport/DiskStore.cs: TryReadMessage out param → [NotNullWhen(true)] out Dictionary<string, object>? msg so callers flow-narrow to non-null on the true branch. - ImmutableAudience.cs: static reference fields (_config, _store, _queue, _transport, _controlClient, _sendTimer, _userId, DefaultPersistentDataPathProvider) → nullable. Public API default params (traits, properties, userId for DeleteData) → nullable. Identify/Alias string-overload identityType / fromType / toType → nullable (body already validates and drops on null). NotifyErrorCallback onError param → nullable. SnapshotCallerDict and Enqueue internal params → nullable. One `!` used at ConsentStore.Save call because Init's guard is out of the compiler's flow-analysis reach. Compile-time annotations only; zero runtime behaviour change.
1 parent 185c566 commit f8279bd

5 files changed

Lines changed: 39 additions & 26 deletions

File tree

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
#nullable enable
2+
13
using System;
24

35
namespace Immutable.Audience
46
{
57
// Configuration passed to ImmutableAudience.Init.
68
public class AudienceConfig
79
{
8-
// Studio API key.
9-
public string PublishableKey { get; set; }
10+
// Studio API key. Required — Init throws if null.
11+
public string? PublishableKey { get; set; }
1012

1113
// Initial consent level.
1214
public ConsentLevel Consent { get; set; } = ConsentLevel.None;
1315

1416
// Distribution platform the game is running on.
15-
public string DistributionPlatform { get; set; }
17+
public string? DistributionPlatform { get; set; }
1618

1719
// Enable debug logging.
1820
public bool Debug { get; set; } = false;
@@ -24,10 +26,11 @@ public class AudienceConfig
2426
public int FlushSize { get; set; } = Constants.DefaultFlushSize;
2527

2628
// Optional error callback.
27-
public Action<AudienceError> OnError { get; set; }
29+
public Action<AudienceError>? OnError { get; set; }
2830

2931
// Directory the SDK uses for identity, consent, and queued events.
30-
public string PersistentDataPath { get; set; }
32+
// Unity hooks populate this from Application.persistentDataPath.
33+
public string? PersistentDataPath { get; set; }
3134

3235
// Library version sent on every message.
3336
public string PackageVersion { get; set; } = Constants.LibraryVersion;
@@ -36,6 +39,6 @@ public class AudienceConfig
3639
public int ShutdownFlushTimeoutMs { get; set; } = 2_000;
3740

3841
// Test seam for HttpTransport; not part of the public API.
39-
internal System.Net.Http.HttpMessageHandler HttpHandler { get; set; }
42+
internal System.Net.Http.HttpMessageHandler? HttpHandler { get; set; }
4043
}
4144
}

src/Packages/Audience/Runtime/ConsentLevel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
namespace Immutable.Audience
24
{
35
// How much data the Audience SDK is allowed to collect.

src/Packages/Audience/Runtime/ImmutableAudience.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.Collections.Generic;
35
using System.Net.Http;
@@ -11,22 +13,22 @@ public static class ImmutableAudience
1113
{
1214
// Reference fields are written inside _initLock; readers fence off the volatile _initialized load.
1315
// _consent and _userId are mutated outside the lock and need volatile themselves.
14-
private static AudienceConfig _config;
15-
private static DiskStore _store;
16-
private static EventQueue _queue;
17-
private static HttpTransport _transport;
18-
private static HttpClient _controlClient;
19-
private static Timer _sendTimer;
16+
private static AudienceConfig? _config;
17+
private static DiskStore? _store;
18+
private static EventQueue? _queue;
19+
private static HttpTransport? _transport;
20+
private static HttpClient? _controlClient;
21+
private static Timer? _sendTimer;
2022
private static volatile ConsentLevel _consent;
21-
private static volatile string _userId;
23+
private static volatile string? _userId;
2224
private static volatile bool _initialized;
2325
private static readonly object _initLock = new object();
2426

2527
// AudienceUnityHooks sets this at SubsystemRegistration so Unity studios
2628
// can omit PersistentDataPath from AudienceConfig and Init will fill it
2729
// from Application.persistentDataPath. Non-Unity callers must still set
2830
// PersistentDataPath on the config.
29-
internal static Func<string> DefaultPersistentDataPathProvider;
31+
internal static Func<string>? DefaultPersistentDataPathProvider;
3032

3133
// Starts the SDK. Call once at launch.
3234
public static void Init(AudienceConfig config)
@@ -119,7 +121,7 @@ public static void Track(IEvent evt)
119121
}
120122

121123
// Send a custom event.
122-
public static void Track(string eventName, Dictionary<string, object> properties = null)
124+
public static void Track(string eventName, Dictionary<string, object>? properties = null)
123125
{
124126
if (!CanTrack()) return;
125127
if (string.IsNullOrEmpty(eventName))
@@ -142,12 +144,12 @@ public static void Track(string eventName, Dictionary<string, object> properties
142144
// -----------------------------------------------------------------
143145

144146
// Attach a known user id to subsequent events.
145-
public static void Identify(string userId, IdentityType identityType, Dictionary<string, object> traits = null) =>
147+
public static void Identify(string userId, IdentityType identityType, Dictionary<string, object>? traits = null) =>
146148
Identify(userId, identityType.ToLowercaseString(), traits);
147149

148150
// Attach a known user id to subsequent events. String overload for
149151
// providers not in IdentityType.
150-
public static void Identify(string userId, string identityType, Dictionary<string, object> traits = null)
152+
public static void Identify(string userId, string? identityType, Dictionary<string, object>? traits = null)
151153
{
152154
if (!_initialized) return;
153155

@@ -185,7 +187,7 @@ public static void Alias(string fromId, IdentityType fromType, string toId, Iden
185187

186188
// Link two user ids for the same player. String overload for
187189
// providers not in IdentityType.
188-
public static void Alias(string fromId, string fromType, string toId, string toType)
190+
public static void Alias(string fromId, string? fromType, string toId, string? toType)
189191
{
190192
if (!_initialized) return;
191193

@@ -225,7 +227,7 @@ public static void Reset()
225227
}
226228

227229
// Ask the backend to erase this player's data.
228-
public static void DeleteData(string userId = null)
230+
public static void DeleteData(string? userId = null)
229231
{
230232
if (!_initialized) return;
231233

@@ -273,7 +275,7 @@ public static void DeleteData(string userId = null)
273275
});
274276
}
275277

276-
private static void NotifyErrorCallback(Action<AudienceError> onError, AudienceErrorCode code, string message)
278+
private static void NotifyErrorCallback(Action<AudienceError>? onError, AudienceErrorCode code, string message)
277279
{
278280
if (onError == null) return;
279281
try
@@ -312,7 +314,8 @@ public static void SetConsent(ConsentLevel level)
312314

313315
try
314316
{
315-
ConsentStore.Save(config.PersistentDataPath, level);
317+
// PersistentDataPath is validated non-null in Init; compiler can't propagate that.
318+
ConsentStore.Save(config.PersistentDataPath!, level);
316319
}
317320
catch (Exception ex)
318321
{
@@ -486,10 +489,10 @@ private static bool CanTrack()
486489
}
487490

488491
// Shallow-copy the caller's dict so a post-call mutation cannot race the drain-thread serialiser.
489-
private static Dictionary<string, object> SnapshotCallerDict(Dictionary<string, object> src) =>
492+
private static Dictionary<string, object>? SnapshotCallerDict(Dictionary<string, object>? src) =>
490493
src != null ? new Dictionary<string, object>(src) : null;
491494

492-
private static void Enqueue(Dictionary<string, object> msg)
495+
private static void Enqueue(Dictionary<string, object>? msg)
493496
{
494497
var queue = _queue;
495498
if (queue == null) return;

src/Packages/Audience/Runtime/Transport/DiskStore.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#nullable enable
2+
13
using System;
24
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
36
using System.IO;
47
using System.Linq;
58

@@ -140,7 +143,7 @@ private void ApplyAnonymousDowngradeToFile(string path)
140143
private static bool IsIdentityMessage(string type) =>
141144
type == MessageTypes.Identify || type == MessageTypes.Alias;
142145

143-
private static bool TryReadMessage(string path, out Dictionary<string, object> msg)
146+
private static bool TryReadMessage(string path, [NotNullWhen(true)] out Dictionary<string, object>? msg)
144147
{
145148
msg = null;
146149
string json;

src/Packages/Audience/Runtime/Transport/EventQueue.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.Collections.Concurrent;
35
using System.Collections.Generic;
@@ -51,7 +53,7 @@ internal EventQueue(DiskStore store, int flushIntervalSeconds, int flushSize)
5153
// The dictionary is not copied -- callers must not mutate it after
5254
// enqueue. Serialisation happens on the drain thread so Track() stays
5355
// allocation-light.
54-
internal void Enqueue(Dictionary<string, object> msg)
56+
internal void Enqueue(Dictionary<string, object>? msg)
5557
{
5658
if (_disposed || msg == null) return;
5759

@@ -65,7 +67,7 @@ internal void Enqueue(Dictionary<string, object> msg)
6567
// Enqueues under _drainLock, re-checking stillAllowed inside the lock.
6668
// Closes the window where a concurrent PurgeAll could complete between
6769
// the caller's check and the enqueue, leaking the event past revocation.
68-
internal void EnqueueChecked(Dictionary<string, object> msg, Func<bool> stillAllowed)
70+
internal void EnqueueChecked(Dictionary<string, object>? msg, Func<bool>? stillAllowed)
6971
{
7072
if (_disposed || msg == null) return;
7173

0 commit comments

Comments
 (0)