665 lines
26 KiB
C#
665 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Unity.Cloud.UserReporting.Client
|
|
{
|
|
/// <summary>
|
|
/// Represents a user reporting client.
|
|
/// </summary>
|
|
public class UserReportingClient
|
|
{
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="UserReportingClient"/> class.
|
|
/// </summary>
|
|
/// <param name="endpoint">The endpoint.</param>
|
|
/// <param name="projectIdentifier">The project identifier.</param>
|
|
/// <param name="platform">The platform.</param>
|
|
/// <param name="configuration">The configuration.</param>
|
|
public UserReportingClient(string endpoint, string projectIdentifier, IUserReportingPlatform platform, UserReportingClientConfiguration configuration)
|
|
{
|
|
// Arguments
|
|
this.Endpoint = endpoint;
|
|
this.ProjectIdentifier = projectIdentifier;
|
|
this.Platform = platform;
|
|
this.Configuration = configuration;
|
|
|
|
// Configuration Clean Up
|
|
this.Configuration.FramesPerMeasure = this.Configuration.FramesPerMeasure > 0 ? this.Configuration.FramesPerMeasure : 1;
|
|
this.Configuration.MaximumEventCount = this.Configuration.MaximumEventCount > 0 ? this.Configuration.MaximumEventCount : 1;
|
|
this.Configuration.MaximumMeasureCount = this.Configuration.MaximumMeasureCount > 0 ? this.Configuration.MaximumMeasureCount : 1;
|
|
this.Configuration.MaximumScreenshotCount = this.Configuration.MaximumScreenshotCount > 0 ? this.Configuration.MaximumScreenshotCount : 1;
|
|
|
|
// Lists
|
|
this.clientMetrics = new Dictionary<string, UserReportMetric>();
|
|
this.currentMeasureMetadata = new Dictionary<string, string>();
|
|
this.currentMetrics = new Dictionary<string, UserReportMetric>();
|
|
this.events = new CyclicalList<UserReportEvent>(configuration.MaximumEventCount);
|
|
this.measures = new CyclicalList<UserReportMeasure>(configuration.MaximumMeasureCount);
|
|
this.screenshots = new CyclicalList<UserReportScreenshot>(configuration.MaximumScreenshotCount);
|
|
|
|
// Device Metadata
|
|
this.deviceMetadata = new List<UserReportNamedValue>();
|
|
foreach (KeyValuePair<string, string> kvp in this.Platform.GetDeviceMetadata())
|
|
{
|
|
this.AddDeviceMetadata(kvp.Key, kvp.Value);
|
|
}
|
|
|
|
// Client Version
|
|
this.AddDeviceMetadata("UserReportingClientVersion", "2.0");
|
|
|
|
// Synchronized Action
|
|
this.synchronizedActions = new List<Action>();
|
|
this.currentSynchronizedActions = new List<Action>();
|
|
|
|
// Update Stopwatch
|
|
this.updateStopwatch = new Stopwatch();
|
|
|
|
// Is Connected to Logger
|
|
this.IsConnectedToLogger = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Fields
|
|
|
|
private Dictionary<string, UserReportMetric> clientMetrics;
|
|
|
|
private Dictionary<string, string> currentMeasureMetadata;
|
|
|
|
private Dictionary<string, UserReportMetric> currentMetrics;
|
|
|
|
private List<Action> currentSynchronizedActions;
|
|
|
|
private List<UserReportNamedValue> deviceMetadata;
|
|
|
|
private CyclicalList<UserReportEvent> events;
|
|
|
|
private int frameNumber;
|
|
|
|
private bool isMeasureBoundary;
|
|
|
|
private int measureFrames;
|
|
|
|
private CyclicalList<UserReportMeasure> measures;
|
|
|
|
private CyclicalList<UserReportScreenshot> screenshots;
|
|
|
|
private int screenshotsSaved;
|
|
|
|
private int screenshotsTaken;
|
|
|
|
private List<Action> synchronizedActions;
|
|
|
|
private Stopwatch updateStopwatch;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets the configuration.
|
|
/// </summary>
|
|
public UserReportingClientConfiguration Configuration { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the endpoint.
|
|
/// </summary>
|
|
public string Endpoint { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the client is connected to the logger. If true, log messages will be included in user reports.
|
|
/// </summary>
|
|
public bool IsConnectedToLogger { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the client is self reporting. If true, event and metrics about the client will be included in user reports.
|
|
/// </summary>
|
|
public bool IsSelfReporting { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the platform.
|
|
/// </summary>
|
|
public IUserReportingPlatform Platform { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the project identifier.
|
|
/// </summary>
|
|
public string ProjectIdentifier { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether user reporting events should be sent to analytics.
|
|
/// </summary>
|
|
public bool SendEventsToAnalytics { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Adds device metadata.
|
|
/// </summary>
|
|
/// <param name="name">The name.</param>
|
|
/// <param name="value">The value.</param>
|
|
public void AddDeviceMetadata(string name, string value)
|
|
{
|
|
lock (this.deviceMetadata)
|
|
{
|
|
UserReportNamedValue userReportNamedValue = new UserReportNamedValue();
|
|
userReportNamedValue.Name = name;
|
|
userReportNamedValue.Value = value;
|
|
this.deviceMetadata.Add(userReportNamedValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds measure metadata. Measure metadata is associated with a period of time.
|
|
/// </summary>
|
|
/// <param name="name">The name.</param>
|
|
/// <param name="value">The value.</param>
|
|
public void AddMeasureMetadata(string name, string value)
|
|
{
|
|
if (this.currentMeasureMetadata.ContainsKey(name))
|
|
{
|
|
this.currentMeasureMetadata[name] = value;
|
|
}
|
|
else
|
|
{
|
|
this.currentMeasureMetadata.Add(name, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a synchronized action.
|
|
/// </summary>
|
|
/// <param name="action">The action.</param>
|
|
private void AddSynchronizedAction(Action action)
|
|
{
|
|
if (action == null)
|
|
{
|
|
throw new ArgumentNullException("action");
|
|
}
|
|
lock (this.synchronizedActions)
|
|
{
|
|
this.synchronizedActions.Add(action);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the screenshots.
|
|
/// </summary>
|
|
public void ClearScreenshots()
|
|
{
|
|
lock (this.screenshots)
|
|
{
|
|
this.screenshots.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a user report.
|
|
/// </summary>
|
|
/// <param name="callback">The callback. Provides the user report that was created.</param>
|
|
public void CreateUserReport(Action<UserReport> callback)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Info, "Creating user report.");
|
|
this.WaitForPerforation(this.screenshotsTaken, () =>
|
|
{
|
|
this.Platform.RunTask(() =>
|
|
{
|
|
// Start Stopwatch
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
|
|
// Copy Data
|
|
UserReport userReport = new UserReport();
|
|
userReport.ProjectIdentifier = this.ProjectIdentifier;
|
|
|
|
// Device Metadata
|
|
lock (this.deviceMetadata)
|
|
{
|
|
userReport.DeviceMetadata = this.deviceMetadata.ToList();
|
|
}
|
|
|
|
// Events
|
|
lock (this.events)
|
|
{
|
|
userReport.Events = this.events.ToList();
|
|
}
|
|
|
|
// Measures
|
|
lock (this.measures)
|
|
{
|
|
userReport.Measures = this.measures.ToList();
|
|
}
|
|
|
|
// Screenshots
|
|
lock (this.screenshots)
|
|
{
|
|
userReport.Screenshots = this.screenshots.ToList();
|
|
}
|
|
|
|
// Complete
|
|
userReport.Complete();
|
|
|
|
// Modify
|
|
this.Platform.ModifyUserReport(userReport);
|
|
|
|
// Stop Stopwatch
|
|
stopwatch.Stop();
|
|
|
|
// Sample Client Metric
|
|
this.SampleClientMetric("UserReportingClient.CreateUserReport.Task", stopwatch.ElapsedMilliseconds);
|
|
|
|
// Copy Client Metrics
|
|
foreach (KeyValuePair<string, UserReportMetric> kvp in this.clientMetrics)
|
|
{
|
|
userReport.ClientMetrics.Add(kvp.Value);
|
|
}
|
|
|
|
// Return
|
|
return userReport;
|
|
}, (result) => { callback(result as UserReport); });
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the endpoint.
|
|
/// </summary>
|
|
/// <returns>The endpoint.</returns>
|
|
public string GetEndpoint()
|
|
{
|
|
if (this.Endpoint == null)
|
|
{
|
|
return "https://localhost";
|
|
}
|
|
return this.Endpoint.Trim();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs an event.
|
|
/// </summary>
|
|
/// <param name="level">The level.</param>
|
|
/// <param name="message">The message.</param>
|
|
public void LogEvent(UserReportEventLevel level, string message)
|
|
{
|
|
this.LogEvent(level, message, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs an event.
|
|
/// </summary>
|
|
/// <param name="level">The level.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="stackTrace">The stack trace.</param>
|
|
public void LogEvent(UserReportEventLevel level, string message, string stackTrace)
|
|
{
|
|
this.LogEvent(level, message, stackTrace, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs an event with a stack trace and exception.
|
|
/// </summary>
|
|
/// <param name="level">The level.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="stackTrace">The stack trace.</param>
|
|
/// <param name="exception">The exception.</param>
|
|
private void LogEvent(UserReportEventLevel level, string message, string stackTrace, Exception exception)
|
|
{
|
|
lock (this.events)
|
|
{
|
|
UserReportEvent userReportEvent = new UserReportEvent();
|
|
userReportEvent.Level = level;
|
|
userReportEvent.Message = message;
|
|
userReportEvent.FrameNumber = this.frameNumber;
|
|
userReportEvent.StackTrace = stackTrace;
|
|
userReportEvent.Timestamp = DateTime.UtcNow;
|
|
if (exception != null)
|
|
{
|
|
userReportEvent.Exception = new SerializableException(exception);
|
|
}
|
|
this.events.Add(userReportEvent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs an exception.
|
|
/// </summary>
|
|
/// <param name="exception">The exception.</param>
|
|
public void LogException(Exception exception)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Error, null, null, exception);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Samples a client metric. These metrics are only sample when self reporting is enabled.
|
|
/// </summary>
|
|
/// <param name="name">The name.</param>
|
|
/// <param name="value">The value.</param>
|
|
public void SampleClientMetric(string name, double value)
|
|
{
|
|
if (double.IsInfinity(value) || double.IsNaN(value))
|
|
{
|
|
return;
|
|
}
|
|
if (!this.clientMetrics.ContainsKey(name))
|
|
{
|
|
UserReportMetric newUserReportMetric = new UserReportMetric();
|
|
newUserReportMetric.Name = name;
|
|
this.clientMetrics.Add(name, newUserReportMetric);
|
|
}
|
|
UserReportMetric userReportMetric = this.clientMetrics[name];
|
|
userReportMetric.Sample(value);
|
|
this.clientMetrics[name] = userReportMetric;
|
|
|
|
// Self Reporting
|
|
if (this.IsSelfReporting)
|
|
{
|
|
this.SampleMetric(name, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Samples a metric. Metrics can be sampled frequently and have low overhead.
|
|
/// </summary>
|
|
/// <param name="name">The name.</param>
|
|
/// <param name="value">The value.</param>
|
|
public void SampleMetric(string name, double value)
|
|
{
|
|
if (this.Configuration.MetricsGatheringMode == MetricsGatheringMode.Disabled)
|
|
{
|
|
return;
|
|
}
|
|
if (double.IsInfinity(value) || double.IsNaN(value))
|
|
{
|
|
return;
|
|
}
|
|
if (!this.currentMetrics.ContainsKey(name))
|
|
{
|
|
UserReportMetric newUserReportMetric = new UserReportMetric();
|
|
newUserReportMetric.Name = name;
|
|
this.currentMetrics.Add(name, newUserReportMetric);
|
|
}
|
|
UserReportMetric userReportMetric = this.currentMetrics[name];
|
|
userReportMetric.Sample(value);
|
|
this.currentMetrics[name] = userReportMetric;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves a user report to disk.
|
|
/// </summary>
|
|
/// <param name="userReport">The user report.</param>
|
|
public void SaveUserReportToDisk(UserReport userReport)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Info, "Saving user report to disk.");
|
|
string json = this.Platform.SerializeJson(userReport);
|
|
File.WriteAllText("UserReport.json", json);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a user report to the server.
|
|
/// </summary>
|
|
/// <param name="userReport">The user report.</param>
|
|
/// <param name="callback">The callback. Provides a value indicating whether sending the user report was successful and provides the user report after it is modified by the server.</param>
|
|
public void SendUserReport(UserReport userReport, Action<bool, UserReport> callback)
|
|
{
|
|
this.SendUserReport(userReport, null, callback);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a user report to the server.
|
|
/// </summary>
|
|
/// <param name="userReport">The user report.</param>
|
|
/// <param name="progressCallback">The progress callback. Provides the upload and download progress.</param>
|
|
/// <param name="callback">The callback. Provides a value indicating whether sending the user report was successful and provides the user report after it is modified by the server.</param>
|
|
public void SendUserReport(UserReport userReport, Action<float, float> progressCallback, Action<bool, UserReport> callback)
|
|
{
|
|
try
|
|
{
|
|
if (userReport == null)
|
|
{
|
|
return;
|
|
}
|
|
if (userReport.Identifier != null)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Warning, "Identifier cannot be set on the client side. The value provided was discarded.");
|
|
return;
|
|
}
|
|
if (userReport.ContentLength != 0)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Warning, "ContentLength cannot be set on the client side. The value provided was discarded.");
|
|
return;
|
|
}
|
|
if (userReport.ReceivedOn != default(DateTime))
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Warning, "ReceivedOn cannot be set on the client side. The value provided was discarded.");
|
|
return;
|
|
}
|
|
if (userReport.ExpiresOn != default(DateTime))
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Warning, "ExpiresOn cannot be set on the client side. The value provided was discarded.");
|
|
return;
|
|
}
|
|
this.LogEvent(UserReportEventLevel.Info, "Sending user report.");
|
|
string json = this.Platform.SerializeJson(userReport);
|
|
byte[] jsonData = Encoding.UTF8.GetBytes(json);
|
|
string endpoint = this.GetEndpoint();
|
|
string url = string.Format(string.Format("{0}/api/userreporting", endpoint));
|
|
this.Platform.Post(url, "application/json", jsonData, (uploadProgress, downloadProgress) =>
|
|
{
|
|
if (progressCallback != null)
|
|
{
|
|
progressCallback(uploadProgress, downloadProgress);
|
|
}
|
|
}, (success, result) =>
|
|
{
|
|
this.AddSynchronizedAction(() =>
|
|
{
|
|
if (success)
|
|
{
|
|
try
|
|
{
|
|
string jsonResult = Encoding.UTF8.GetString(result);
|
|
UserReport userReportResult = this.Platform.DeserializeJson<UserReport>(jsonResult);
|
|
if (userReportResult != null)
|
|
{
|
|
if (this.SendEventsToAnalytics)
|
|
{
|
|
Dictionary<string, object> eventData = new Dictionary<string, object>();
|
|
eventData.Add("UserReportIdentifier", userReport.Identifier);
|
|
this.Platform.SendAnalyticsEvent("UserReportingClient.SendUserReport", eventData);
|
|
}
|
|
callback(success, userReportResult);
|
|
}
|
|
else
|
|
{
|
|
callback(false, null);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Error, string.Format("Sending user report failed: {0}", ex.ToString()));
|
|
callback(false, null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Error, "Sending user report failed.");
|
|
callback(false, null);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Error, string.Format("Sending user report failed: {0}", ex.ToString()));
|
|
callback(false, null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a screenshot.
|
|
/// </summary>
|
|
/// <param name="maximumWidth">The maximum width.</param>
|
|
/// <param name="maximumHeight">The maximum height.</param>
|
|
/// <param name="callback">The callback. Provides the screenshot.</param>
|
|
public void TakeScreenshot(int maximumWidth, int maximumHeight, Action<UserReportScreenshot> callback)
|
|
{
|
|
this.TakeScreenshotFromSource(maximumWidth, maximumHeight, null, callback);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a screenshot.
|
|
/// </summary>
|
|
/// <param name="maximumWidth">The maximum width.</param>
|
|
/// <param name="maximumHeight">The maximum height.</param>
|
|
/// <param name="source">The source. Passing null will capture the screen. Passing a camera will capture the camera's view. Passing a render texture will capture the render texture.</param>
|
|
/// <param name="callback">The callback. Provides the screenshot.</param>
|
|
public void TakeScreenshotFromSource(int maximumWidth, int maximumHeight, object source, Action<UserReportScreenshot> callback)
|
|
{
|
|
this.LogEvent(UserReportEventLevel.Info, "Taking screenshot.");
|
|
this.screenshotsTaken++;
|
|
this.Platform.TakeScreenshot(this.frameNumber, maximumWidth, maximumHeight, source, (passedFrameNumber, data) =>
|
|
{
|
|
this.AddSynchronizedAction(() =>
|
|
{
|
|
lock (this.screenshots)
|
|
{
|
|
UserReportScreenshot userReportScreenshot = new UserReportScreenshot();
|
|
userReportScreenshot.FrameNumber = passedFrameNumber;
|
|
userReportScreenshot.DataBase64 = Convert.ToBase64String(data);
|
|
this.screenshots.Add(userReportScreenshot);
|
|
this.screenshotsSaved++;
|
|
callback(userReportScreenshot);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the user reporting client, which updates networking communication, screenshotting, and metrics gathering.
|
|
/// </summary>
|
|
public void Update()
|
|
{
|
|
// Stopwatch
|
|
this.updateStopwatch.Reset();
|
|
this.updateStopwatch.Start();
|
|
|
|
// Update Platform
|
|
this.Platform.Update(this);
|
|
|
|
// Measures
|
|
if (this.Configuration.MetricsGatheringMode != MetricsGatheringMode.Disabled)
|
|
{
|
|
this.isMeasureBoundary = false;
|
|
int framesPerMeasure = this.Configuration.FramesPerMeasure;
|
|
if (this.measureFrames >= framesPerMeasure)
|
|
{
|
|
lock (this.measures)
|
|
{
|
|
UserReportMeasure userReportMeasure = new UserReportMeasure();
|
|
userReportMeasure.StartFrameNumber = this.frameNumber - framesPerMeasure;
|
|
userReportMeasure.EndFrameNumber = this.frameNumber - 1;
|
|
UserReportMeasure evictedUserReportMeasure = this.measures.GetNextEviction();
|
|
if (evictedUserReportMeasure.Metrics != null)
|
|
{
|
|
userReportMeasure.Metadata = evictedUserReportMeasure.Metadata;
|
|
userReportMeasure.Metrics = evictedUserReportMeasure.Metrics;
|
|
}
|
|
else
|
|
{
|
|
userReportMeasure.Metadata = new List<UserReportNamedValue>();
|
|
userReportMeasure.Metrics = new List<UserReportMetric>();
|
|
}
|
|
userReportMeasure.Metadata.Clear();
|
|
userReportMeasure.Metrics.Clear();
|
|
foreach (KeyValuePair<string, string> kvp in this.currentMeasureMetadata)
|
|
{
|
|
UserReportNamedValue userReportNamedValue = new UserReportNamedValue();
|
|
userReportNamedValue.Name = kvp.Key;
|
|
userReportNamedValue.Value = kvp.Value;
|
|
userReportMeasure.Metadata.Add(userReportNamedValue);
|
|
}
|
|
foreach (KeyValuePair<string, UserReportMetric> kvp in this.currentMetrics)
|
|
{
|
|
userReportMeasure.Metrics.Add(kvp.Value);
|
|
}
|
|
this.currentMetrics.Clear();
|
|
this.measures.Add(userReportMeasure);
|
|
this.measureFrames = 0;
|
|
this.isMeasureBoundary = true;
|
|
}
|
|
}
|
|
this.measureFrames++;
|
|
}
|
|
else
|
|
{
|
|
this.isMeasureBoundary = true;
|
|
}
|
|
|
|
// Synchronization
|
|
lock (this.synchronizedActions)
|
|
{
|
|
foreach (Action synchronizedAction in this.synchronizedActions)
|
|
{
|
|
this.currentSynchronizedActions.Add(synchronizedAction);
|
|
}
|
|
this.synchronizedActions.Clear();
|
|
}
|
|
|
|
// Perform Synchronized Actions
|
|
foreach (Action synchronizedAction in this.currentSynchronizedActions)
|
|
{
|
|
synchronizedAction();
|
|
}
|
|
this.currentSynchronizedActions.Clear();
|
|
|
|
// Frame Number
|
|
this.frameNumber++;
|
|
|
|
// Stopwatch
|
|
this.updateStopwatch.Stop();
|
|
this.SampleClientMetric("UserReportingClient.Update", this.updateStopwatch.ElapsedMilliseconds);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the user reporting client at the end of the frame, which updates networking communication, screenshotting, and metrics gathering.
|
|
/// </summary>
|
|
public void UpdateOnEndOfFrame()
|
|
{
|
|
// Stopwatch
|
|
this.updateStopwatch.Reset();
|
|
this.updateStopwatch.Start();
|
|
|
|
// Update Platform
|
|
this.Platform.OnEndOfFrame(this);
|
|
|
|
// Stopwatch
|
|
this.updateStopwatch.Stop();
|
|
this.SampleClientMetric("UserReportingClient.UpdateOnEndOfFrame", this.updateStopwatch.ElapsedMilliseconds);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits for perforation, a boundary between measures when no screenshots are in progress.
|
|
/// </summary>
|
|
/// <param name="currentScreenshotsTaken">The current screenshots taken.</param>
|
|
/// <param name="callback">The callback.</param>
|
|
private void WaitForPerforation(int currentScreenshotsTaken, Action callback)
|
|
{
|
|
if (this.screenshotsSaved >= currentScreenshotsTaken && this.isMeasureBoundary)
|
|
{
|
|
callback();
|
|
}
|
|
else
|
|
{
|
|
this.AddSynchronizedAction(() => { this.WaitForPerforation(currentScreenshotsTaken, callback); });
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |