mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 08:18:12 +02:00
490 lines
14 KiB
ObjectPascal
490 lines
14 KiB
ObjectPascal
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<P> = class;
|
||
|
||
ISimpleMultiEvent<P> = 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<P>); -- FPC bug 39681
|
||
procedure RunEventOnBorrowedData(const RootEvent: TObject);
|
||
end;
|
||
|
||
ESimpleMultiEvent = class(Exception) end;
|
||
|
||
{ TSimpleMultiEvent }
|
||
|
||
TSimpleMultiEvent<P> = class(TInterfacedObject, ISimpleMultiEvent<P>)
|
||
protected // for debug tests if nothing better
|
||
|
||
// 0 - default; +1, +2 - вложенные вызовы при запуске события; -1, -2 - при удалении/добавлении обработчиков
|
||
MEState: integer;
|
||
MEHandlers: TList<P>;
|
||
|
||
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<P>); virtual;
|
||
|
||
// вызывается после обработчиков - зачистка ref-counted переменных
|
||
procedure ClearEventVars; virtual;
|
||
|
||
// дополнительные функции, например "показать часики" перед обработчиками
|
||
// события и убрать после, или записать в журнал или еще что.
|
||
// не должны бросать наружу исключения!
|
||
procedure BeforeEvents; virtual;
|
||
procedure AfterEvents; virtual;
|
||
|
||
// FPC bug 39581, param should be TSimpleMultiEvent<P>
|
||
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<P>;
|
||
// procedure RunEvent(Self, ......) - копипастить в каждом классе
|
||
end;
|
||
|
||
|
||
IMultiNotifyEvent = interface(ISimpleMultiEvent<TNotifyEvent>)
|
||
function RunEvent(const Sender: TObject): integer;
|
||
end;
|
||
|
||
TMultiNotifyEvent = class (TSimpleMultiEvent<TNotifyEvent>, IMultiNotifyEvent)
|
||
protected
|
||
procedure InvokeOneHandler(const Handler: TNotifyEvent); override;
|
||
function RunEvent(const Sender: TObject): integer;
|
||
end;
|
||
|
||
TSingleEvent = procedure (const NewValue: single) of object;
|
||
|
||
IMultiSingleValEvent = interface(ISimpleMultiEvent<TSingleEvent>)
|
||
function RunEvent(const NewVal: single): integer;
|
||
end;
|
||
|
||
{ TMultiSingleValEvent }
|
||
|
||
TMultiSingleValEvent = class (TSimpleMultiEvent<TSingleEvent>, IMultiSingleValEvent)
|
||
private
|
||
EV_Val: single;
|
||
protected
|
||
procedure InvokeOneHandler(const Handler: TSingleEvent); override;
|
||
function RunEvent(const NewVal: single): integer;
|
||
end;
|
||
|
||
|
||
implementation
|
||
|
||
type TMethodEq = record helper for TMethod
|
||
function EqualTo(const M2: TMethod): boolean; inline;
|
||
end;
|
||
|
||
function TMethodEq.EqualTo(const M2: TMethod): boolean;
|
||
begin
|
||
Result := (Self.Data = M2.Data) and (Self.Code = M2.Code);
|
||
end;
|
||
|
||
|
||
{ TSimpleMultiEvent<P> }
|
||
|
||
// вызывается после сохранения всех параметров события
|
||
function TSimpleMultiEvent<P>.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<P>.BorrowDataFrom(const RootEvent: TSimpleMultiEvent
|
||
<P>);
|
||
begin
|
||
Self.EV_Sender := RootEvent.EV_Sender;
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.ClearEventVars;
|
||
begin
|
||
EV_StopEventLoop := False; // готовимся к следующему циклу
|
||
EV_Sender := nil;
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.BeforeEvents;
|
||
begin
|
||
// to be used by derived subclasses
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.AfterEvents;
|
||
begin
|
||
// to be used by derived subclasses
|
||
|
||
if Assigned(EventAfter) then
|
||
// EventAfter.Run
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.RunEventOnBorrowedData(
|
||
const RootEvent: TObject);
|
||
begin
|
||
BorrowDataFrom(RootEvent as TSimpleMultiEvent<P>);
|
||
InvokeAllHandlers;
|
||
end;
|
||
|
||
// Inc/Dec можно заменить на многопоточный System.SyncObjs.TInterlocked.Increment
|
||
|
||
procedure TSimpleMultiEvent<P>.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<P>.EndDoingEvent;
|
||
begin
|
||
if MEState > 0 then
|
||
Dec(MEState);
|
||
|
||
if MEState = 0 then
|
||
AfterEvents;
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.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<P>.EndManagingHandlers;
|
||
begin
|
||
if MEState < 0 then
|
||
Inc(MEState);
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.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<P>.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<P>.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<P>.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<P>.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<P>.PredObj(var Handler; var Data): Boolean;
|
||
var M: ^TMethod;
|
||
Obj: TObject absolute Data;
|
||
begin
|
||
M := Pointer(@Handler);
|
||
Result := M^.Data = Obj;
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.RemoveHandler(const Obj: TObject);
|
||
var LObj: TObject;
|
||
begin
|
||
StartManagingHandlers;
|
||
try
|
||
LObj := Obj;
|
||
InternalRemoveHandlers(
|
||
PredObj, LObj
|
||
);
|
||
finally
|
||
EndManagingHandlers
|
||
end;
|
||
end;
|
||
|
||
function TSimpleMultiEvent<P>.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<P>.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<P>.BeforeDestruction;
|
||
begin
|
||
Assert( MEState = 0, 'MultiEvent is busy, can not destroy!' );
|
||
ClearAllHandlers;
|
||
ClearEventVars;
|
||
MEHandlers.Free;
|
||
inherited;
|
||
end;
|
||
|
||
procedure TSimpleMultiEvent<P>.AfterConstruction;
|
||
begin
|
||
MEHandlers := TList<P>.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<TNotifyEvent>)
|
||
function RunEvent(const Sender: TObject): integer;
|
||
end;
|
||
|
||
TMultiEventExample = class(TSimpleMultiEvent<TNotifyEvent>, iMultiEventExample)
|
||
public
|
||
function RunEvent(const Sender: TObject): integer;
|
||
procedure InvokeOneHandler(const Handler: TNotifyEvent); override;
|
||
end;
|
||
|
||
// // использовать - через интерфейс
|
||
// THostingComponent = class ....
|
||
// private MyEvent: iMultiEventExample;
|
||
//
|
||
// // чтобы другие объекты могли подписываться
|
||
// public property Event: iMultiEventExample read MyEvent;
|
||
// end;
|
||
//
|
||
// procedure THostingComponent.DoEvent; begin
|
||
// MyEvent.RunEvent( Self, ....);
|
||
// end.
|
||
|
||
|
||
{ TMultiEventExample }
|
||
|
||
function TMultiEventExample.RunEvent(const Sender: TObject): integer;
|
||
begin
|
||
EV_Sender := Sender;
|
||
|
||
Result := InvokeAllHandlers();
|
||
end;
|
||
|
||
procedure TMultiEventExample.InvokeOneHandler(const Handler: TNotifyEvent);
|
||
begin
|
||
Handler(EV_Sender);
|
||
end;
|
||
|
||
|
||
// ищем у подписантов строку типа Apple=20, возвращаем значение
|
||
type
|
||
TSomeSearchEvent = procedure (const Sender: TObject; const Name: string; var Data: integer; Var Found_Stop_Now: boolean ) of object;
|
||
|
||
iMEvExample2 = interface(iSimpleMultiEvent<TSomeSearchEvent>)
|
||
function RunEvent(const Sender: TObject; const Name: string; var Data: integer): integer;
|
||
end;
|
||
|
||
TMEvExample2 = class(TSimpleMultiEvent<TSomeSearchEvent>, iMEvExample2)
|
||
private
|
||
EV_Data: ^integer;
|
||
EV_Name: string;
|
||
protected
|
||
procedure ClearEventVars; override;
|
||
public
|
||
function RunEvent(const Sender: TObject; const Name: string; var Data: integer): integer;
|
||
procedure InvokeOneHandler(const Handler: TSomeSearchEvent); override;
|
||
end;
|
||
|
||
{ TMEvExample2 }
|
||
|
||
procedure TMEvExample2.InvokeOneHandler(const Handler: TSomeSearchEvent);
|
||
begin
|
||
Handler( EV_Sender, EV_Name, EV_Data^, EV_StopEventLoop );
|
||
end;
|
||
|
||
procedure TMEvExample2.ClearEventVars;
|
||
begin
|
||
EV_Name := ''; // освободить ARC-Объект
|
||
// EV_Data := nil; - перестраховка для отладки
|
||
|
||
inherited;
|
||
end;
|
||
|
||
function TMEvExample2.RunEvent(const Sender: TObject; const Name: string;
|
||
var Data: integer): integer;
|
||
begin
|
||
EV_Sender := Sender;
|
||
EV_Data := @Data;
|
||
EV_Name := Name;
|
||
|
||
Result := InvokeAllHandlers();
|
||
end;
|
||
|
||
(****
|
||
|
||
***)
|
||
|
||
end.
|
||
|