runewalsh

Биндинги.

Jan 25th, 2020
446
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 14.02 KB | None | 0 0
  1. {$ifdef description}
  2.    БИНДИНГ (Binding) — управляемая обёртка над BindingInstance —
  3.    это штука, которую можно добавить в МЕНЕДЖЕР БИНДИНГОВ (BindingManager).
  4.  
  5.    BindingManager.ForEach обходит все биндинги в порядке добавления,
  6.    а деструктор биндинга автоматически удаляет его из менеджера, в который он добавлен.
  7.  
  8.    Это удобно для вещей, которые можно куда-то добавлять и (обычно автоматически) удалять.
  9.    Например, для цепочки обработчиков нажатия клавиши Z:
  10.        private
  11.            onPressZ: BindingManager;
  12.        public
  13.            type PressZCallback = function(...);
  14.            function BindPressZ(func: PressZCallback): PressZBinding;
  15.            procedure CallPressZ;
  16.  
  17.    BindPressZ регистрирует биндинг и возвращает пользователю управляемую ссылку на него:
  18.        binding := PressZBindingInstance.Create(func);
  19.        onPressZ.Add(binding);
  20.        result := binding;
  21.  
  22.    CallPressZ вызывает все зарегистрированные обработчики:
  23.        onPressZ.ForEach(b -> (b as PressZBindingInstance).func());
  24.  
  25.    Когда пользователь прекратит удерживать binding, он автоматически удалится из onPressZ.
  26.    В частности, если функция регистрируется в пределах жизни некоторого объекта — такого как окно, реагирующее на клавишу Z,
  27.    биндинг естественно сохранить как поле этого объекта, чтобы регистрация автоматически отзывалась с закрытием окна.
  28.  
  29.    Полностью поддерживаются добавления и удаления в ходе ForEach.
  30.    В нашем случае это означает, что обработчик Z свободен в установке и удалении других обработчиков Z, в частности, может разрегистрировать сам себя.
  31.  
  32.    Семантика вложенных операций:
  33.    — Удаления биндингов видны для ForEach, в котором произошли, т. е. он не будет проходить элементы, удалённые в процессе собственной работы.
  34.      Если хранятся 'ABCDE' и в момент прохода по 'B' удаляется 'D', то по окончании ForEach окажутся пройденными 'ABСE'.
  35.  
  36.    — Добавления, наоборот, не видны ForEach-у, в котором произошли.
  37.      Если хранятся 'ABCDE' и ForEach добавляет по 1 элементу: на 'A''a', ..., 'E''e', то будут пройдены 'ABCDE', а полный список станет 'ABCDEabcde'.
  38.  
  39.    — Но добавления видны вложенным ForEach, причём по состоянию на момент их начала.
  40.      Если хранятся 'ABCDE', ForEach добавляет на 'A''a', ..., 'E''e', и на 'C' вызывается вложенный ForEach2,
  41.      то ForEach2 пройдёт 'ABCDEabc', а внешний ForEach так и завершится на 'ABCDE'.
  42.  
  43.    В будущем может понадобиться явное управление порядком посредством приоритетов,
  44.    но тогда вместо простого связного списка придётся использовать красно-чёрное дерево. :)
  45. {$endif}
  46.  
  47. {$include opts.inc}
  48. unit Framework.System.Bindings;
  49.  
  50. {$include non_copyable.inc} {$include enum_shortcuts.inc} {$include logging.inc}
  51.  
  52. interface
  53.  
  54. uses
  55.     Framework.System.Common, Framework.System.Threads;
  56.  
  57. type
  58.     pBindingManager = ^BindingManager;
  59.     BindingInstance = class;
  60.  
  61.     Binding = record
  62.     {$define typ := Binding} {$define instance := BindingInstance} {$include shared_ref.h.inc}
  63.     end;
  64.  
  65.     BindingInstance = class(Refcounted)
  66.         destructor Destroy; override;
  67.     protected
  68.         procedure Teardown; virtual;
  69.     private type
  70.         pNode = ^Node;
  71.         Node = record
  72.             man: pBindingManager;
  73.             b: BindingInstance;
  74.             prev, next, nextRemoved: pNode;
  75.         end;
  76.     const
  77.         TEARING_DOWN = pNode(1);
  78.     var
  79.         n: pNode;
  80.     end;
  81.  
  82.     BindingManager = record
  83.         {$define enum := SelfSufficientLockDescEnum} {$define items := NoLock _ OwnLock} enum_shortcuts
  84.         {$define enum := TraversalDirection} {$define items := Straight _ Reversed} enum_shortcuts
  85.     type
  86.         LockDesc = record
  87.         {$define enum := ModeEnum} {$define items := DontUse _ External _ Own _ Dead} enum_shortcuts
  88.             class operator :=(mode: SelfSufficientLockDescEnum): LockDesc;
  89.             class operator :=(const lock: ThreadLockReference): LockDesc;
  90.         private
  91.             mode: ModeEnum;
  92.             lock: ThreadLockReference;
  93.         end;
  94.  
  95.         BindingProc = procedure(b: BindingInstance; param: pointer);
  96.         BreakableBindingProc = function(b: BindingInstance; param: pointer): boolean;
  97.  
  98.         procedure Init(const lock: LockDesc; const name: string);
  99.         function OK: boolean;
  100.         procedure Add(const b: Binding);
  101.         procedure Add(b: BindingInstance);
  102.         procedure ForEach(proc: BindingProc; param: pointer; direction: TraversalDirection = Straight);
  103.         procedure ForEach(proc: BreakableBindingProc; param: pointer; direction: TraversalDirection = Straight);
  104.     private type
  105.         pNode = BindingInstance.pNode;
  106.         // Node = BindingInstance.Node;
  107.  
  108.         AdaptBindingProcAsBreakableContext = record
  109.             proc, param: pointer;
  110.         end;
  111.     const
  112.         // используется вместо nil как маркер конца списка removedHead, чтобы можно было проверить, удалён ли узел, через Assigned(nextRemoved).
  113.         LAST_REMOVED = pNode(1);
  114.     var
  115.         first, last, removedHead: pNode;
  116.         _lock: LockDesc;
  117.         busy: uint;
  118.         name: string;
  119.         function CreateNode: pNode;
  120.         procedure DestroyNode(n: pNode);
  121.         procedure Remove(n: pNode);
  122.         procedure RemoveRightAway(n: pNode);
  123.         procedure PostponeRemove(n: pNode);
  124.         procedure SweepPostponedRemoves;
  125.         procedure Lock;
  126.         procedure Unlock;
  127.         function Mine: boolean;
  128.         procedure LockOwn(const what: string);
  129.         procedure UnlockOwn;
  130.         class function AdaptBindingProcAsBreakable(b: BindingInstance; param: pointer): boolean; static;
  131.     {$define typ := BindingManager} {$define initfinal} non_copyable
  132.     end;
  133.  
  134. implementation
  135.  
  136. uses
  137.     Framework.System;
  138.  
  139.     {$define typ := Binding} {$include shared_ref.pp.inc}
  140.  
  141.     destructor BindingInstance.Destroy;
  142.     var
  143.         savedN: pNode;
  144.     begin
  145.         if Assigned(n) then
  146.         begin
  147.             Assert(n <> TEARING_DOWN, 'Binding.Destroy повторилась рекурсивно');
  148.             savedN := n;
  149.             n := TEARING_DOWN;
  150.             savedN^.man^.Remove(savedN);
  151.             Teardown;
  152.         end;
  153.         inherited Destroy;
  154.     end;
  155.  
  156.     procedure BindingInstance.Teardown;
  157.     begin
  158.     end;
  159.  
  160.     class operator BindingManager.LockDesc.:=(mode: SelfSufficientLockDescEnum): LockDesc;
  161.     begin
  162.         case mode of
  163.             NoLock: result.mode := DontUse;
  164.             OwnLock: result.mode := Own;
  165.         end;
  166.     end;
  167.  
  168.     class operator BindingManager.LockDesc.:=(const lock: ThreadLockReference): LockDesc;
  169.     begin
  170.         result.mode := External;
  171.         result.lock := lock;
  172.     end;
  173.  
  174.     procedure BindingManager.Init(const lock: LockDesc; const name: string);
  175.     begin
  176.         if OK then ThrowOpenedOver('BindingManager', name, self.name);
  177.         _lock := lock;
  178.         if _lock.mode = lock.Own then _lock.lock := RecursiveThreadLock.Create^;
  179.         self.name := name;
  180.     end;
  181.  
  182.     function BindingManager.OK: boolean;
  183.     begin
  184.         result := self._lock.mode <> LockDesc.Dead;
  185.     end;
  186.  
  187.     procedure BindingManager.Add(const b: Binding);
  188.     begin
  189.         Add(b.p);
  190.     end;
  191.  
  192.     procedure BindingManager.Add(b: BindingInstance);
  193.     var
  194.         n: pNode;
  195.     begin
  196.         Assert(OK);
  197.         if Assigned(b.n) then raise Error('{} уже привязана.', b.ToString);
  198.         LockOwn('Add');
  199.         try
  200.             // Add работает безотносительно busy.
  201.             n := CreateNode;
  202.             n^.b := b;
  203.             n^.prev := last;
  204.             n^.next := nil;
  205.             n^.nextRemoved := nil;
  206.             if Assigned(last) then last^.next := n else first := n;
  207.             last := n;
  208.             b.n := n;
  209.         finally
  210.             UnlockOwn;
  211.         end;
  212.     end;
  213.  
  214.     procedure BindingManager.ForEach(proc: BindingProc; param: pointer; direction: TraversalDirection = Straight);
  215.     var
  216.         ctx: AdaptBindingProcAsBreakableContext;
  217.     begin
  218.         ctx.proc := proc;
  219.         ctx.param := param;
  220.         ForEach(@AdaptBindingProcAsBreakable, @ctx, direction);
  221.     end;
  222.  
  223.     procedure BindingManager.ForEach(proc: BreakableBindingProc; param: pointer; direction: TraversalDirection = Straight);
  224.     var
  225.         cur, lastVisible: pNode;
  226.     begin
  227.         Assert(OK);
  228.         LockOwn('ForEach');
  229.         inc(busy);
  230.         try
  231.             if direction = Straight then
  232.             begin
  233.                 cur := first;
  234.                 if not Assigned(cur) then exit;
  235.                 lastVisible := last;
  236.  
  237.                 // Хитрое условие, чтобы записать всё в одну строчку.
  238.                 // Если Assigned(cur^.nextRemoved) — элемент отложенно удалён, и proc вызывать не нужно.
  239.                 // Иначе вызывается proc. Если она вернула ложь, т. е. явно попросила прерваться — цикл прерывается.
  240.                 // Наконец, если cur = lastVisible, то это был последний (по состоянию на начало ForEach) элемент в списке.
  241.                 // При busy удаления исполняются отложенно, и это гарантирует, что lastVisible никуда из списка не денется.
  242.                 while (Assigned(cur^.nextRemoved) or proc(cur^.b, param)) and (cur <> lastVisible) do
  243.                     cur := cur^.next;
  244.             end else
  245.             begin
  246.                 // Код обхода в обратном направлении полностью зеркалит код обхода в прямом на случай,
  247.                 // если будет добавлена поддержка добавления элементов в начало.
  248.                 cur := last;
  249.                 if not Assigned(cur) then exit;
  250.                 lastVisible := first;
  251.                 while (Assigned(cur^.nextRemoved) or proc(cur^.b, param)) and (cur <> lastVisible) do
  252.                     cur := cur^.prev;
  253.             end;
  254.         finally
  255.             dec(busy);
  256.             if busy = 0 then SweepPostponedRemoves;
  257.             UnlockOwn;
  258.         end;
  259.     end;
  260.  
  261.     function BindingManager.CreateNode: pNode;
  262.     begin
  263.         new(result);
  264.         result^.man := @self;
  265.     end;
  266.  
  267.     procedure BindingManager.DestroyNode(n: pNode);
  268.     begin
  269.         dispose(n);
  270.     end;
  271.  
  272.     procedure BindingManager.Remove(n: pNode);
  273.     begin
  274.         Lock;
  275.         try
  276.             if busy = 0 then RemoveRightAway(n) else PostponeRemove(n);
  277.         finally
  278.             Unlock;
  279.         end;
  280.     end;
  281.  
  282.     procedure BindingManager.RemoveRightAway(n: pNode);
  283.     var
  284.         prev, next: pNode;
  285.     begin
  286.         Assert(Mine);
  287.         Assert(n^.man = @self);
  288.         prev := n^.prev;
  289.         next := n^.next;
  290.  
  291.         if Assigned(prev) then prev^.next := next else begin Assert(n = first); first := next; end;
  292.         if Assigned(next) then next^.prev := prev else begin Assert(n = last); last := prev; end;
  293.         DestroyNode(n);
  294.     end;
  295.  
  296.     procedure BindingManager.PostponeRemove(n: pNode);
  297.     begin
  298.         Assert(Mine);
  299.         Assert(n^.man = @self);
  300.         Assert(not Assigned(n^.nextRemoved));
  301.         if Assigned(removedHead) then n^.nextRemoved := removedHead else n^.nextRemoved := LAST_REMOVED;
  302.         removedHead := n;
  303.     end;
  304.  
  305.     procedure BindingManager.SweepPostponedRemoves;
  306.     var
  307.         cur, nx: pNode;
  308.     begin
  309.         if Assigned(removedHead) then
  310.         begin
  311.             cur := removedHead;
  312.             repeat
  313.                 nx := cur^.nextRemoved;
  314.                 RemoveRightAway(cur);
  315.                 cur := nx;
  316.             until cur = LAST_REMOVED;
  317.             removedHead := nil;
  318.         end;
  319.     end;
  320.  
  321.     procedure BindingManager.Lock;
  322.     begin
  323.         if _lock.mode <> _lock.DontUse then _lock.lock.Enter;
  324.     end;
  325.  
  326.     procedure BindingManager.Unlock;
  327.     begin
  328.         if _lock.mode <> _lock.DontUse then _lock.lock.Leave;
  329.     end;
  330.  
  331.     function BindingManager.Mine: boolean;
  332.     begin
  333.         case _lock.mode of
  334.             _lock.Own, _lock.External: result := _lock.lock.Mine;
  335.             else result := yes;
  336.         end;
  337.     end;
  338.  
  339.     procedure BindingManager.LockOwn(const what: string);
  340.     begin
  341.         case _lock.mode of
  342.             _lock.Own: Lock;
  343.             _lock.External: Assert(_lock.lock.Mine, '{} должна выполняться под блокировкой'.Format(what));
  344.             else ;
  345.         end;
  346.     end;
  347.  
  348.     procedure BindingManager.UnlockOwn;
  349.     begin
  350.         case _lock.mode of
  351.             _lock.Own: Unlock;
  352.             else ;
  353.         end;
  354.     end;
  355.  
  356.     class function BindingManager.AdaptBindingProcAsBreakable(b: BindingInstance; param: pointer): boolean;
  357.     var
  358.         ctx: ^AdaptBindingProcAsBreakableContext absolute param;
  359.     begin
  360.         BindingProc(ctx^.proc)(b, ctx^.param);
  361.         result := yes;
  362.     end;
  363.  
  364.     class operator BindingManager.Initialize(var self: BindingManager);
  365.     begin
  366.         self.first := nil;
  367.         self.last := nil;
  368.         self.removedHead := nil;
  369.         self._lock.mode := LockDesc.Dead;
  370.         self.busy := 0;
  371.     end;
  372.  
  373.     class operator BindingManager.Finalize(var self: BindingManager);
  374.  
  375.         procedure EmergencyFreeDanglingNodes;
  376.         var
  377.             count: SizeUint;
  378.             cur, next: pNode;
  379.         begin
  380.             count := 0;
  381.             cur := self.first;
  382.             while Assigned(cur) do
  383.             begin
  384.                 inc(count);
  385.                 next := cur^.next;
  386.                 cur^.b.n := nil;
  387.                 self.DestroyNode(cur);
  388.                 cur := next;
  389.             end;
  390.             log_warning('В BindingManager({}) остал{1:ся/ись/ось} {} незакрытых биндинг{/а/ов}.'.Format(self.name, count.ToString));
  391.         end;
  392.  
  393.     begin
  394.         // По-хорошему этого не должно быть, но может происходить в искусственных примерах.
  395.         if Assigned(self.first) then EmergencyFreeDanglingNodes;
  396.         if (self._lock.Mode = LockDesc.Own) and Assigned(self._lock.lock.Plain) then dispose(self._lock.lock.Plain);
  397.         self._lock.Mode := LockDesc.Dead;
  398.     end;
  399.  
  400. {$define typ := BindingManager} non_copyable_impl
  401.  
  402. end.
Add Comment
Please, Sign In to add comment