github.com/remobjects/goldbaselibrary@v0.0.0-20230924164425-d458680a936b/Source/Gold/Gold.Channels.Echoes.pas (about)

     1  namespace go.builtin;
     2  {$IFDEF ECHOES}
     3  uses
     4    System.Threading.Tasks,
     5    System.Collections.Generic,
     6    System.Linq;
     7  {$ENDIF}
     8  
     9  type
    10    Message<T> = class(IWaitSendMessage)
    11    assembly
    12      fOwner: BidirectionalChannel<T>;
    13      fNotifier: Func<Func<Boolean>, Boolean>;
    14    public
    15      constructor(aVal: T; aOwner: BidirectionalChannel<T>);
    16      begin
    17        Data := aVal;
    18        fOwner := aOwner;
    19      end;
    20  
    21      property Data: T; readonly;
    22  
    23      method IntReceive(aReceiver: IWaitReceiveMessage<T>): Boolean;
    24      begin
    25        // If we can can't remove it, it's not valid because someone else already took it.
    26        if fOwner.RemoveItem(self, -> aReceiver.TryHandOff(Data)) then begin
    27          exit true;
    28        end;
    29        exit false;
    30      end;
    31  
    32      method Receive(aReceiver: IWaitReceiveMessage<T>): Boolean;
    33      begin
    34        var lNot := fNotifier;
    35        if lNot = nil then exit IntReceive(aReceiver);
    36        exit lNot(-> IntReceive(aReceiver));
    37      end;
    38  
    39      method Cancel;
    40      begin
    41        fOwner.RemoveItem(self, -> true);
    42      end;
    43  
    44      method Start(aNotifier: Func<Func<Boolean>, Boolean>): Boolean;
    45      begin
    46        fNotifier := aNotifier;
    47        exit fOwner.AddItem(self);
    48      end;
    49    end;
    50  
    51    ReceiveMessage<T> = class(IWaitReceiveMessage<T>)
    52    assembly
    53      fOwner: BidirectionalChannel<T>;
    54      fNotifier: Func<Func<Boolean>, Boolean>;
    55      fHaveValue: Boolean;
    56      fReceivedValue: T;
    57    public
    58      constructor(aOwner: BidirectionalChannel<T>);
    59      begin
    60        fOwner := aOwner;
    61      end;
    62  
    63      property Data: tuple of (T, Boolean) read (fReceivedValue, fHaveValue);
    64  
    65      method Cancel;
    66      begin
    67        fOwner.RemoveItem(self, -> begin end);
    68      end;
    69  
    70      method IntTryHandOff(aVal: T): Boolean;
    71      begin
    72        exit fOwner.RemoveItem(self, -> begin
    73          fHaveValue := true;
    74          fReceivedValue := aVal;
    75        end);
    76      end;
    77  
    78      method TryHandOff(aVal: T): Boolean;
    79      begin
    80        var lNot := fNotifier;
    81        if lNot = nil then
    82          exit IntTryHandOff(aVal);
    83        exit lNot(-> IntTryHandOff(aVal));
    84      end;
    85  
    86      method Start(aNotifier: Func<Func<Boolean>,Boolean>): Boolean;
    87      begin
    88        fNotifier := aNotifier;
    89        exit fOwner.AddItem(self);
    90      end;
    91    end;
    92  
    93    [ValueTypeSemantics]
    94    BidirectionalChannel<T> = public class(SendingChannel<T>, ReceivingChannel<T>, IChannel)
    95    assembly
    96    {$IFDEF ISLAND}
    97      fLock: Monitor := new Monitor;
    98      fCS: ConditionalVariable := new ConditionalVariable;
    99    {$ELSE}
   100      fLock: Object := new Object;
   101    {$ENDIF}
   102      fClosed: Integer; volatile;
   103      fQueueSize: Integer;
   104      fData: List<Message<T>> := new List<Message<T>>;
   105      fWaiting: List<ReceiveMessage<T>> := new List<ReceiveMessage<T>>();
   106  
   107      method AddItem(aVal: ReceiveMessage<T>): Boolean;
   108      begin
   109        if fClosed <> 0 then raise new Exception('Channel closed!');
   110        locking fLock do begin
   111          fWaiting.Add(aVal);
   112          for i: Integer := 0 to fData.Count -1 do begin
   113            if fData[i].Receive(aVal) then begin // will remove inside the handoff.
   114              exit true;
   115            end;
   116          end;
   117          {$IFDEF ISLAND}
   118          fCS.Signal;
   119          {$ELSE}
   120          System.Threading.Monitor.Pulse(fLock);
   121          {$ENDIF}
   122          exit false;
   123        end;
   124      end;
   125  
   126      method RemoveItem(aVal: ReceiveMessage<T>; aNot: Action): Boolean;
   127      begin
   128        locking fLock do begin
   129          if not fWaiting.Contains(aVal) then exit false;
   130          aNot:Invoke();
   131          fWaiting.Remove(aVal);
   132          exit true;
   133        end;
   134      end;
   135  
   136  
   137      method AddItem(aVal: Message<T>): Boolean;
   138      begin
   139        if fClosed <> 0 then raise new Exception('Channel closed!');
   140        locking fLock do begin
   141           fData.Add(aVal);
   142          for i: Integer := 0 to fWaiting.Count -1 do begin
   143            if aVal.Receive(fWaiting[i]) then
   144              exit true;
   145          end;
   146          if fData.Count < fQueueSize then begin
   147            aVal.fNotifier:Invoke(-> begin end);
   148            exit true;
   149          end;
   150        end;
   151        exit false;
   152      end;
   153  
   154      method RemoveItem(aVal: Message<T>; aNotifier: Func<Boolean>): Boolean;
   155      begin
   156        locking fLock do begin
   157          if not fData.Contains(aVal) then exit false;
   158  
   159          if not aNotifier() then exit false;
   160          fData.Remove(aVal);
   161  
   162          for i: Integer := Math.Min(fData.Count, fQueueSize) -1 downto 0 do begin
   163            fData[i].fNotifier:Invoke(-> begin end);
   164            fData[i].fNotifier := nil;
   165          end;
   166  
   167          exit true;
   168        end;
   169      end;
   170  
   171    public
   172      constructor(aQueueSize: Integer := 0);
   173      begin
   174        fQueueSize := aQueueSize + 1;
   175      end;
   176  
   177      property Length: Integer read fData.Count;
   178      property Capacity: Integer read fQueueSize -1;
   179  
   180      class var fZero: BidirectionalChannel<T> := new BidirectionalChannel<T>();
   181      class property Zero: BidirectionalChannel<T> := fZero; published;
   182  
   183      class operator IsNil(aVal: BidirectionalChannel<T>): Boolean;
   184      begin
   185        result := (Object(aVal) = nil) or (Object(aVal) = Object(fZero));
   186      end;
   187  
   188      method __Clone: BidirectionalChannel<T>;
   189      begin
   190        exit self;
   191      end;
   192  
   193      method Send(aVal: T);
   194      begin
   195        var lSig := TrySend(aVal);
   196        {$IFDEF ISLAND}
   197        var lLock := new Monitor;
   198        var lCV := new ConditionalVariable;
   199        {$ELSE}
   200        var lLock := new Object;
   201        {$ENDIF}
   202        var lDone := false;
   203        if lSig.Start(a-> begin
   204          locking lLock do begin
   205            if not a() then exit false;
   206            lDone := true;
   207            {$IFDEF ISLAND}
   208            lCV.Signal;
   209            {$ELSE}
   210            System.Threading.Monitor.Pulse(lLock);
   211            {$ENDIF}
   212            exit true;
   213          end;
   214        end) then exit;
   215        loop begin
   216          if lDone then break;
   217          if fLock = nil then raise new Exception('Channel closed!');
   218          locking lLock do begin
   219            {$IFDEF ISLAND}
   220            lCV.Wait(lLock);
   221            {$ELSE}
   222            System.Threading.Monitor.Wait(lLock);
   223            {$ENDIF}
   224          end;
   225        end;
   226      end;
   227  
   228  
   229      method Receive: tuple of (T, Boolean);
   230      begin
   231        {$IFDEF ISLAND}
   232        var lLock := new Monitor;
   233        var lCV := new ConditionalVariable;
   234        {$ELSE}
   235        var lLock := new Object;
   236        {$ENDIF}
   237        var lDone := false;
   238        var lRes := TryReceive();
   239        lRes.Start(a -> begin
   240          locking lLock do begin
   241            if not a() then exit false;
   242            lDone := true;
   243            {$IFDEF ISLAND}
   244            lCV.Signal;
   245            {$ELSE}
   246            System.Threading.Monitor.Pulse(lLock);
   247            {$ENDIF}
   248            exit true;
   249          end;
   250        end);
   251  
   252        loop begin
   253          if lDone then break;
   254          if fClosed <> 0 then exit (nil, false);
   255          locking lLock do begin
   256            {$IFDEF ISLAND}
   257            lCV.Wait(lLock);
   258            {$ELSE}
   259            System.Threading.Monitor.Wait(lLock);
   260            {$ENDIF}
   261          end;
   262        end;
   263  
   264        exit lRes.Data;
   265      end;
   266  
   267  
   268      method TryReceive: IWaitReceiveMessage<T>;
   269      begin
   270        if (fClosed <> 0) or (self = fZero) then exit nil;
   271        exit new ReceiveMessage<T>(self);
   272      end;
   273  
   274      method TrySend(aVal: T): IWaitSendMessage;
   275      begin
   276        if fClosed <> 0 then raise new Exception('Channel closed!');
   277        exit new Message<T>(aVal, self);
   278      end;
   279  
   280      method GetSequence: sequence of T; iterator;
   281      begin
   282        var lData: T;
   283        var lReceiver := new class IWaitReceiveMessage<T>(
   284          Cancel := method begin end,
   285          get_Data := -> (lData, true),
   286          Start := a -> begin end,
   287          TryHandOff := a -> begin lData := a; exit true; end
   288        );
   289        loop begin
   290          locking fLock do begin
   291            if fData.Count = 0 then begin
   292              if fClosed <> 0 then exit;
   293              {$IFDEF ISLAND}
   294              fCS.Wait(fLock);
   295              {$ELSE}
   296              System.Threading.Monitor.Wait(fLock);
   297              {$ENDIF}
   298              end;
   299            for i: Integer := 0 to fData.Count -1 do begin
   300              if fData[0].Receive(lReceiver) then
   301                yield lData;
   302            end;
   303          end;
   304        end;
   305      end;
   306  
   307      method Close;
   308      begin
   309        var lClosed := fClosed;
   310        fClosed := 1;
   311        if lClosed <> 0 then exit;
   312        locking fLock do begin
   313          for i: Integer := 0 to fData.Count -1 do
   314            fData[i].fNotifier:Invoke(-> true);
   315          for i: Integer := 0 to fWaiting.Count -1 do
   316            fWaiting[i].fNotifier:Invoke(-> true);
   317        end;
   318      end;
   319    end;
   320  
   321    RandNumber = static class
   322      class var fRandom: go.crypto.rand.PlatformRandom;
   323  
   324      method Random(aMax: Cardinal): Cardinal;
   325      begin
   326        {$IF ISLAND}
   327        exit fRandom.Random mod aMax;
   328        {$ELSEIF ECHOES}
   329        exit fRandom.Next(aMax);
   330        {$ENDIF}
   331      end;
   332  
   333      class constructor;
   334      begin
   335        fRandom := new go.crypto.rand.PlatformRandom();
   336      end;
   337    end;
   338  
   339    method Channel_Select(aHandles: array of IWaitMessage; aBlock: Boolean): Integer; public;
   340    begin
   341      {$IFDEF ISLAND}
   342      var lLock := new Monitor;
   343      var lWake := new ConditionalVariable;
   344      {$ELSE}
   345      var lLock := new Object;
   346      {$ENDIF}
   347      var lDone := -1;
   348      locking lLock do begin
   349        for i: Integer := 0 to aHandles.Count -1 do begin
   350          var ci := i;
   351          if (aHandles[i] <> nil) and aHandles[i].Start(a -> begin
   352            locking lLock do begin
   353              if lDone <> -1 then exit false;
   354              if not a() then exit false;
   355              lDone := ci;
   356              {$IFDEF ISLAND}
   357              lWake.Signal;
   358              {$ELSE}
   359              System.Threading.Monitor.Pulse(lLock);
   360              {$ENDIF}
   361  
   362              exit true;
   363            end;
   364          end) then begin
   365            // start returns true if it's already done, if so, we can return that one and cancel the previous ones.
   366            for j: Integer := 0 to i -1 do
   367              if aHandles[j] <> nil then
   368                aHandles[j].Cancel;
   369            exit i;
   370          end;
   371        end;
   372      end;
   373      if not aBlock then
   374        exit -1;
   375      // if all channels are nil (reading closed channels), choose one using random number (Go way)
   376      var lAnyChannel := false;
   377      for x: Integer := 0 to aHandles.Length - 1 do begin
   378        if aHandles[x] ≠ nil then begin
   379          lAnyChannel := true;
   380          break;
   381        end;
   382      end;
   383      if not lAnyChannel then
   384        exit RandNumber.Random(aHandles.Length);
   385      loop begin
   386        locking lLock do begin
   387          if lDone <> -1 then break;
   388          {$IFDEF ISLAND}
   389          lWake.Wait(lLock);
   390          {$ELSE}
   391          System.Threading.Monitor.Wait(lLock);
   392          {$ENDIF}
   393        end;
   394      end;
   395      for j: Integer := 0 to aHandles.Count -1 do
   396        if (aHandles[j] <> nil) and (j <> lDone) then
   397          aHandles[j].Cancel;
   398      exit lDone;
   399    end;
   400  
   401  
   402  end.