Skip to content

Commit 0b49467

Browse files
authored
Merge pull request #924 from agracio/event-task-rework
ApiEventManager Rework and refactoring of task calls
2 parents c37f98d + 6fa65aa commit 0b49467

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1068
-1116
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ env:
99
jobs:
1010
linux:
1111
runs-on: ubuntu-latest
12+
timeout-minutes: 10
1213

1314
steps:
1415
- uses: actions/checkout@v4
@@ -26,6 +27,7 @@ jobs:
2627

2728
windows:
2829
runs-on: windows-latest
30+
timeout-minutes: 10
2931

3032
steps:
3133
- uses: actions/checkout@v4

src/ElectronNET.API/API/ApiBase.cs

Lines changed: 194 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,64 @@
1-
namespace ElectronNET.API
1+
// ReSharper disable InconsistentNaming
2+
namespace ElectronNET.API
23
{
3-
using ElectronNET.API.Serialization;
4-
using ElectronNET.Common;
4+
using Common;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
57
using System;
68
using System.Collections.Concurrent;
79
using System.Diagnostics;
810
using System.Runtime.CompilerServices;
9-
using System.Text.Json;
1011
using System.Threading.Tasks;
1112

1213
public abstract class ApiBase
1314
{
14-
protected enum SocketEventNameTypes
15+
protected enum SocketTaskEventNameTypes
16+
{
17+
DashesLowerFirst,
18+
NoDashUpperFirst
19+
}
20+
protected enum SocketTaskMessageNameTypes
1521
{
1622
DashesLowerFirst,
17-
NoDashUpperFirst,
23+
NoDashUpperFirst
1824
}
1925

20-
internal const int PropertyTimeout = 1000;
26+
protected enum SocketEventNameTypes
27+
{
28+
DashedLower,
29+
CamelCase,
30+
}
31+
32+
private const int PropertyTimeout = 1000;
2133

2234
private readonly string objectName;
23-
private readonly ConcurrentDictionary<string, PropertyGetter> propertyGetters = new ConcurrentDictionary<string, PropertyGetter>();
24-
private readonly ConcurrentDictionary<string, string> propertyEventNames = new ConcurrentDictionary<string, string>();
25-
private readonly ConcurrentDictionary<string, string> propertyMessageNames = new ConcurrentDictionary<string, string>();
26-
private readonly ConcurrentDictionary<string, string> methodMessageNames = new ConcurrentDictionary<string, string>();
35+
private readonly ConcurrentDictionary<string, PropertyGetter> propertyGetters;
36+
private readonly ConcurrentDictionary<string, string> propertyEventNames = new();
37+
private readonly ConcurrentDictionary<string, string> propertyMessageNames = new();
38+
private readonly ConcurrentDictionary<string, string> methodMessageNames = new();
39+
private static readonly ConcurrentDictionary<string, EventContainer> eventContainers = new();
40+
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, PropertyGetter>> AllPropertyGetters = new();
41+
2742
private readonly object objLock = new object();
2843

2944
public virtual int Id
3045
{
31-
get
32-
{
33-
return -1;
34-
}
46+
get => -1;
3547

3648
// ReSharper disable once ValueParameterNotUsed
3749
protected set
3850
{
3951
}
4052
}
4153

42-
protected abstract SocketEventNameTypes SocketEventNameType { get; }
54+
protected abstract SocketTaskEventNameTypes SocketTaskEventNameType { get; }
55+
protected virtual SocketTaskMessageNameTypes SocketTaskMessageNameType => SocketTaskMessageNameTypes.NoDashUpperFirst;
56+
protected virtual SocketEventNameTypes SocketEventNameType => SocketEventNameTypes.DashedLower;
4357

4458
protected ApiBase()
4559
{
4660
this.objectName = this.GetType().Name.LowerFirst();
61+
propertyGetters = AllPropertyGetters.GetOrAdd(objectName, _ => new ConcurrentDictionary<string, PropertyGetter>());
4762
}
4863

4964
protected void CallMethod0([CallerMemberName] string callerName = null)
@@ -98,15 +113,15 @@ protected void CallMethod3(object val1, object val2, object val3, [CallerMemberN
98113
}
99114
}
100115

101-
protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = null)
116+
protected Task<T> GetPropertyAsync<T>(object arg = null, [CallerMemberName] string callerName = null)
102117
{
103118
Debug.Assert(callerName != null, nameof(callerName) + " != null");
104119

105120
lock (this.objLock)
106121
{
107122
return this.propertyGetters.GetOrAdd(callerName, _ =>
108123
{
109-
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout);
124+
var getter = new PropertyGetter<T>(this, callerName, PropertyTimeout, arg);
110125

111126
getter.Task<T>().ContinueWith(_ =>
112127
{
@@ -120,6 +135,98 @@ protected Task<T> GetPropertyAsync<T>([CallerMemberName] string callerName = nul
120135
}).Task<T>();
121136
}
122137
}
138+
139+
protected void AddEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
140+
{
141+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
142+
var eventName = EventName(callerName);
143+
144+
var eventKey = EventKey(eventName, id);
145+
146+
lock (objLock)
147+
{
148+
var container = eventContainers.GetOrAdd(eventKey, _ =>
149+
{
150+
var container = new EventContainer();
151+
BridgeConnector.Socket.On(eventKey, container.OnEventAction);
152+
BridgeConnector.Socket.Emit($"register-{eventName}", id);
153+
return container;
154+
});
155+
156+
container.Register(value);
157+
}
158+
}
159+
160+
protected void RemoveEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
161+
{
162+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
163+
var eventName = EventName(callerName);
164+
var eventKey = EventKey(eventName, id);
165+
166+
lock (objLock)
167+
{
168+
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
169+
{
170+
BridgeConnector.Socket.Off(eventKey);
171+
eventContainers.TryRemove(eventKey, out _);
172+
}
173+
}
174+
}
175+
176+
protected void AddEvent<T>(Action<T> value, int? id = null, [CallerMemberName] string callerName = null)
177+
{
178+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
179+
180+
var eventName = EventName(callerName);
181+
var eventKey = EventKey(eventName, id);
182+
183+
lock (objLock)
184+
{
185+
var container = eventContainers.GetOrAdd(eventKey, _ =>
186+
{
187+
var container = new EventContainer();
188+
BridgeConnector.Socket.On<T>(eventKey, container.OnEventActionT);
189+
BridgeConnector.Socket.Emit($"register-{eventName}", id);
190+
return container;
191+
});
192+
193+
container.Register(value);
194+
}
195+
}
196+
197+
protected void RemoveEvent<T>(Action<T> value, int? id = null, [CallerMemberName] string callerName = null)
198+
{
199+
Debug.Assert(callerName != null, nameof(callerName) + " != null");
200+
var eventName = EventName(callerName);
201+
var eventKey = EventKey(eventName, id);
202+
203+
lock (objLock)
204+
{
205+
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
206+
{
207+
BridgeConnector.Socket.Off(eventKey);
208+
eventContainers.TryRemove(eventKey, out _);
209+
}
210+
}
211+
}
212+
213+
private string EventName(string callerName)
214+
{
215+
switch (SocketEventNameType)
216+
{
217+
case SocketEventNameTypes.DashedLower:
218+
return $"{objectName}-{callerName.ToDashedEventName()}";
219+
case SocketEventNameTypes.CamelCase:
220+
return $"{objectName}-{callerName.ToCamelCaseEventName()}";
221+
default:
222+
throw new ArgumentOutOfRangeException();
223+
}
224+
}
225+
226+
private string EventKey(string eventName, int? id)
227+
{
228+
return string.Format(CultureInfo.InvariantCulture, "{0}{1:D}", eventName, id);
229+
}
123230

124231
internal abstract class PropertyGetter
125232
{
@@ -131,26 +238,37 @@ internal class PropertyGetter<T> : PropertyGetter
131238
private readonly Task<T> tcsTask;
132239
private TaskCompletionSource<T> tcs;
133240

134-
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
241+
public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs, object arg = null)
135242
{
136243
this.tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
137244
this.tcsTask = this.tcs.Task;
138245

139246
string eventName;
247+
string messageName;
140248

141-
switch (apiBase.SocketEventNameType)
249+
switch (apiBase.SocketTaskEventNameType)
142250
{
143-
case SocketEventNameTypes.DashesLowerFirst:
251+
case SocketTaskEventNameTypes.DashesLowerFirst:
144252
eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}-completed");
145253
break;
146-
case SocketEventNameTypes.NoDashUpperFirst:
254+
case SocketTaskEventNameTypes.NoDashUpperFirst:
147255
eventName = apiBase.propertyEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}{s.StripAsync()}Completed");
148256
break;
149257
default:
150258
throw new ArgumentOutOfRangeException();
151259
}
152-
153-
var messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
260+
261+
switch (apiBase.SocketTaskMessageNameType)
262+
{
263+
case SocketTaskMessageNameTypes.DashesLowerFirst:
264+
messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}");
265+
break;
266+
case SocketTaskMessageNameTypes.NoDashUpperFirst:
267+
messageName = apiBase.propertyMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
268+
break;
269+
default:
270+
throw new ArgumentOutOfRangeException();
271+
}
154272

155273
BridgeConnector.Socket.Once<T>(eventName, (result) =>
156274
{
@@ -171,14 +289,14 @@ public PropertyGetter(ApiBase apiBase, string callerName, int timeoutMs)
171289
}
172290
}
173291
});
174-
175-
if (apiBase.Id >= 0)
292+
293+
if (arg != null)
176294
{
177-
BridgeConnector.Socket.Emit(messageName, apiBase.Id);
295+
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id, arg) : BridgeConnector.Socket.Emit(messageName, arg);
178296
}
179297
else
180298
{
181-
BridgeConnector.Socket.Emit(messageName);
299+
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id) : BridgeConnector.Socket.Emit(messageName);
182300
}
183301

