Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

GH-143 Change compass to event based. #144

Merged
merged 5 commits into from
Apr 2, 2018
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
28 changes: 2 additions & 26 deletions Caboodle.Tests/Compass_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public class Compass_Tests
public Compass_Tests()
{
Compass.Stop();
Compass.DisposeToken();
}

[Fact]
Expand All @@ -18,37 +17,14 @@ public void IsSupported_On_NetStandard() =>

[Fact]
public void Monitor_Null_Handler_On_NetStandard() =>
Assert.Throws<ArgumentNullException>(() => Compass.Start(SensorSpeed.Normal, null));
Assert.Throws<ArgumentNullException>(() => Compass.Start(SensorSpeed.Normal));

[Fact]
public void Monitor_On_NetStandard() =>
Assert.Throws<NotImplementedInReferenceAssemblyException>(() => Compass.Start(SensorSpeed.Normal, (data) => { }));
Assert.Throws<NotImplementedInReferenceAssemblyException>(() => Compass.Start(SensorSpeed.Normal));

[Fact]
public void IsMonitoring_Default_On_NetStandard() =>
Assert.False(Compass.IsMonitoring);

[Fact]
public void IsMonitoring_NetStandard()
{
Compass.CreateToken();
Assert.True(Compass.IsMonitoring);
}

[Fact]
public void Dispose_Token_NetStandard()
{
Compass.CreateToken();
Compass.DisposeToken();
Assert.Null(Compass.MonitorCTS);
}

[Fact]
public void Stop_Monitor_NetStandard()
{
Compass.CreateToken();
Compass.Stop();
Assert.False(Compass.IsMonitoring);
}
}
}
80 changes: 36 additions & 44 deletions Caboodle/Compass/Compass.android.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Android.Hardware;
using Android.Runtime;

Expand All @@ -13,10 +11,13 @@ public static partial class Compass
Platform.SensorManager?.GetDefaultSensor(SensorType.Accelerometer) != null &&
Platform.SensorManager?.GetDefaultSensor(SensorType.MagneticField) != null;

internal static void PlatformStart(SensorSpeed sensorSpeed, Action<CompassData> handler)
static SensorListener listener;
static Sensor magnetometer;
static Sensor accelerometer;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we dispose/null these out when they are no longer needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so they are shared system wide and if you dispose them out then they will effect other listeners from my testing :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well actually.... maybe if we only allow one at a time.... let me see here. Will need to see if you can do both compass and others at the same time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could get ugly, but could we track/count the # of listeners we have and dispose if 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we use a weak reference? This way we leave it up the Androids?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#resolved


internal static void PlatformStart(SensorSpeed sensorSpeed)
{
var delay = SensorDelay.Normal;
var useSyncContext = false;
switch (sensorSpeed)
{
case SensorSpeed.Normal:
Expand All @@ -27,49 +28,51 @@ internal static void PlatformStart(SensorSpeed sensorSpeed, Action<CompassData>
break;
case SensorSpeed.Game:
delay = SensorDelay.Game;
useSyncContext = true;
break;
case SensorSpeed.Ui:
delay = SensorDelay.Ui;
useSyncContext = true;
break;
}

var sensorListener = new SensorListener(useSyncContext, handler, delay);

MonitorCTS.Token.Register(CancelledToken, useSyncContext);
accelerometer = Platform.SensorManager.GetDefaultSensor(SensorType.Accelerometer);
magnetometer = Platform.SensorManager.GetDefaultSensor(SensorType.MagneticField);
listener = new SensorListener(accelerometer, magnetometer, delay);
Platform.SensorManager.RegisterListener(listener, accelerometer, delay);
Platform.SensorManager.RegisterListener(listener, magnetometer, delay);
}

void CancelledToken()
{
sensorListener.Dispose();
sensorListener = null;
DisposeToken();
}
internal static void PlatformStop()
{
if (listener == null)
return;

Platform.SensorManager.UnregisterListener(listener, accelerometer);
Platform.SensorManager.UnregisterListener(listener, magnetometer);
listener.Dispose();
listener = null;
magnetometer?.Dispose();
magnetometer = null;
accelerometer?.Dispose();
accelerometer = null;
}
}

