Skip to content

Commit

Permalink
Merge pull request #7137 from MartinZikmund/dev/mazi/startstopevent-s…
Browse files Browse the repository at this point in the history
…ensors
  • Loading branch information
MartinZikmund authored Jan 7, 2025
2 parents 4dd8184 + 1d09a11 commit a6f6184
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 98 deletions.
2 changes: 1 addition & 1 deletion src/Uno.UI.Runtime.Skia.Gtk/Helpers/Dpi/DpiHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Uno.UI.Runtime.Skia.Gtk.Helpers.Dpi;

internal class DpiHelper
{
private readonly StartStopEventWrapper<EventHandler> _dpiChangedWrapper;
private readonly StartStopDelegateWrapper<EventHandler> _dpiChangedWrapper;
private readonly UnoGtkWindow _window;

private float? _dpi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public class Given_StartStopEventWrapper
[TestMethod]
public void When_Default_Event_Null()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
Assert.IsNull(wrapper.Event);
}

[TestMethod]
public void When_Add_Handler_Event_Not_Null()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => { });
Assert.IsNotNull(wrapper.Event);
}
Expand All @@ -31,7 +31,7 @@ public void When_Add_Remove_Event_Null()
void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e)
{
}
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler(Handler);
wrapper.RemoveHandler(Handler);
Assert.IsNull(wrapper.Event);
Expand All @@ -40,7 +40,7 @@ void Handler(Accelerometer a, AccelerometerReadingChangedEventArgs e)
[TestMethod]
public void When_Remove_On_Empty()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.RemoveHandler((s, e) => { });
Assert.IsNull(wrapper.Event);
}
Expand All @@ -49,7 +49,7 @@ public void When_Remove_On_Empty()
[TestMethod]
public void When_Remove_Nonexistent()
{
var wrapper = new StartStopEventWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => { });
wrapper.RemoveHandler((s, e) => { });
Assert.IsNotNull(wrapper.Event);
Expand All @@ -59,7 +59,7 @@ public void When_Remove_Nonexistent()
public void When_Single_Invoked()
{
var invoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => invoked = true);
Assert.IsFalse(invoked);
wrapper.Event?.Invoke(null, EventArgs.Empty);
Expand All @@ -71,7 +71,7 @@ public void When_Multiple_Invoked()
{
var firstInvoked = false;
var secondInvoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler((s, e) => firstInvoked = true);
wrapper.AddHandler((s, e) => secondInvoked = true);
Assert.IsFalse(firstInvoked);
Expand All @@ -90,7 +90,7 @@ void FirstHandler(object sender, EventArgs args)
firstInvoked = true;
}
var secondInvoked = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => { });
wrapper.AddHandler(FirstHandler);
wrapper.AddHandler((s, e) => secondInvoked = true);
Assert.IsFalse(firstInvoked);
Expand All @@ -105,7 +105,7 @@ void FirstHandler(object sender, EventArgs args)
public void When_First_Subscriber_OnFirst_Invoked()
{
var onFirst = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirst = true, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirst = true, () => { });
Assert.IsFalse(onFirst);
wrapper.AddHandler((s, e) => { });
Assert.IsTrue(onFirst);
Expand All @@ -115,7 +115,7 @@ public void When_First_Subscriber_OnFirst_Invoked()
public void When_Multiple_Subscribers_OnFirst_Once()
{
var onFirstCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
Assert.AreEqual(0, onFirstCounter);
wrapper.AddHandler((s, e) => { });
wrapper.AddHandler((s, e) => { });
Expand All @@ -134,7 +134,7 @@ void SecondSubscriber(object sender, EventArgs e)
}

var onFirstCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => onFirstCounter++, () => { });
Assert.AreEqual(0, onFirstCounter);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Expand All @@ -153,7 +153,7 @@ void FirstSubscriber(object sender, EventArgs e)
{
}
var onLast = false;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLast = true);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLast = true);
wrapper.AddHandler(FirstSubscriber);
Assert.IsFalse(onLast);
wrapper.RemoveHandler(FirstSubscriber);
Expand All @@ -171,7 +171,7 @@ void SecondSubscriber(object sender, EventArgs e)
{
}
var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Assert.AreEqual(0, onLastCounter);
Expand All @@ -188,7 +188,7 @@ void FirstSubscriber(object sender, EventArgs e)
}

var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
Assert.AreEqual(0, onLastCounter);
wrapper.RemoveHandler(FirstSubscriber);
Expand All @@ -208,7 +208,7 @@ void SecondSubscriber(object sender, EventArgs e)
}

var onLastCounter = 0;
var wrapper = new StartStopEventWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
var wrapper = new StartStopDelegateWrapper<EventHandler<EventArgs>>(() => { }, () => onLastCounter++);
wrapper.AddHandler(FirstSubscriber);
wrapper.AddHandler(SecondSubscriber);
Assert.AreEqual(0, onLastCounter);
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UWP/Devices/Geolocation/Geolocator.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void OnLocationChanged(Location location)
_location = location;

BroadcastStatusChanged(PositionStatus.Ready);
_owner._positionChangedWrapper.Event?.Invoke(_owner, new PositionChangedEventArgs(location.ToGeoPosition()));
_owner._positionChangedWrapper.Invoke(_owner, new PositionChangedEventArgs(location.ToGeoPosition()));
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/Uno.UWP/Devices/Geolocation/Geolocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public sealed partial class Geolocator
// Using ConcurrentDictionary as concurrent HashSet (https://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework), byte is throwaway
private static readonly ConcurrentDictionary<Geolocator, byte> _statusChangedSubscriptions = new ConcurrentDictionary<Geolocator, byte>();

private readonly StartStopEventWrapper<TypedEventHandler<Geolocator, StatusChangedEventArgs>> _statusChangedWrapper;
private readonly StartStopEventWrapper<TypedEventHandler<Geolocator, PositionChangedEventArgs>> _positionChangedWrapper;
private readonly StartStopTypedEventWrapper<Geolocator, StatusChangedEventArgs> _statusChangedWrapper;
private readonly StartStopTypedEventWrapper<Geolocator, PositionChangedEventArgs> _positionChangedWrapper;

private PositionAccuracy _desiredAccuracy = PositionAccuracy.Default;

