-
Notifications
You must be signed in to change notification settings - Fork 35
/
DelphiUtils.AutoEvents.pas
287 lines (238 loc) · 6.38 KB
/
DelphiUtils.AutoEvents.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
unit DelphiUtils.AutoEvents;
{
This module add support for multi-subscriber events compatible with anonymous
functions.
}
interface
uses
DelphiUtils.AutoObjects, Ntapi.ntrtl, DelphiApi.Reflection;
var
// A callback for handing exceptions that occur while delivering events.
// The result indicates whether the exception was handled.
AutoEventsExceptionHanlder: function (E: TObject): Boolean;
type
IAutoReleasable = DelphiUtils.AutoObjects.IAutoReleasable;
// A collection of weak interface references
[ThreadSafe]
TWeakArray<I : IInterface> = record
private
FEntries: TArray<Weak<I>>;
FLock: TRtlSRWLock;
function PreferredSizeMin(Count: Integer): Integer;
function PreferredSizeMax(Count: Integer): Integer;
[ThreadSafe(False)] function CompactWorker: Integer;
public
function Entries: TArray<I>;
function Add(const Entry: I): IAutoReleasable;
function HasAny: Boolean;
procedure Compact;
end;
TEventCallback = reference to procedure;
TCustomInvoker = reference to procedure (
Callback: TEventCallback
);
// An automatic multi-subscriber event with no parameters
[ThreadSafe]
TAutoEvent = record
private
FSubscribers: TWeakArray<TEventCallback>;
public
function Subscribe(Callback: TEventCallback): IAutoReleasable;
function HasSubscribers: Boolean;
procedure Invoke;
end;
TEventCallback<T> = reference to procedure (const Parameter: T);
TCustomInvoker<T> = reference to procedure (
Callback: TEventCallback<T>;
const Parameter: T
);
// An automatic multi-subscriber event with one parameter
[ThreadSafe]
TAutoEvent<T> = record
private
FSubscribers: TWeakArray<TEventCallback<T>>;
public
function Subscribe(Callback: TEventCallback<T>): IAutoReleasable;
function HasSubscribers: Boolean;
procedure Invoke(const Parameter: T);
end;
TEventCallback<T1, T2> = reference to procedure (
const Parameter1: T1;
const Parameter2: T2
);
TCustomInvoker<T1, T2> = reference to procedure (
Callback: TEventCallback<T1, T2>;
const Parameter1: T1;
const Parameter2: T2
);
// An automatic multi-subscriber event with two parameters
[ThreadSafe]
TAutoEvent<T1, T2> = record
private
FSubscribers: TWeakArray<TEventCallback<T1, T2>>;
public
function Subscribe(Callback: TEventCallback<T1, T2>): IAutoReleasable;
function HasSubscribers: Boolean;
procedure Invoke(const Parameter1: T1; const Parameter2: T2);
end;
implementation
uses
NtUtils.Synchronization;
{$BOOLEVAL OFF}
{$IFOPT R+}{$DEFINE R+}{$ENDIF}
{$IFOPT Q+}{$DEFINE Q+}{$ENDIF}
{ TWeakArray<I> }
function TWeakArray<I>.Add;
var
FirstEmptyIndex: Integer;
LockReverter: IAutoReleasable;
begin
LockReverter := RtlxAcquireSRWLockExclusive(@FLock);
// Compact and locate the first empty slot
FirstEmptyIndex := CompactWorker;
// Expand if the new item doesn't fit
if FirstEmptyIndex > High(FEntries) then
SetLength(FEntries, PreferredSizeMin(Succ(FirstEmptyIndex)));
// Save a weak reference and return a wrapper with a strong reference
FEntries[FirstEmptyIndex] := Entry;
Result := Auto.Copy<I>(Entry);
end;
procedure TWeakArray<I>.Compact;
var
LockReverter: IAutoReleasable;
begin
if RtlxTryAcquireSRWLockExclusive(@FLock, LockReverter) then
CompactWorker;
end;
function TWeakArray<I>.CompactWorker;
var
StrongRef: I;
j: Integer;
begin
// Move occupied slots into a continuous block preserving order
Result := 0;
for j := 0 to High(FEntries) do
if FEntries[j].Upgrade(StrongRef) then
begin
if j <> Result then
FEntries[Result] := StrongRef;
Inc(Result);
end;
// Trim the array when there are too many empty slots
if Length(FEntries) > PreferredSizeMax(Succ(Result)) then
SetLength(FEntries, PreferredSizeMax(Succ(Result)));
end;
function TWeakArray<I>.Entries;
var
i, Count: Integer;
LockReverter: IAutoReleasable;
begin
LockReverter := RtlxAcquireSRWLockShared(@FLock);
SetLength(Result, Length(FEntries));
Count := 0;
// Make strong reference copies
for i := 0 to High(Result) do
if FEntries[i].Upgrade(Result[Count]) then
Inc(Count);
// Truncate the result if necessary
if Length(Result) <> Count then
begin
SetLength(Result, Count);
// If there are too many empty slots, release our lock and try to compact
if Length(FEntries) > PreferredSizeMax(Count) then
begin
LockReverter := nil;
Compact;
end;
end;
end;
function TWeakArray<I>.HasAny;
var
StrongRef: I;
i: Integer;
LockReverter: IAutoReleasable;
begin
LockReverter := RtlxAcquireSRWLockShared(@FLock);
for i := 0 to High(FEntries) do
if FEntries[i].Upgrade(StrongRef) then
Exit(True);
Result := False;
end;
function TWeakArray<I>.PreferredSizeMax;
begin
Result := Count + Count div 3 + 6;
end;
function TWeakArray<I>.PreferredSizeMin;
begin
Result := Count + Count div 8 + 1;
end;
{ TAutoEvent }
function TAutoEvent.HasSubscribers;
begin
Result := FSubscribers.HasAny;
end;
procedure TAutoEvent.Invoke;
var
Callback: TEventCallback;
begin
for Callback in FSubscribers.Entries do
try
Callback;
except
on E: TObject do
if not Assigned(AutoEventsExceptionHanlder) or not
AutoEventsExceptionHanlder(E) then
raise;
end;
end;
function TAutoEvent.Subscribe;
begin
Result := FSubscribers.Add(Callback);
end;
{ TAutoEvent<T> }
function TAutoEvent<T>.HasSubscribers;
begin
Result := FSubscribers.HasAny;
end;
procedure TAutoEvent<T>.Invoke;
var
Callback: TEventCallback<T>;
begin
for Callback in FSubscribers.Entries do
try
Callback(Parameter);
except
on E: TObject do
if not Assigned(AutoEventsExceptionHanlder) or not
AutoEventsExceptionHanlder(E) then
raise;
end;
end;
function TAutoEvent<T>.Subscribe;
begin
Result := FSubscribers.Add(Callback);
end;
{ TAutoEvent<T1, T2> }
function TAutoEvent<T1, T2>.HasSubscribers;
begin
Result := FSubscribers.HasAny;
end;
procedure TAutoEvent<T1, T2>.Invoke;
var
Callback: TEventCallback<T1, T2>;
begin
for Callback in FSubscribers.Entries do
try
Callback(Parameter1, Parameter2);
except
on E: TObject do
if not Assigned(AutoEventsExceptionHanlder) or not
AutoEventsExceptionHanlder(E) then
raise;
end;
end;
function TAutoEvent<T1, T2>.Subscribe;
begin
Result := FSubscribers.Add(Callback);
end;
end.