unit tw39582; {$mode Delphi}{$H+} // примитивный множественный обрабочтчик событий, Pub/Sub. // // универсальный класс можно найти, например, в Spring4Delphi // но там очень сложная библиотека с вуду типа генерации кода // в рантайме по RTTI, также с расширенными контейнерными // библиотеками и спользованием генериков так глубоко, что // Delphi XE2 часто ломается на сборке interface uses Classes, SysUtils, Generics.Collections; // события только типа procedure (Sender: TObject; ... params ...; var Cancel: boolean) of object // другие типы не поддерживаются, тип не проверяется (или S4D со всеми наворотами) // параметры событий хранятся внутри объекта во время цикла // следовательно, никакой рекурсии и многопоточности // процедуры вызова и передачи параметров придётся хардкодить и копипастить (или S4D с их вуду) type TSimpleMultiEvent
= class; ISimpleMultiEvent
= interface procedure AddHandler(const Proc: P); procedure RemoveHandler(const Proc: P); overload; procedure RemoveHandler(const Obj: TObject); overload; procedure RemoveHandler(const Method: TMethod); overload; // procedure RunEventOnBorrowedData(const RootEvent: TSimpleMultiEvent
); -- FPC bug 39681 procedure RunEventOnBorrowedData(const RootEvent: TObject); end; ESimpleMultiEvent = class(Exception) end; { TSimpleMultiEvent } TSimpleMultiEvent
= class(TInterfacedObject, ISimpleMultiEvent
) protected // for debug tests if nothing better // 0 - default; +1, +2 - вложенные вызовы при запуске события; -1, -2 - при удалении/добавлении обработчиков MEState: integer; MEHandlers: TList
; type TPredicate = function(var Value; var Data): boolean of object; private function PredMeth(var Handler; var Data): Boolean; function PredObj (var Handler; var Data): Boolean; function PredType(var Handler; var Data): Boolean; protected // переменные события EV_Sender: TObject; EV_StopEventLoop: boolean; // цикл отработки событий после сохранения переменных; // возвращает количество вызванных обработчиков function InvokeAllHandlers: integer; // вызывает один обработчик, с нужными параметрами и сохраняет флаг остановки цикла procedure InvokeOneHandler(const Handler: P); virtual; abstract; // забирает данные из "корневого" события procedure BorrowDataFrom(const RootEvent: TSimpleMultiEvent
); virtual; // вызывается после обработчиков - зачистка ref-counted переменных procedure ClearEventVars; virtual; // дополнительные функции, например "показать часики" перед обработчиками // события и убрать после, или записать в журнал или еще что. // не должны бросать наружу исключения! procedure BeforeEvents; virtual; procedure AfterEvents; virtual; // FPC bug 39581, param should be TSimpleMultiEvent
procedure RunEventOnBorrowedData(const RootEvent: TObject); private procedure ClearAllHandlers; procedure InternalRemoveHandlers(const Where: TPredicate; var Data); procedure StartDoingEvent; procedure EndDoingEvent; procedure StartManagingHandlers; procedure EndManagingHandlers; public procedure AddHandler(const Proc: P); procedure RemoveHandler(const Proc: P); overload; procedure RemoveHandler(const Obj: TObject); overload; procedure RemoveHandler(const Method: TMethod); overload; procedure BeforeDestruction; override; procedure AfterConstruction; override; var EventAfter, EventBefore: ISimpleMultiEvent
;
// procedure RunEvent(Self, ......) - копипастить в каждом классе
end;
IMultiNotifyEvent = interface(ISimpleMultiEvent }
// вызывается после сохранения всех параметров события
function TSimpleMultiEvent .InvokeAllHandlers: integer;
var Handler: P;
begin
StartDoingEvent;
try
Result := 0;
EV_StopEventLoop := False;
for Handler in MEHandlers do begin
InvokeOneHandler(Handler);
Inc(Result);
if EV_StopEventLoop then break; // событие отработано, дальше не вызываем
end;
finally
try
EndDoingEvent;
finally
ClearEventVars;
end;
end;
end;
procedure TSimpleMultiEvent .BorrowDataFrom(const RootEvent: TSimpleMultiEvent
);
begin
Self.EV_Sender := RootEvent.EV_Sender;
end;
procedure TSimpleMultiEvent .ClearEventVars;
begin
EV_StopEventLoop := False; // готовимся к следующему циклу
EV_Sender := nil;
end;
procedure TSimpleMultiEvent .BeforeEvents;
begin
// to be used by derived subclasses
end;
procedure TSimpleMultiEvent .AfterEvents;
begin
// to be used by derived subclasses
if Assigned(EventAfter) then
// EventAfter.Run
end;
procedure TSimpleMultiEvent .RunEventOnBorrowedData(
const RootEvent: TObject);
begin
BorrowDataFrom(RootEvent as TSimpleMultiEvent );
InvokeAllHandlers;
end;
// Inc/Dec можно заменить на многопоточный System.SyncObjs.TInterlocked.Increment
procedure TSimpleMultiEvent .StartDoingEvent;
begin
// >0 тоже нельзя, переменные сохраняются - не реентерабельно!
if MEState <> 0 then
raise ESimpleMultiEvent.Create('Can not invoke ' + QualifiedClassName + ' while its handlers are changing.');
Inc(MEState);
BeforeEvents;
end;
procedure TSimpleMultiEvent .EndDoingEvent;
begin
if MEState > 0 then
Dec(MEState);
if MEState = 0 then
AfterEvents;
end;
procedure TSimpleMultiEvent .StartManagingHandlers;
begin
if MEState > 0 then
raise ESimpleMultiEvent.Create('Can not change handlers of ' + QualifiedClassName + ' while the event is running.');
Dec(MEState);
end;
procedure TSimpleMultiEvent .EndManagingHandlers;
begin
if MEState < 0 then
Inc(MEState);
end;
procedure TSimpleMultiEvent .ClearAllHandlers;
begin
StartManagingHandlers;
try
if MEHandlers <> nil then
MEHandlers.Clear;
finally
EndManagingHandlers
end;
end;
(*** возникает вопрос, как передать значение.
var M: TNotifyEvent absolute Proc; - проще всего и быстрее всего, но...
[DCC Fatal Error] F2084 Internal Error: AV084D2278-W00000014-1
/*union*/ record case... 0:(Method: procedure of object); 1:(Arg: P); end;
[DCC Error] E2569 Type parameter 'P' may need finalization - not allowed in variant record. Consider using RECORD constraint
Приходится делать Move, и даже там "хардкодить" размер переменной :-/ ***)
procedure TSimpleMultiEvent .AddHandler(const Proc: P);
var M: procedure of object; // const Sz = SizeOf(M) == 0 !!! expression типа результат вызова void-функции;
begin
StartManagingHandlers;
try
M := nil;
Move(Proc, M, SizeOf(TNotifyEvent));
if Assigned(M) then
MEHandlers.Add(Proc);
finally
EndManagingHandlers
end;
end;
procedure TSimpleMultiEvent .InternalRemoveHandlers(
const Where: TPredicate; var Data);
var i: integer; H: P;
begin
if MEHandlers = nil then exit;
i := MEHandlers.Count;
if i <= 0 then exit;
StartManagingHandlers;
try
while i > 0 do begin
Dec(i);
H := MEHandlers[i];
if Where(H, Data) then
MEHandlers.Delete(i);
end;
finally
EndManagingHandlers
end;
end;
function TSimpleMultiEvent .PredMeth(var Handler; var Data): Boolean;
var M: TMethod absolute Handler; // - ICE on "absolute P"!
N: TMethod absolute Data;
begin
// Move(Handler, M, SizeOf(TNotifyEvent));
Result := ( M.EqualTo( N ) );
end;
procedure TSimpleMultiEvent .RemoveHandler(const Method: TMethod);
var LMethod: TMethod; // generic lambdas can not capture const-param record
begin
StartManagingHandlers;
try
LMethod := Method;
InternalRemoveHandlers(
PredMeth, LMethod
);
finally
EndManagingHandlers
end;
end;
function TSimpleMultiEvent .PredObj(var Handler; var Data): Boolean;
var M: ^TMethod;
Obj: TObject absolute Data;
begin
M := Pointer(@Handler);
Result := M^.Data = Obj;
end;
procedure TSimpleMultiEvent .RemoveHandler(const Obj: TObject);
var LObj: TObject;
begin
StartManagingHandlers;
try
LObj := Obj;
InternalRemoveHandlers(
PredObj, LObj
);
finally
EndManagingHandlers
end;
end;
function TSimpleMultiEvent .PredType(var Handler; var Data): Boolean;
var MH: TMethod absolute Handler;
MD: TMethod absolute Data;
begin
// Move(Handler, MH, SizeOf(TNotifyEvent));
Result := MH.EqualTo(MD);
end;
procedure TSimpleMultiEvent .RemoveHandler(const Proc: P);
var MP: TMethod; // generic lambdas can not capture const-param record
begin
StartManagingHandlers;
try
MP.Code := nil;
Move(Proc, MP, SizeOf(TNotifyEvent));
InternalRemoveHandlers(
PredType, MP
);
finally
EndManagingHandlers
end;
end;
procedure TSimpleMultiEvent .BeforeDestruction;
begin
Assert( MEState = 0, 'MultiEvent is busy, can not destroy!' );
ClearAllHandlers;
ClearEventVars;
MEHandlers.Free;
inherited;
end;
procedure TSimpleMultiEvent .AfterConstruction;
begin
MEHandlers := TList .Create;
MEHandlers.Capacity := 4;
inherited;
end;
{ TMultiNotifyEvent }
procedure TMultiNotifyEvent.InvokeOneHandler(const Handler: TNotifyEvent);
begin
Handler(EV_Sender);
end;
function TMultiNotifyEvent.RunEvent(const Sender: TObject): integer;
begin
EV_Sender := Sender;
Result := InvokeAllHandlers;
end;
{ TMultiSingleValEvent }
procedure TMultiSingleValEvent.InvokeOneHandler(const Handler: TSingleEvent);
begin
Handler( EV_Val );
end;
function TMultiSingleValEvent.RunEvent(const NewVal: single): integer;
begin
EV_Val := NewVal;
Result := InvokeAllHandlers;
end;
{ TMethodEq }
(****
// а вот тут копи-паста для использования
type
iMultiEventExample = interface(iSimpleMultiEvent