Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.2.5
- Reduced memory allocations by utilizing ListPool and DictionaryPool
- Flush method properly returns a cancellable Task
- CancellationToken handled all the way through to the WebRequest
- Improved error handling and logging

## 0.2.4

- Fixed memory leak with event errors
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ A few important notes:

## Preparing for Submission to Apple App Store

When submitting your app to the Apple App Store, you'll need to fill out the `App Privacy` form. You can find all the answers on our [How to fill out the Apple App Privacy when using Aptabase](https://aptabase.com/docs/apple-app-privacy) guide.
When submitting your app to the Apple App Store, you'll need to fill out the `App Privacy` form. You can find all the answers on our [How to fill out the Apple App Privacy when using Aptabase](https://aptabase.com/docs/apple-app-privacy) guide.
85 changes: 43 additions & 42 deletions Runtime/Aptabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Pool;
using Random = UnityEngine.Random;

namespace AptabaseSDK
Expand All @@ -13,70 +14,72 @@ public static class Aptabase
private static IDispatcher _dispatcher;
private static EnvironmentInfo _env;
private static Settings _settings;

private static DateTime _lastTouched = DateTime.UtcNow;
private static string _baseURL;

private static readonly TimeSpan _sessionTimeout = TimeSpan.FromMinutes(60);

private static readonly Dictionary<string, string> _hosts = new()
{
{ "US", "https://us.aptabase.com" },
{ "EU", "https://eu.aptabase.com" },
{ "DEV", "http://localhost:3000" },
{ "SH", "" },
{ "SH", "" }
};

private static int _flushTimer;
private static CancellationTokenSource _pollingCancellationTokenSource;

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
//load settings
// load settings
_settings = Resources.Load<Settings>("AptabaseSettings");
if (_settings == null)
{
Debug.LogWarning("Aptabase Settings not found. Tracking will be disabled");
Debug.LogError("[AptabaseAnalytics] Aptabase Settings not found. Tracking will be disabled");
return;
}

var key = _settings.AppKey;

var parts = key.Split("-");
if (parts.Length != 3 || !_hosts.ContainsKey(parts[1]))
{
Debug.LogWarning($"The Aptabase App Key {key} is invalid. Tracking will be disabled");
Debug.LogError($"[AptabaseAnalytics] The Aptabase App Key {key} is invalid. Tracking will be disabled");
return;
}

_env = Environment.GetEnvironmentInfo(Version.GetVersionInfo(_settings));

_baseURL = GetBaseUrl(parts[1]);

#if UNITY_WEBGL
_dispatcher = new WebGLDispatcher(_settings.AppKey, _baseURL, _env);
#else
_dispatcher = new Dispatcher(_settings.AppKey, _baseURL, _env);
#endif

//create listener
if (string.IsNullOrEmpty(_baseURL))
return;

#if UNITY_WEBGL
_dispatcher = new WebGLDispatcher(_settings.AppKey, _baseURL, _env);
#else
_dispatcher = new Dispatcher(_settings.AppKey, _baseURL, _env);
#endif

// create listener
var eventFocusHandler = new GameObject("AptabaseService");
eventFocusHandler.AddComponent<AptabaseService>();
}
private static async void StartPolling(int flushTimer)

private static async Task StartPolling(int flushTimer)
{
StopPolling();

_flushTimer = flushTimer;
_pollingCancellationTokenSource = new CancellationTokenSource();

while (_pollingCancellationTokenSource is { IsCancellationRequested: false })
{
try
{
await Task.Delay(_flushTimer, _pollingCancellationTokenSource.Token);
Flush();
await Flush();
}
catch (TaskCanceledException)
{
Expand All @@ -89,23 +92,16 @@ private static void StopPolling()
{
if (_flushTimer <= 0)
return;

_pollingCancellationTokenSource?.Cancel();
_pollingCancellationTokenSource?.Dispose();
_pollingCancellationTokenSource = null;
_flushTimer = 0;
}

public static void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
StartPolling(GetFlushInterval());
}
else
{
Flush();
StopPolling();
}
_ = hasFocus ? StartPolling(GetFlushInterval()) : Flush().ContinueWith(_ => StopPolling());
}

private static string EvalSessionId()
Expand All @@ -118,14 +114,15 @@ private static string EvalSessionId()
_lastTouched = now;
return _sessionId;
}