internal class SensorListener : Java.Lang.Object, ISensorEventListener, IDisposable
{
Action<CompassData> handler;
float[] lastAccelerometer = new float[3];
float[] lastMagnetometer = new float[3];
bool lastAccelerometerSet;
bool lastMagnetometerSet;
float[] r = new float[9];
float[] orientation = new float[3];
bool useSyncContext;
Sensor accelerometer;

Sensor magnetometer;
Sensor accelerometer;

internal SensorListener(bool useSyncContext, Action<CompassData> handler, SensorDelay delay)
internal SensorListener(Sensor accelerometer, Sensor magnetometer, SensorDelay delay)
{
this.handler = handler;
this.useSyncContext = useSyncContext;

accelerometer = Platform.SensorManager.GetDefaultSensor(SensorType.Accelerometer);
magnetometer = Platform.SensorManager.GetDefaultSensor(SensorType.MagneticField);
Platform.SensorManager.RegisterListener(this, accelerometer, delay);
Platform.SensorManager.RegisterListener(this, magnetometer, delay);
this.magnetometer = magnetometer;
this.accelerometer = accelerometer;
}

public void OnAccuracyChanged(Sensor sensor, [GeneratedEnum] SensorStatus accuracy)
Expand Down Expand Up @@ -97,14 +100,7 @@ public void OnSensorChanged(SensorEvent e)
var azimuthInDegress = (Java.Lang.Math.ToDegrees(azimuthInRadians) + 360.0) % 360.0;

var data = new CompassData(azimuthInDegress);
if (useSyncContext)
{
Platform.BeginInvokeOnMainThread(() => handler?.Invoke(data));
}
else
{
handler?.Invoke(data);
}
Compass.OnChanged(data);
lastMagnetometerSet = false;
lastAccelerometerSet = false;
}
Expand All @@ -118,21 +114,17 @@ void CopyValues(IList<float> source, float[] destination)
}
}

bool disposed = false;
bool disposed;

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposed)
{
if (disposing)
{
Platform.SensorManager.UnregisterListener(this, accelerometer);
Platform.SensorManager.UnregisterListener(this, magnetometer);
}
if (!disposing || disposed)
return;

disposed = true;
}
disposed = true;
accelerometer = null;
magnetometer = null;
}
}
}
48 changes: 19 additions & 29 deletions Caboodle/Compass/Compass.ios.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public static partial class Compass
internal static bool IsSupported =>
CLLocationManager.HeadingAvailable;

