unit Myc.Core.Notifier; interface uses System.SysUtils, System.SyncObjs; type // Low-level implementation for thread-safe multicast events. // Implemented as a list referencing IInterface instances, with a locking mechanism for thread safety. // Utilizes minimal memory. // `Advise` adds an interface and returns a tag for constant-time removal by `Unadvise`. // `UnadviseAll` removes all registered interfaces. // After `Finalize` is called, the list is cleared, and no new interfaces can be added (closed state). TMycNotifyList = record type TTag = Pointer; // Opaque tag used to identify a registered receiver for unsubscription. PItem = ^TItem; // Pointer to an internal list item. TItem = record // Internal structure for storing a receiver and list linkage. Next, Prev: PItem; // Pointers to the next and previous items in the doubly linked list. Receiver: T; // The registered interface instance (the event sink). end; strict private FFirst: NativeUInt; // Stores the first receiver if no list is allocated, or acts as a combined lock and state field. // Bit 0: Lock state (0 = locked, 1 = unlocked). // Bit 1: Finalized state (0 = not finalized, 1 = finalized). // Other bits (if not 0 and bit 0 is 1) can be a direct interface pointer if FList is nil. FList: PItem; // Head of the linked list for additional receivers beyond the first one. class function AllocItem: PItem; static; inline; // Allocates and initializes memory for a new TItem. class procedure FreeItem( Item: PItem ); static; inline; // Frees memory previously allocated for a TItem. public procedure Create; // Initializes the notification list, preparing it for use. procedure Destroy; // Cleans up all resources, including unadvising all receivers. Assumes no concurrent access. function Advise(const Receiver: T): TTag; // Registers a receiver interface and returns an opaque tag for later unsubscription. procedure Unadvise(Tag: TTag); // Unregisters a specific receiver using the tag obtained from Advise. procedure UnadviseAll; // Unregisters all currently advised receivers. procedure Finalize; // Clears all receivers and permanently prevents new interfaces from being added. procedure Lock; inline; // Acquires an exclusive lock for thread-safe operations on the list. procedure Release; inline;// Releases the previously acquired exclusive lock. function IsLocked: Boolean; inline; // Checks if the list is currently locked by any thread. function IsFinalized: Boolean; inline; // Checks if the list has been finalized and no longer accepts new receivers. procedure Notify(Func: TPredicate); // Iterates through registered receivers and invokes the predicate; removes receiver if predicate returns false. end; implementation procedure TMycNotifyList.Create; begin FFirst := 1; FList := nil; end; procedure TMycNotifyList.Destroy; begin // Because refcounting is thread-safe, this will always be entered once after all references // to Self are dropped. No locking needed! Assert( not IsLocked ); FFirst := FFirst and not 3; UnadviseAll; end; function TMycNotifyList.Advise(const Receiver: T): TTag; var Item: PItem; begin Assert( IsLocked ); if IsFinalized then exit( 0 ); if FFirst=0 then begin IInterface( FFirst ) := Receiver; exit( PPointer(@Receiver)^ ); end; Item := AllocItem; Item.Receiver := Receiver; Item.Prev := nil; Item.Next := FList; if Item.Next<>nil then Item.Next.Prev := Item; FList := Item; exit( Item ); end; procedure TMycNotifyList.Lock; begin repeat if FFirst and 1 = 0 then begin YieldProcessor; continue; end; until TInterlocked.BitTestAndClear( PNativeUint( @FFirst )^, 0 ); Assert( IsLocked, 'Locking failed' ); end; class function TMycNotifyList.AllocItem: PItem; begin Result := AllocMem( sizeof( TItem ) ); end; procedure TMycNotifyList.Finalize; begin Assert( IsLocked ); UnadviseAll; FFirst := FFirst or 2; end; class procedure TMycNotifyList.FreeItem(Item: PItem); begin FreeMem( Item, sizeof( TItem ) ); end; function TMycNotifyList.IsLocked: Boolean; begin Result := FFirst and 1 = 0; end; function TMycNotifyList.IsFinalized: Boolean; begin Result := FFirst and 2 <> 0; end; procedure TMycNotifyList.Notify(Func: TPredicate); var Item, P: PItem; begin Assert( IsLocked ); if IsFinalized then exit; if FFirst<>0 then if not Func( IInterface( FFirst ) ) then IInterface( FFirst ) := nil; Item := FList; while Item<>nil do begin P := Item.Next; if not Func( Item.Receiver ) then Unadvise( TTag( Item ) ); Item := P; end; end; procedure TMycNotifyList.Release; begin Assert( IsLocked ); TInterlocked.Exchange( Pointer( FFirst ), Pointer( FFirst or 1 ) ); end; procedure TMycNotifyList.Unadvise(Tag: TTag); var Item: PItem; begin Assert( IsLocked ); if IsFinalized then exit; if NativeUInt(Tag) = FFirst then begin IInterface( FFirst ) := nil; exit; end; Item := PItem( Tag ); if Item = FList then FList := Item.Next; if Item.Prev<>nil then Item.Prev.Next := Item.Next; if Item.Next<>nil then Item.Next.Prev := Item.Prev; Item.Receiver := nil; FreeItem( Item ); end; procedure TMycNotifyList.UnadviseAll; begin Assert( IsLocked ); if IsFinalized then exit; IInterface( FFirst ) := nil; while FList<>nil do Unadvise( TTag( FList ) ); end; end.