private static string GetBaseUrl(string region)
{
if (region == "SH")
{
if (string.IsNullOrEmpty(_settings.SelfHostURL))
{
Debug.LogWarning("Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled.");
Debug.LogWarning(
"[AptabaseAnalytics] Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled.");
return null;
}

Expand All @@ -135,26 +132,30 @@ private static string GetBaseUrl(string region)
return _hosts[region];
}

public static void Flush()
public static Task Flush(CancellationToken cancellationToken = default)
{
_dispatcher.Flush();
return _dispatcher.Flush(cancellationToken);
}

public static void TrackEvent(string eventName, Dictionary<string, object> props = null)
public static void TrackEvent(string eventName, Dictionary<string, object> eventProps = null)
{
if (string.IsNullOrEmpty(_baseURL))
return;

props ??= new Dictionary<string, object>();
var eventData = new Event()

var props = DictionaryPool<string, object>.Get();
if (eventProps != null)
foreach (var prop in eventProps)
props.Add(prop.Key, prop.Value);

var eventData = new Event
{
timestamp = DateTime.UtcNow.ToString("o"),
sessionId = EvalSessionId(),
eventName = eventName,
systemProps = _env,
props = props
};

_dispatcher.Enqueue(eventData);
}

Expand Down
95 changes: 50 additions & 45 deletions Runtime/Dispatcher/Dispatcher.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AptabaseSDK.TinyJson;
using UnityEngine;
using UnityEngine.Pool;

namespace AptabaseSDK
{
public class Dispatcher: IDispatcher
public class Dispatcher : IDispatcher
{
private const string EVENTS_ENDPOINT = "/api/v0/events";

private const int MAX_BATCH_SIZE = 25;

private static string _apiURL;
private static WebRequestHelper _webRequestHelper;
private static string _appKey;
private static EnvironmentInfo _environment;

private bool _flushInProgress;

private readonly Queue<Event> _events;

private readonly List<Event> _failedEvents;
private readonly WebRequestHelper _webRequestHelper;

private bool _flushInProgress;

public Dispatcher(string appKey, string baseURL, EnvironmentInfo env)
{
//create event queue
// create the event queue
_events = new Queue<Event>();

//web request setup information
_apiURL = $"{baseURL}{EVENTS_ENDPOINT}";
_appKey = appKey;
_environment = env;
_webRequestHelper = new WebRequestHelper();
_failedEvents = new List<Event>(10);

// web request setup information
_webRequestHelper = new WebRequestHelper($"{baseURL}{EVENTS_ENDPOINT}", appKey, env);
}

public void Enqueue(Event data)
{
_events.Enqueue(data);
}

private void Enqueue(List<Event> data)
{
foreach (var eventData in data)
_events.Enqueue(eventData);
}

public async void Flush()
public async Task Flush(CancellationToken cancellationToken)
{
if (_flushInProgress || _events.Count <= 0)
return;

_flushInProgress = true;
var failedEvents = new List<Event>();
//flush all events
_failedEvents.Clear();

// flush all events
do
{
var eventsCount = Mathf.Min(MAX_BATCH_SIZE, _events.Count);
var eventsToSend = new List<Event>();
for (var i = 0; i < eventsCount; i++)
eventsToSend.Add(_events.Dequeue());
var eventsToSend = ListPool<Event>.Get();

try
{
var result = await SendEvents(eventsToSend);
if (!result) failedEvents.AddRange(eventsToSend);
{
var eventsCount = Mathf.Min(MAX_BATCH_SIZE, _events.Count);
for (var i = 0; i < eventsCount; i++)
eventsToSend.Add(_events.Dequeue());

var result = await SendEvents(eventsToSend, cancellationToken);
if (!result)
_failedEvents.AddRange(eventsToSend);
}
catch
{
failedEvents.AddRange(eventsToSend);
_failedEvents.AddRange(eventsToSend);
}
finally
{
foreach (var evt in eventsToSend.Except(_failedEvents))
DictionaryPool<string, object>.Release(evt.props);

} while (_events.Count > 0);

if (failedEvents.Count > 0)
Enqueue(failedEvents);
ListPool<Event>.Release(eventsToSend);
}
} while (_events.Count > 0 && !cancellationToken.IsCancellationRequested);

if (_failedEvents.Count > 0)
Enqueue(_failedEvents);

_flushInProgress = false;
}

private static async Task<bool> SendEvents(List<Event> events)

private void Enqueue(List<Event> data)
{
foreach (var eventData in data)
_events.Enqueue(eventData);
}

private async Task<bool> SendEvents(List<Event> events, CancellationToken cancellationToken)
{
var webRequest = _webRequestHelper.CreateWebRequest(_apiURL, _appKey, _environment, events.ToJson());
var result = await _webRequestHelper.SendWebRequestAsync(webRequest);
return result;
return await _webRequestHelper.CreateAndSendWebRequestAsync(events.ToJson(), cancellationToken);
}
}
}
7 changes: 5 additions & 2 deletions Runtime/Dispatcher/IDispatcher.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Threading;
using System.Threading.Tasks;

namespace AptabaseSDK
{
public interface IDispatcher
{
public void Enqueue(Event data);
void Enqueue(Event data);

public void Flush();
Task Flush(CancellationToken cancellationToken);
}
}
Loading