184302
System.Threading.Tasks.Task.Delay(PropertyTimeout).ContinueWith(_ =>
@@ -203,5 +321,53 @@ public override Task<T1> Task<T1>()
203321
return this.tcsTask as Task<T1>;
204322
}
205323
}
324+
325+
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")]
326+
private class EventContainer
327+
{
328+
private Action eventAction;
329+
private Delegate eventActionT;
330+
331+
private Action<T> GetEventActionT<T>()
332+
{
333+
return (Action<T>)eventActionT;
334+
}
335+
336+
private void SetEventActionT<T>(Action<T> actionT)
337+
{
338+
eventActionT = actionT;
339+
}
340+
341+
public void OnEventAction() => eventAction?.Invoke();
342+
343+
public void OnEventActionT<T>(T p) => GetEventActionT<T>()?.Invoke(p);
344+
345+
public void Register(Action receiver)
346+
{
347+
eventAction += receiver;
348+
}
349+
350+
public void Register<T>(Action<T> receiver)
351+
{
352+
var actionT = GetEventActionT<T>();
353+
actionT += receiver;
354+
SetEventActionT(actionT);
355+
}
356+
357+
public bool Unregister(Action receiver)
358+
{
359+
eventAction -= receiver;
360+
return this.eventAction != null;
361+
}
362+
363+
public bool Unregister<T>(Action<T> receiver)
364+
{
365+
var actionT = GetEventActionT<T>();
366+
actionT -= receiver;
367+
SetEventActionT(actionT);
368+
369+
return actionT != null;
370+
}
371+
}
206372
}
207373
}

0 commit comments

Comments
 (0)