Skip to content

Commit 685497a

Browse files
lisandroctaasonikazimuth
authored
Example of custom event handling (#327)
## Description of Changes *Describe what has been changed, any new features or bug fixes* ## API - [ ] This is an API breaking change to the SDK *If the API is breaking, please state below what will break* ## Requires SpacetimeDB PRs *List any PRs here that are required for this SDK change to work* ## Testsuite *If you would like to run the your SDK changes in this PR against a specific SpacetimeDB branch, specify that here. This can be a branch name or a link to a PR.* SpacetimeDB branch name: master ## Testing *Write instructions for a test that you performed for this PR* - [ ] Describe a test for this PR that you have completed --------- Co-authored-by: Alessandro Asoni <[email protected]> Co-authored-by: James Gilles <[email protected]>
1 parent 39d9994 commit 685497a

File tree

8 files changed

+285
-15
lines changed

8 files changed

+285
-15
lines changed

src/EventHandling.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
3+
namespace SpacetimeDB.EventHandling
4+
{
5+
internal class AbstractEventHandler
6+
{
7+
private EventListeners<Action> Listeners { get; } = new();
8+
9+
public void Invoke()
10+
{
11+
for (var i = Listeners.Count - 1; i >= 0; i--)
12+
{
13+
Listeners[i]?.Invoke();
14+
}
15+
}
16+
17+
public void AddListener(Action listener) => Listeners.Add(listener);
18+
19+
public void RemoveListener(Action listener) => Listeners.Remove(listener);
20+
}
21+
internal class AbstractEventHandler<T>
22+
{
23+
private EventListeners<Action<T>> Listeners { get; } = new();
24+
25+
public void Invoke(T value)
26+
{
27+
for (var i = Listeners.Count - 1; i >= 0; i--)
28+
{
29+
Listeners[i]?.Invoke(value);
30+
}
31+
}
32+
33+
public void AddListener(Action<T> listener) => Listeners.Add(listener);
34+
public void RemoveListener(Action<T> listener) => Listeners.Remove(listener);
35+
}
36+
37+
internal class AbstractEventHandler<T1, T2>
38+
{
39+
private EventListeners<Action<T1, T2>> Listeners { get; } = new();
40+
41+
public void Invoke(T1 v1, T2 v2)
42+
{
43+
for (var i = Listeners.Count - 1; i >= 0; i--)
44+
{
45+
Listeners[i]?.Invoke(v1, v2);
46+
}
47+
}
48+
49+
public void AddListener(Action<T1, T2> listener) => Listeners.Add(listener);
50+
public void RemoveListener(Action<T1, T2> listener) => Listeners.Remove(listener);
51+
}
52+
53+
internal class AbstractEventHandler<T1, T2, T3>
54+
{
55+
private EventListeners<Action<T1, T2, T3>> Listeners { get; } = new();
56+
57+
public void Invoke(T1 v1, T2 v2, T3 v3)
58+
{
59+
for (var i = Listeners.Count - 1; i >= 0; i--)
60+
{
61+
Listeners[i]?.Invoke(v1, v2, v3);
62+
}
63+
}
64+
65+
public void AddListener(Action<T1, T2, T3> listener) => Listeners.Add(listener);
66+
public void RemoveListener(Action<T1, T2, T3> listener) => Listeners.Remove(listener);
67+
}
68+
69+
internal class AbstractEventHandler<T1, T2, T3, T4>
70+
{
71+
private EventListeners<Action<T1, T2, T3, T4>> Listeners { get; } = new();
72+
73+
public void Invoke(T1 v1, T2 v2, T3 v3, T4 v4)
74+
{
75+
for (var i = Listeners.Count - 1; i >= 0; i--)
76+
{
77+
Listeners[i]?.Invoke(v1, v2, v3, v4);
78+
}
79+
}
80+
81+
public void AddListener(Action<T1, T2, T3, T4> listener) => Listeners.Add(listener);
82+
public void RemoveListener(Action<T1, T2, T3, T4> listener) => Listeners.Remove(listener);
83+
}
84+
85+
internal class AbstractEventHandler<T1, T2, T3, T4, T5>
86+
{
87+
private EventListeners<Action<T1, T2, T3, T4, T5>> Listeners { get; } = new();
88+
89+
public void Invoke(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5)
90+
{
91+
for (var i = Listeners.Count - 1; i >= 0; i--)
92+
{
93+
Listeners[i]?.Invoke(v1, v2, v3, v4, v5);
94+
}
95+
}
96+
97+
public void AddListener(Action<T1, T2, T3, T4, T5> listener) => Listeners.Add(listener);
98+
public void RemoveListener(Action<T1, T2, T3, T4, T5> listener) => Listeners.Remove(listener);
99+
}
100+
}

src/EventHandling/AbstractEventHandler.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EventHandling/EventListeners.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace SpacetimeDB.EventHandling
5+
{
6+
internal class EventListeners<T> where T : Delegate
7+
{
8+
private List<T> List { get; }
9+
private Dictionary<T, int> Indices { get; }
10+
11+
public int Count => List.Count;
12+
13+
public T this[int index] => List[index];
14+
15+
public EventListeners() : this(0) { }
16+
public EventListeners(int initialSize)
17+
{
18+
List = new List<T>(initialSize);
19+
Indices = new Dictionary<T, int>(initialSize);
20+
}
21+
22+
public void Add(T listener)
23+
{
24+
if (listener == null || !Indices.TryAdd(listener, List.Count)) return;
25+
List.Add(listener);
26+
}
27+
28+
public void Remove(T listener)
29+
{
30+
if (listener == null || List.Count <= 0 || !Indices.Remove(listener, out var index)) return;
31+
var lastListener = List[^1];
32+
if (lastListener != listener)
33+
{
34+
Indices[lastListener] = index;
35+
}
36+
37+
List.RemoveAtSwapBack(index);
38+
}
39+
}
40+
}

src/EventHandling/EventListeners.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EventHandling/ListExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace SpacetimeDB.EventHandling
5+
{
6+
internal static class ListExtensions
7+
{
8+
public static void RemoveAtSwapBack<T>(this List<T> list, int index)
9+
{
10+
if (list == null) throw new ArgumentNullException(nameof(list));
11+
12+
var lastIndex = list.Count - 1;
13+
14+
if (index < 0 || index > lastIndex) throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
15+
16+
if (index == lastIndex)
17+
{
18+
list.RemoveAt(index);
19+
return;
20+
}
21+
22+
list[index] = list[lastIndex];
23+
24+
list.RemoveAt(lastIndex);
25+
}
26+
}
27+
}

src/EventHandling/ListExtensions.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Table.cs

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using SpacetimeDB.BSATN;
99
using SpacetimeDB.ClientApi;
10+
using SpacetimeDB.EventHandling;
1011

1112
#nullable enable
1213
namespace SpacetimeDB
@@ -134,8 +135,18 @@ public RemoteTableHandle(IDbConnection conn) : base(conn) { }
134135
// I didn't do that because that delays the index updates until after the row is processed.
135136
// In theory, that shouldn't be the issue, but I didn't want to break it right before leaving :)
136137
// - Ingvar
137-
private event Action<Row>? OnInternalInsert;
138-
private event Action<Row>? OnInternalDelete;
138+
private AbstractEventHandler<Row> OnInternalInsertHandler { get; } = new();
139+
private event Action<Row> OnInternalInsert
140+
{
141+
add => OnInternalInsertHandler.AddListener(value);
142+
remove => OnInternalInsertHandler.RemoveListener(value);
143+
}
144+
private AbstractEventHandler<Row> OnInternalDeleteHandler { get; } = new();
145+
private event Action<Row> OnInternalDelete
146+
{
147+
add => OnInternalDeleteHandler.AddListener(value);
148+
remove => OnInternalDeleteHandler.RemoveListener(value);
149+
}
139150

140151
// These are implementations of the type-erased interface.
141152
object? IRemoteTableHandle.GetPrimaryKey(IStructuralReadWrite row) => GetPrimaryKey((Row)row);
@@ -177,12 +188,32 @@ private static IReadWrite<Row> Serializer
177188
IStructuralReadWrite IRemoteTableHandle.DecodeValue(BinaryReader reader) => Serializer.Read(reader);
178189

179190
public delegate void RowEventHandler(EventContext context, Row row);
180-
public event RowEventHandler? OnInsert;
181-
public event RowEventHandler? OnDelete;
182-
public event RowEventHandler? OnBeforeDelete;
191+
private CustomRowEventHandler OnInsertHandler { get; } = new();
192+
public event RowEventHandler OnInsert
193+
{
194+
add => OnInsertHandler.AddListener(value);
195+
remove => OnInsertHandler.RemoveListener(value);
196+
}
197+
private CustomRowEventHandler OnDeleteHandler { get; } = new();
198+
public event RowEventHandler OnDelete
199+
{
200+
add => OnDeleteHandler.AddListener(value);
201+
remove => OnDeleteHandler.RemoveListener(value);
202+
}
203+
private CustomRowEventHandler OnBeforeDeleteHandler { get; } = new();
204+
public event RowEventHandler OnBeforeDelete
205+
{
206+
add => OnBeforeDeleteHandler.AddListener(value);
207+
remove => OnBeforeDeleteHandler.RemoveListener(value);
208+
}
183209

184210
public delegate void UpdateEventHandler(EventContext context, Row oldRow, Row newRow);
185-
public event UpdateEventHandler? OnUpdate;
211+
private CustomUpdateEventHandler OnUpdateHandler { get; } = new();
212+
public event UpdateEventHandler OnUpdate
213+
{
214+
add => OnUpdateHandler.AddListener(value);
215+
remove => OnUpdateHandler.RemoveListener(value);
216+
}
186217

187218
public int Count => (int)Entries.CountDistinct;
188219

@@ -195,7 +226,7 @@ void InvokeInsert(IEventContext context, IStructuralReadWrite row)
195226
{
196227
try
197228
{
198-
OnInsert?.Invoke((EventContext)context, (Row)row);
229+
OnInsertHandler.Invoke((EventContext)context, (Row)row);
199230
}
200231
catch (Exception e)
201232
{
@@ -207,7 +238,7 @@ void InvokeDelete(IEventContext context, IStructuralReadWrite row)
207238
{
208239
try
209240
{
210-
OnDelete?.Invoke((EventContext)context, (Row)row);
241+
OnDeleteHandler.Invoke((EventContext)context, (Row)row);
211242
}
212243
catch (Exception e)
213244
{
@@ -219,7 +250,7 @@ void InvokeBeforeDelete(IEventContext context, IStructuralReadWrite row)
219250
{
220251
try
221252
{
222-
OnBeforeDelete?.Invoke((EventContext)context, (Row)row);
253+
OnBeforeDeleteHandler.Invoke((EventContext)context, (Row)row);
223254
}
224255
catch (Exception e)
225256
{
@@ -231,7 +262,7 @@ void InvokeUpdate(IEventContext context, IStructuralReadWrite oldRow, IStructura
231262
{
232263
try
233264
{
234-
OnUpdate?.Invoke((EventContext)context, (Row)oldRow, (Row)newRow);
265+
OnUpdateHandler.Invoke((EventContext)context, (Row)oldRow, (Row)newRow);
235266
}
236267
catch (Exception e)
237268
{
@@ -276,7 +307,7 @@ void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta<object
276307
{
277308
if (value is Row newRow)
278309
{
279-
OnInternalInsert?.Invoke(newRow);
310+
OnInternalInsertHandler.Invoke(newRow);
280311
}
281312
else
282313
{
@@ -287,7 +318,7 @@ void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta<object
287318
{
288319
if (oldValue is Row oldRow)
289320
{
290-
OnInternalDelete?.Invoke((Row)oldValue);
321+
OnInternalDeleteHandler.Invoke(oldRow);
291322
}
292323
else
293324
{
@@ -297,7 +328,7 @@ void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta<object
297328

298329
if (newValue is Row newRow)
299330
{
300-
OnInternalInsert?.Invoke(newRow);
331+
OnInternalInsertHandler.Invoke(newRow);
301332
}
302333
else
303334
{
@@ -309,7 +340,7 @@ void IRemoteTableHandle.Apply(IEventContext context, MultiDictionaryDelta<object
309340
{
310341
if (value is Row oldRow)
311342
{
312-
OnInternalDelete?.Invoke(oldRow);
343+
OnInternalDeleteHandler.Invoke(oldRow);
313344
}
314345
}
315346
}
@@ -333,6 +364,37 @@ void IRemoteTableHandle.PostApply(IEventContext context)
333364
wasRemoved.Clear();
334365

335366
}
367+
368+
private class CustomRowEventHandler
369+
{
370+
private EventListeners<RowEventHandler> Listeners { get; } = new();
371+
372+
public void Invoke(EventContext ctx, Row row)
373+
{
374+
for (var i = Listeners.Count - 1; i >= 0; i--)
375+
{
376+
Listeners[i]?.Invoke(ctx, row);
377+
}
378+
}
379+
380+
public void AddListener(RowEventHandler listener) => Listeners.Add(listener);
381+
public void RemoveListener(RowEventHandler listener) => Listeners.Remove(listener);
382+
}
383+
private class CustomUpdateEventHandler
384+
{
385+
private EventListeners<UpdateEventHandler> Listeners { get; } = new();
386+
387+
public void Invoke(EventContext ctx, Row oldRow, Row newRow)
388+
{
389+
for (var i = Listeners.Count - 1; i >= 0; i--)
390+
{
391+
Listeners[i]?.Invoke(ctx, oldRow, newRow);
392+
}
393+
}
394+
395+
public void AddListener(UpdateEventHandler listener) => Listeners.Add(listener);
396+
public void RemoveListener(UpdateEventHandler listener) => Listeners.Remove(listener);
397+
}
336398
}
337399
}
338-
#nullable disable
400+
#nullable disable

0 commit comments

Comments
 (0)