internal static void PlatformStart(SensorSpeed sensorSpeed, Action<CompassData> handler)
{
var useSyncContext = false;
static CLLocationManager locationManager;

var locationManager = new CLLocationManager();
internal static void PlatformStart(SensorSpeed sensorSpeed)
{
locationManager = new CLLocationManager();
switch (sensorSpeed)
{
case SensorSpeed.Fastest:
Expand All @@ -32,44 +32,34 @@ internal static void PlatformStart(SensorSpeed sensorSpeed, Action<CompassData>
case SensorSpeed.Normal:
locationManager.HeadingFilter = NormalFilter;
locationManager.DesiredAccuracy = CLLocation.AccuracyBest;
useSyncContext = true;
break;
case SensorSpeed.Ui:
locationManager.HeadingFilter = UiFilter;
locationManager.DesiredAccuracy = CLLocation.AccuracyBest;
useSyncContext = true;
break;
}

MonitorCTS.Token.Register(CancelledToken, useSyncContext);

locationManager.UpdatedHeading += LocationManagerUpdatedHeading;
locationManager.StartUpdatingHeading();
}

void CancelledToken()
{
if (locationManager != null)
{
locationManager.UpdatedHeading -= LocationManagerUpdatedHeading;
locationManager.StopUpdatingHeading();
locationManager.Dispose();
locationManager = null;
}
DisposeToken();
}
static void LocationManagerUpdatedHeading(object sender, CLHeadingUpdatedEventArgs e)
{
var data = new CompassData(e.NewHeading.MagneticHeading);
OnChanged(data);
}

void LocationManagerUpdatedHeading(object sender, CLHeadingUpdatedEventArgs e)
internal static void PlatformStop()
{
if (locationManager == null)
{
var data = new CompassData(e.NewHeading.MagneticHeading);
if (useSyncContext)
{
Platform.BeginInvokeOnMainThread(() => handler?.Invoke(data));
}
else
{
handler?.Invoke(data);
}
return;
}

locationManager.UpdatedHeading -= LocationManagerUpdatedHeading;
locationManager.StopUpdatingHeading();
locationManager.Dispose();
locationManager = null;
}
}
}
5 changes: 4 additions & 1 deletion Caboodle/Compass/Compass.netstandard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ public static partial class Compass
internal static bool IsSupported =>
throw new NotImplementedInReferenceAssemblyException();

internal static void PlatformStart(SensorSpeed sensorSpeed, Action<CompassData> handler) =>
internal static void PlatformStart(SensorSpeed sensorSpeed) =>
throw new NotImplementedInReferenceAssemblyException();

internal static void PlatformStop() =>
throw new NotImplementedInReferenceAssemblyException();
}
}
67 changes: 33 additions & 34 deletions Caboodle/Compass/Compass.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,13 @@ namespace Microsoft.Caboodle
{
public static partial class Compass
{
public static bool IsMonitoring =>
MonitorCTS != null && !MonitorCTS.IsCancellationRequested;
public static event CompassChangedEventHandler ReadingChanged;

public static void Start(SensorSpeed sensorSpeed, Action<CompassData> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}

PreMonitorValidation();
CreateToken();
PlatformStart(sensorSpeed, handler);
}

public static void Stop()
{
if (MonitorCTS == null)
return;

if (!MonitorCTS.Token.CanBeCanceled || MonitorCTS.Token.IsCancellationRequested)
return;

MonitorCTS.Cancel();
}
public static bool IsMonitoring { get; private set; }

internal static CancellationTokenSource MonitorCTS { get; set; }
internal static bool UseSyncContext { get; set; }

internal static void PreMonitorValidation()
public static void Start(SensorSpeed sensorSpeed)
{
if (!IsSupported)
{
Expand All @@ -42,26 +20,47 @@ internal static void PreMonitorValidation()

if (IsMonitoring)
{
throw new InvalidOperationException("Compass is already being monitored. Please stop to start a new session.");
return;
}

PlatformStart(sensorSpeed);
IsMonitoring = true;
}

internal static void CreateToken()
public static void Stop()
{
DisposeToken();
MonitorCTS = new CancellationTokenSource();
PlatformStop();
IsMonitoring = false;
}

internal static void DisposeToken()
internal static void OnChanged(CompassData reading)
=> OnChanged(new CompassChangedEventArgs(reading));

internal static void OnChanged(CompassChangedEventArgs e)
{
if (MonitorCTS == null)
if (ReadingChanged == null)
return;

MonitorCTS.Dispose();
MonitorCTS = null;
if (UseSyncContext)
{
Platform.BeginInvokeOnMainThread(() => ReadingChanged?.Invoke(e));
}
else
{
ReadingChanged?.Invoke(e);
}
}
}

public delegate void CompassChangedEventHandler(CompassChangedEventArgs e);

public class CompassChangedEventArgs : EventArgs
{
internal CompassChangedEventArgs(CompassData reading) => Reading = reading;

public CompassData Reading { get; }
}

public struct CompassData
{
internal CompassData(double headingMagneticNorth)
Expand Down
Loading