Expand All @@ -33,11 +33,11 @@ public sealed partial class Geolocator
/// </summary>
public Geolocator()
{
_statusChangedWrapper = new StartStopEventWrapper<TypedEventHandler<Geolocator, StatusChangedEventArgs>>(
_statusChangedWrapper = new StartStopTypedEventWrapper<Geolocator, StatusChangedEventArgs>(
() => StartStatusChanged(),
() => StopStatusChanged(),
_syncLock);
_positionChangedWrapper = new StartStopEventWrapper<TypedEventHandler<Geolocator, PositionChangedEventArgs>>(
_positionChangedWrapper = new StartStopTypedEventWrapper<Geolocator, PositionChangedEventArgs>(
() => StartPositionChanged(),
() => StopPositionChanged(),
_syncLock);
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UWP/Devices/Sensors/LightSensor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public partial class LightSensor
{
private readonly static Lazy<LightSensor?> _instance = new Lazy<LightSensor?>(() => TryCreateInstance());

private readonly StartStopEventWrapper<TypedEventHandler<LightSensor, LightSensorReadingChangedEventArgs>> _readingChangedWrapper;
private readonly StartStopTypedEventWrapper<LightSensor, LightSensorReadingChangedEventArgs> _readingChangedWrapper;

private LightSensor()
{
_readingChangedWrapper = new StartStopEventWrapper<TypedEventHandler<LightSensor, LightSensorReadingChangedEventArgs>>(
_readingChangedWrapper = new StartStopTypedEventWrapper<LightSensor, LightSensorReadingChangedEventArgs>(
StartReading, StopReading);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UWP/Devices/Sensors/ProximitySensor.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Windows.Devices.Sensors;

public partial class ProximitySensor
{
private readonly StartStopEventWrapper<TypedEventHandler<ProximitySensor, ProximitySensorReadingChangedEventArgs>> _readingChangedWrapper;
private readonly StartStopTypedEventWrapper<ProximitySensor, ProximitySensorReadingChangedEventArgs> _readingChangedWrapper;

private Sensor? _sensor;
private ProximitySensorListener? _listener;
Expand Down
8 changes: 4 additions & 4 deletions src/Uno.UWP/Gaming/Input/Gamepad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public partial class Gamepad : IGameController
{
private static readonly object _syncLock = new object();

private static readonly StartStopEventWrapper<EventHandler<Gamepad>> _gamepadAddedWrapper;
private static readonly StartStopEventWrapper<EventHandler<Gamepad>> _gamepadRemovedWrapper;
private static readonly StartStopEventWrapper<Gamepad> _gamepadAddedWrapper;
private static readonly StartStopEventWrapper<Gamepad> _gamepadRemovedWrapper;

static Gamepad()
{
_gamepadAddedWrapper = new StartStopEventWrapper<EventHandler<Gamepad>>(
_gamepadAddedWrapper = new StartStopEventWrapper<Gamepad>(
StartGamepadAdded, EndGamepadAdded, _syncLock);
_gamepadRemovedWrapper = new StartStopEventWrapper<EventHandler<Gamepad>>(
_gamepadRemovedWrapper = new StartStopEventWrapper<Gamepad>(
StartGamepadRemoved, EndGamepadRemoved, _syncLock);
}

Expand Down
90 changes: 90 additions & 0 deletions src/Uno.UWP/Helpers/StartStopDelegateWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#nullable enable

using System;

namespace Uno.Helpers;

/// <summary>
/// Creates a wrapper around a generic event, which needs to be synchronized
/// and needs to run an action when first subscriber is added and when
/// last subscriber is removed. The operations executed when first subscriber
/// is added and last subscriber is removed will execute within
/// a synchronization lock, so please avoid blocking within the actions.
/// </summary>
internal class StartStopDelegateWrapper<TDelegate>
where TDelegate : Delegate
{
private readonly object _syncLock;
private readonly Action _onFirst;
private readonly Action _onLast;

/// <summary>
/// Creates a new instance of start-stop delegate wrapper.
/// </summary>
/// <param name="onFirst">Action to run when first subscriber is added.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="onLast">Action to run when last subscriber is removed.
/// This will run within a synchronization lock so it should not involve blocking operations.</param>
/// <param name="sharedLock">Optional shared object to lock on (when multiple events
/// rely on the same native platform operation.</param>
public StartStopDelegateWrapper(
Action onFirst,
Action onLast,
object? sharedLock = null)
{
_onFirst = onFirst;
_onLast = onLast;
_syncLock = sharedLock ?? new object();
}

/// <summary>
/// Backing delegate.
/// </summary>
public TDelegate? Event { get; private set; }

/// <summary>
/// Indicates if there is at least one active subscription.
/// </summary>
public bool IsActive => Event != null;

/// <summary>
/// Synchronization lock.
/// </summary>
public object SyncLock => _syncLock;

/// <summary>
/// Adds a handler.
/// </summary>
/// <param name="handler">Handler.</param>
public void AddHandler(TDelegate handler)
{
lock (_syncLock)
{
var firstSubscriber = Event == null;
Event = (TDelegate)Delegate.Combine(Event, handler);
if (firstSubscriber)
{
_onFirst();
}
}
}

/// <summary>
/// Removes a handler.
/// </summary>
/// <param name="handler">Handler.</param>
public void RemoveHandler(TDelegate handler)
{
lock (_syncLock)
{
if (Event != null)
{
Event = (TDelegate?)Delegate.Remove(Event, handler);
if (Event == null)
{
_onLast();
}
}
}
}
}
Loading

0 comments on commit a6f6184

Please sign in to comment.