Notify list refactoring
This commit is contained in:
+141
-101
@@ -19,8 +19,15 @@ type
|
||||
PItem = ^TItem;
|
||||
// Internal structure for storing a receiver and list linkage.
|
||||
TItem = record
|
||||
Next, Prev: PItem; // Pointers to the next and previous items in the doubly linked list.
|
||||
private
|
||||
FNext: PItem;
|
||||
FPrev: PItem;
|
||||
function GetNext: PItem; inline;
|
||||
public
|
||||
|
||||
Receiver: T; // The registered interface instance (the event sink).
|
||||
property Next: PItem read GetNext;
|
||||
property Prev: PItem read FPrev;
|
||||
end;
|
||||
|
||||
// Notify function. If this results false, it will be removed from the notify list
|
||||
@@ -31,6 +38,9 @@ type
|
||||
// Bit 0 of the address stores the lock state (0 = locked, 1 = unlocked).
|
||||
[volatile]
|
||||
FList: PItem;
|
||||
class var
|
||||
FReverseOnNotify: Boolean;
|
||||
class constructor CreateClass;
|
||||
|
||||
// Allocates memory for a new TItem.
|
||||
class function AllocItem: PItem; static; inline;
|
||||
@@ -62,10 +72,18 @@ type
|
||||
// by setting its interface reference to nil. The list item itself is not freed here.
|
||||
procedure Notify(const Func: TNotifyProc);
|
||||
property First: PItem read GetFirst;
|
||||
|
||||
// If this is true, the list is reversed after each Notify, hoping for a better distrubution of events.
|
||||
class property ReverseOnNotify: Boolean read FReverseOnNotify write FReverseOnNotify;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
class constructor TMycNotifyList<T>.CreateClass;
|
||||
begin
|
||||
FReverseOnNotify := true;
|
||||
end;
|
||||
|
||||
class operator TMycNotifyList<T>.Initialize(out Dest: TMycNotifyList<T>);
|
||||
begin
|
||||
NativeUInt(Dest.FList) := 1;
|
||||
@@ -88,10 +106,10 @@ begin
|
||||
|
||||
Item := AllocItem;
|
||||
Item.Receiver := Receiver;
|
||||
Item.Prev := nil;
|
||||
Item.Next := FList;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item;
|
||||
Item.FPrev := nil;
|
||||
Item.FNext := FList;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item;
|
||||
|
||||
FList := Item;
|
||||
|
||||
@@ -133,108 +151,123 @@ var
|
||||
begin
|
||||
Assert(IsLocked);
|
||||
|
||||
var rev: PItem := nil;
|
||||
var tmp: PItem := nil;
|
||||
// Invariant: Append all detached items at end of the list. We can't simply free them, because the subscription is owned by the client.
|
||||
|
||||
while (FList <> nil) and Assigned(FList.Receiver) do
|
||||
if FReverseOnNotify then
|
||||
begin
|
||||
Item := FList;
|
||||
FList := Item.Next;
|
||||
// Method: stack assigned items and rebuild the list from the stack (reversing the order)
|
||||
|
||||
if Func(Item.Receiver) then
|
||||
begin
|
||||
Item.Next := rev;
|
||||
rev := Item;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// release the receiver
|
||||
Item.Receiver := nil;
|
||||
var stack: PItem := nil;
|
||||
var released: PItem := nil;
|
||||
|
||||
Item.Next := tmp;
|
||||
tmp := Item;
|
||||
while (FList <> nil) and Assigned(FList.Receiver) do
|
||||
begin
|
||||
Item := FList;
|
||||
FList := Item.FNext;
|
||||
|
||||
if Func(Item.Receiver) then
|
||||
begin
|
||||
// item stays valid
|
||||
Item.FNext := stack;
|
||||
stack := Item;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// release the receiver
|
||||
Item.Receiver := nil;
|
||||
|
||||
Item.FNext := released;
|
||||
released := Item;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
while tmp <> nil do
|
||||
begin
|
||||
Item := tmp;
|
||||
tmp := Item.Next;
|
||||
|
||||
Item.Prev := nil;
|
||||
Item.Next := FList;
|
||||
FList := Item;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item;
|
||||
end;
|
||||
|
||||
while rev <> nil do
|
||||
begin
|
||||
Item := rev;
|
||||
rev := Item.Next;
|
||||
|
||||
Item.Prev := nil;
|
||||
Item.Next := FList;
|
||||
FList := Item;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item;
|
||||
end;
|
||||
{
|
||||
tmp := nil;
|
||||
Last := nil;
|
||||
Item := FList;
|
||||
while (Item <> nil) and Assigned(Item.Receiver) do
|
||||
begin
|
||||
if not Func(Item.Receiver) then
|
||||
// The list now only contains old released items. Push the newly released ones.
|
||||
while released <> nil do
|
||||
begin
|
||||
// Receiver wants no more notifications, detach it
|
||||
if Item = FList then
|
||||
FList := Item.Next;
|
||||
Item := released;
|
||||
released := Item.FNext;
|
||||
|
||||
if Item.Prev <> nil then
|
||||
Item.Prev.Next := Item.Next;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item.Prev;
|
||||
|
||||
// release the receiver
|
||||
Item.Receiver := nil;
|
||||
|
||||
// and save the list item in a tmp list for later use
|
||||
var nxt := Item.Next;
|
||||
Item.Next := tmp;
|
||||
tmp := Item;
|
||||
Item := nxt;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Last := Item;
|
||||
Item := Last.Next;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Append all detached items at end of the list. We can't simply free them, because the subscription is owned by the client.
|
||||
while tmp <> nil do
|
||||
begin
|
||||
Item := tmp;
|
||||
tmp := Item.Next;
|
||||
|
||||
if Last = nil then
|
||||
begin
|
||||
Item.Prev := nil;
|
||||
Item.Next := FList;
|
||||
Item.FPrev := nil;
|
||||
Item.FNext := FList;
|
||||
FList := Item;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Item.Prev := Last;
|
||||
Item.Next := Last.Next;
|
||||
if Item.Prev <> nil then
|
||||
Item.Prev.Next := Item;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item;
|
||||
end;
|
||||
|
||||
// Now push the valid items, so that they are at the beginning of the list.
|
||||
while stack <> nil do
|
||||
begin
|
||||
Item := stack;
|
||||
stack := Item.FNext;
|
||||
|
||||
Item.FPrev := nil;
|
||||
Item.FNext := FList;
|
||||
FList := Item;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Method: filter released items and add then to the end of the list
|
||||
|
||||
var released: PItem := nil;
|
||||
var lastValid: PItem := nil;
|
||||
|
||||
Item := FList;
|
||||
while (Item <> nil) and Assigned(Item.Receiver) do
|
||||
begin
|
||||
if not Func(Item.Receiver) then
|
||||
begin
|
||||
// item is now invalid
|
||||
if Item = FList then
|
||||
FList := Item.FNext;
|
||||
|
||||
if Item.FPrev <> nil then
|
||||
Item.FPrev.FNext := Item.FNext;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item.FPrev;
|
||||
|
||||
// release the receiver
|
||||
Item.Receiver := nil;
|
||||
|
||||
// and save the list item in a tmp list for later use
|
||||
var nxt := Item.FNext;
|
||||
Item.FNext := released;
|
||||
released := Item;
|
||||
Item := nxt;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// save a pointer to the last valid item
|
||||
lastValid := Item;
|
||||
Item := lastValid.FNext;
|
||||
end;
|
||||
end;
|
||||
|
||||
// insert all new released items after the last valid item
|
||||
while released <> nil do
|
||||
begin
|
||||
Item := released;
|
||||
released := Item.FNext;
|
||||
|
||||
if lastValid = nil then
|
||||
begin
|
||||
Item.FPrev := nil;
|
||||
Item.FNext := FList;
|
||||
FList := Item;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Item.FPrev := lastValid;
|
||||
Item.FNext := lastValid.Next;
|
||||
if Item.FPrev <> nil then
|
||||
Item.FPrev.FNext := Item;
|
||||
end;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item;
|
||||
end;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item;
|
||||
end;
|
||||
}
|
||||
end;
|
||||
|
||||
procedure TMycNotifyList<T>.Release;
|
||||
@@ -270,12 +303,12 @@ begin
|
||||
Item.Receiver := nil;
|
||||
|
||||
if Item = FList then
|
||||
FList := Item.Next;
|
||||
FList := Item.FNext;
|
||||
|
||||
if Item.Prev <> nil then
|
||||
Item.Prev.Next := Item.Next;
|
||||
if Item.Next <> nil then
|
||||
Item.Next.Prev := Item.Prev;
|
||||
if Item.FPrev <> nil then
|
||||
Item.FPrev.FNext := Item.FNext;
|
||||
if Item.FNext <> nil then
|
||||
Item.FNext.FPrev := Item.FPrev;
|
||||
|
||||
FreeItem(Item);
|
||||
end;
|
||||
@@ -287,4 +320,11 @@ begin
|
||||
Unadvise(TTag(FList));
|
||||
end;
|
||||
|
||||
function TMycNotifyList<T>.TItem.GetNext: PItem;
|
||||
begin
|
||||
if not Assigned(Receiver) then
|
||||
exit(nil);
|
||||
Result := FNext;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user