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.