1 /++ 2 The section of [kameloso.plugins.common] that deals with delaying executing 3 of [core.thread.fiber.Fiber|Fiber]s and delegates to a later point in time, 4 and registering such to await a specific type of [dialect.defs.IRCEvent|IRCEvent]. 5 6 This was all in one `plugins/common.d` file that just grew too big. 7 8 See_Also: 9 [kameloso.plugins.common.core], 10 [kameloso.plugins.common.mixins] 11 12 Copyright: [JR](https://github.com/zorael) 13 License: [Boost Software License 1.0](https://www.boost.org/users/license.html) 14 15 Authors: 16 [JR](https://github.com/zorael) 17 +/ 18 module kameloso.plugins.common.delayawait; 19 20 private: 21 22 import kameloso.plugins.common.core : IRCPlugin; 23 import dialect.defs; 24 import std.typecons : Flag, No, Yes; 25 import core.thread : Fiber; 26 import core.time : Duration; 27 28 public: 29 30 31 // delay 32 /++ 33 Queues a [core.thread.fiber.Fiber|Fiber] to be called at a point `duration` 34 later, by appending it to the `plugin`'s 35 [kameloso.plugins.common.core.IRCPluginState.scheduledFibers|IRCPluginState.scheduledFibers]. 36 37 Updates the 38 [kameloso.plugins.common.core.IRCPluginState.nextScheduledTimestamp|IRCPluginState.nextScheduledFibers] 39 timestamp so that the main loop knows when to next process the array of 40 [kameloso.thread.ScheduledFiber|ScheduledFiber]s. 41 42 Params: 43 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 44 fiber = [core.thread.fiber.Fiber|Fiber] to enqueue to be executed at a 45 later point in time. 46 duration = Amount of time to delay the `fiber`. 47 48 See_Also: 49 [removeDelayedFiber] 50 +/ 51 void delay(IRCPlugin plugin, Fiber fiber, const Duration duration) 52 in ((fiber !is null), "Tried to delay a null Fiber") 53 { 54 import kameloso.thread : ScheduledFiber; 55 import std.datetime.systime : Clock; 56 57 immutable time = Clock.currStdTime + duration.total!"hnsecs"; 58 plugin.state.scheduledFibers ~= ScheduledFiber(fiber, time); 59 plugin.state.updateSchedule(); 60 } 61 62 63 // delay 64 /++ 65 Queues a [core.thread.fiber.Fiber|Fiber] to be called at a point `duration` 66 later, by appending it to the `plugin`'s 67 [kameloso.plugins.common.core.IRCPluginState.scheduledFibers|IRCPluginState.scheduledFibers]. 68 Overload that implicitly queues [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 69 70 Params: 71 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 72 duration = Amount of time to delay the implicit fiber in the current context. 73 yield = Whether or not to immediately yield the Fiber. 74 75 See_Also: 76 [removeDelayedFiber] 77 +/ 78 void delay(IRCPlugin plugin, const Duration duration, const Flag!"yield" yield) 79 in (Fiber.getThis, "Tried to delay the current Fiber outside of a Fiber") 80 { 81 delay(plugin, Fiber.getThis, duration); 82 if (yield) Fiber.yield(); 83 } 84 85 86 // delay 87 /++ 88 Queues a `void delegate()` delegate to be called at a point `duration` 89 later, by appending it to the `plugin`'s 90 [kameloso.plugins.common.core.IRCPluginState.scheduledDelegates|IRCPluginState.scheduledDelegates]. 91 92 Updates the 93 [kameloso.plugins.common.core.IRCPluginState.nextScheduledTimestamp|IRCPluginState.nextScheduledFibers] 94 timestamp so that the main loop knows when to next process the array of 95 [kameloso.thread.ScheduledDelegate|ScheduledDelegate]s. 96 97 Params: 98 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 99 dg = Delegate to enqueue to be executed at a later point in time. 100 duration = Amount of time to delay the `fiber`. 101 102 See_Also: 103 [removeDelayedDelegate] 104 +/ 105 void delay(IRCPlugin plugin, void delegate() dg, const Duration duration) 106 in ((dg !is null), "Tried to delay a null delegate") 107 { 108 import kameloso.thread : ScheduledDelegate; 109 import std.datetime.systime : Clock; 110 111 immutable time = Clock.currStdTime + duration.total!"hnsecs"; 112 plugin.state.scheduledDelegates ~= ScheduledDelegate(dg, time); 113 plugin.state.updateSchedule(); 114 } 115 116 117 // removeDelayedFiber 118 /++ 119 Removes a [core.thread.fiber.Fiber|Fiber] from being called at any point later. 120 121 Updates the `nextScheduledTimestamp` UNIX timestamp (by way of 122 [kameloso.plugins.common.core.IRCPluginState.updateSchedule|IRCPluginState.updateSchedule]) 123 so that the main loop knows when to process the array of [core.thread.fiber.Fiber|Fiber]s. 124 125 Do not destroy and free the removed [core.thread.fiber.Fiber|Fiber], as it may be reused. 126 127 Params: 128 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 129 fiber = [core.thread.fiber.Fiber|Fiber] to dequeue from being executed 130 at a later point in time. 131 132 See_Also: 133 [delay] 134 +/ 135 void removeDelayedFiber(IRCPlugin plugin, Fiber fiber) 136 in ((fiber !is null), "Tried to remove a delayed null Fiber") 137 { 138 import std.algorithm.mutation : SwapStrategy, remove; 139 140 size_t[] toRemove; 141 142 foreach (immutable i, scheduledFiber; plugin.state.scheduledFibers) 143 { 144 if (scheduledFiber.fiber is fiber) 145 { 146 toRemove ~= i; 147 } 148 } 149 150 if (!toRemove.length) return; 151 152 foreach_reverse (immutable i; toRemove) 153 { 154 plugin.state.scheduledFibers = plugin.state.scheduledFibers 155 .remove!(SwapStrategy.unstable)(i); 156 } 157 158 plugin.state.updateSchedule(); 159 } 160 161 162 // removeDelayedFiber 163 /++ 164 Removes a [core.thread.fiber.Fiber|Fiber] from being called at any point later. 165 Overload that implicitly removes [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 166 167 Params: 168 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 169 170 See_Also: 171 [delay] 172 +/ 173 void removeDelayedFiber(IRCPlugin plugin) 174 in (Fiber.getThis, "Tried to call `removeDelayedFiber` from outside a Fiber") 175 { 176 return removeDelayedFiber(plugin, Fiber.getThis); 177 } 178 179 180 // removeDelayedDelegate 181 /++ 182 Removes a `void delegate()` delegate from being called at any point later. 183 184 Updates the `nextScheduledTimestamp` UNIX timestamp so that the main loop knows 185 when to process the array of delegates. 186 187 Params: 188 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 189 dg = Delegate to dequeue from being executed at a later point in time. 190 191 See_Also: 192 [delay] 193 +/ 194 void removeDelayedDelegate(IRCPlugin plugin, void delegate() dg) 195 in ((dg !is null), "Tried to remove a delayed null delegate") 196 { 197 import std.algorithm.mutation : SwapStrategy, remove; 198 199 size_t[] toRemove; 200 201 foreach (immutable i, scheduledDg; plugin.state.scheduledDelegates) 202 { 203 if (scheduledDg.dg is dg) 204 { 205 toRemove ~= i; 206 } 207 } 208 209 if (!toRemove.length) return; 210 211 foreach_reverse (immutable i; toRemove) 212 { 213 plugin.state.scheduledDelegates = plugin.state.scheduledDelegates 214 .remove!(SwapStrategy.unstable)(i); 215 } 216 217 plugin.state.updateSchedule(); 218 } 219 220 221 // await 222 /++ 223 Queues a [core.thread.fiber.Fiber|Fiber] to be called whenever the next parsed 224 and triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 225 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. 226 227 Params: 228 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 229 fiber = [core.thread.fiber.Fiber|Fiber] to enqueue to be executed when the next 230 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 231 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that should trigger the 232 passed awaiting fiber. 233 234 See_Also: 235 [unawait] 236 +/ 237 void await(IRCPlugin plugin, Fiber fiber, const IRCEvent.Type type) 238 in ((fiber !is null), "Tried to set up a null Fiber to await events") 239 in ((type != IRCEvent.Type.UNSET), "Tried to set up a Fiber to await `IRCEvent.Type.UNSET`") 240 { 241 plugin.state.awaitingFibers[type] ~= fiber; 242 } 243 244 245 // await 246 /++ 247 Queues a [core.thread.fiber.Fiber|Fiber] to be called whenever the next parsed 248 and triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 249 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. 250 Overload that implicitly queues [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 251 252 Params: 253 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 254 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that should trigger this 255 implicit awaiting fiber (in the current context). 256 yield = Whether or not to immediately yield the Fiber. 257 258 See_Also: 259 [unawait] 260 +/ 261 void await(IRCPlugin plugin, const IRCEvent.Type type, const Flag!"yield" yield) 262 in (Fiber.getThis, "Tried to `await` the current Fiber outside of a Fiber") 263 in ((type != IRCEvent.Type.UNSET), "Tried to set up a Fiber to await `IRCEvent.Type.UNSET`") 264 { 265 plugin.state.awaitingFibers[type] ~= Fiber.getThis; 266 if (yield) Fiber.yield(); 267 } 268 269 270 // await 271 /++ 272 Queues a [core.thread.fiber.Fiber|Fiber] to be called whenever the next parsed 273 and triggering [dialect.defs.IRCEvent|IRCEvent] matches any of the passed 274 [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. 275 276 Params: 277 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 278 fiber = [core.thread.fiber.Fiber|Fiber] to enqueue to be executed when the next 279 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 280 types = The kinds of [dialect.defs.IRCEvent|IRCEvent] that should trigger 281 the passed awaiting fiber, in an array with elements of type 282 [dialect.defs.IRCEvent.Type|IRCEvent.Type]. 283 284 See_Also: 285 [unawait] 286 +/ 287 void await(IRCPlugin plugin, Fiber fiber, const IRCEvent.Type[] types) 288 in ((fiber !is null), "Tried to set up a null Fiber to await events") 289 { 290 foreach (immutable type; types) 291 { 292 assert((type != IRCEvent.Type.UNSET), 293 "Tried to set up a Fiber to await `IRCEvent.Type.UNSET`"); 294 plugin.state.awaitingFibers[type] ~= fiber; 295 } 296 } 297 298 299 // await 300 /++ 301 Queues a [core.thread.fiber.Fiber|Fiber] to be called whenever the next parsed 302 and triggering [dialect.defs.IRCEvent|IRCEvent] matches any of the passed 303 [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. 304 Overload that implicitly queues [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 305 306 Params: 307 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 308 types = The kinds of [dialect.defs.IRCEvent|IRCEvent] that should trigger 309 this implicit awaiting fiber (in the current context), in an array 310 with elements of type [dialect.defs.IRCEvent.Type|IRCEvent.Type]. 311 yield = Whether or not to immediately yield the Fiber. 312 313 See_Also: 314 [unawait] 315 +/ 316 void await(IRCPlugin plugin, const IRCEvent.Type[] types, const Flag!"yield" yield) 317 in (Fiber.getThis, "Tried to `await` the current Fiber outside of a Fiber") 318 { 319 foreach (immutable type; types) 320 { 321 assert((type != IRCEvent.Type.UNSET), 322 "Tried to set up the current Fiber to await `IRCEvent.Type.UNSET`"); 323 plugin.state.awaitingFibers[type] ~= Fiber.getThis; 324 } 325 326 if (yield) Fiber.yield(); 327 } 328 329 330 // await 331 /++ 332 Queues a `void delegate(IRCEvent)` delegate to be called whenever the 333 next parsed and triggering const [dialect.defs.IRCEvent|IRCEvent] matches the 334 passed [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. 335 336 Note: The delegate stays in the queue until a call to [unawait] it is made. 337 338 Params: 339 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 340 dg = Delegate to enqueue to be executed when the next const 341 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 342 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that should trigger the 343 passed awaiting delegate. 344 345 See_Also: 346 [unawait] 347 +/ 348 void await(IRCPlugin plugin, void delegate(IRCEvent) dg, const IRCEvent.Type type) 349 in ((dg !is null), "Tried to set up a null delegate to await events") 350 in ((type != IRCEvent.Type.UNSET), "Tried to set up a delegate to await `IRCEvent.Type.UNSET`") 351 { 352 plugin.state.awaitingDelegates[type] ~= dg; 353 } 354 355 356 // await 357 /++ 358 Queues a `void delegate(IRCEvent)` delegate to be called whenever the 359 next parsed and triggering const [dialect.defs.IRCEvent|IRCEvent] matches 360 the passed [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. Overload that 361 takes an array of types. 362 363 Note: The delegate stays in the queue until a call to [unawait] it is made. 364 365 Params: 366 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 367 dg = Delegate to enqueue to be executed when the next const 368 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 369 types = An array of the kinds of [dialect.defs.IRCEvent|IRCEvent]s that 370 should trigger the passed awaiting delegate. 371 372 See_Also: 373 [unawait] 374 +/ 375 void await(IRCPlugin plugin, void delegate(IRCEvent) dg, const IRCEvent.Type[] types) 376 in ((dg !is null), "Tried to set up a null delegate to await events") 377 { 378 foreach (immutable type; types) 379 { 380 assert((type != IRCEvent.Type.UNSET), 381 "Tried to set up a delegate to await `IRCEvent.Type.UNSET`"); 382 plugin.state.awaitingDelegates[type] ~= dg; 383 } 384 } 385 386 387 // unawaitImpl 388 /++ 389 Dequeues something from being called whenever the next parsed and 390 triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 391 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. Implementation template. 392 393 Params: 394 thing = Thing to dequeue from being executed when the next 395 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 396 aa = Associative array to remove entries from. 397 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that would trigger the 398 passed awaiting thing. 399 400 See_Also: 401 [unawait] 402 +/ 403 private void unawaitImpl(Thing, AA) 404 (Thing thing, 405 ref AA aa, 406 const IRCEvent.Type type) 407 in ((thing !is null), "Tried to unlist a null " ~ Thing.stringof ~ " from awaiting events") 408 in ((type != IRCEvent.Type.UNSET), "Tried to unlist a " ~ Thing.stringof ~ 409 " from awaiting `IRCEvent.Type.UNSET`") 410 { 411 import std.algorithm.mutation : SwapStrategy, remove; 412 413 void removeForType(const IRCEvent.Type type) 414 { 415 foreach (immutable i, awaitingThing; aa[type]) 416 { 417 if (awaitingThing is thing) 418 { 419 aa[type] = aa[type].remove!(SwapStrategy.unstable)(i); 420 break; 421 } 422 } 423 } 424 425 if (type == IRCEvent.Type.ANY) 426 { 427 import std.traits : EnumMembers; 428 429 static immutable allTypes = [ EnumMembers!(IRCEvent.Type) ]; 430 431 foreach (immutable thisType; allTypes) 432 { 433 removeForType(thisType); 434 } 435 } 436 else 437 { 438 removeForType(type); 439 } 440 } 441 442 443 // unawait 444 /++ 445 Dequeues a [core.thread.fiber.Fiber|Fiber] from being called whenever the 446 next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 447 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. 448 449 Params: 450 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 451 fiber = [core.thread.fiber.Fiber|Fiber] to dequeue from being executed 452 when the next [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 453 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that would trigger the 454 passed awaiting fiber. 455 456 See_Also: 457 [unawaitImpl] 458 [await] 459 +/ 460 void unawait(IRCPlugin plugin, Fiber fiber, const IRCEvent.Type type) 461 in (fiber, "Tried to call `unawait` with a null Fiber") 462 { 463 return unawaitImpl(fiber, plugin.state.awaitingFibers, type); 464 } 465 466 467 // unawait 468 /++ 469 Dequeues a [core.thread.fiber.Fiber|Fiber] from being called whenever the 470 next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 471 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. Overload that implicitly dequeues 472 [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 473 474 Params: 475 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 476 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that would trigger this 477 implicit awaiting fiber (in the current context). 478 479 See_Also: 480 [unawaitImpl] 481 [await] 482 +/ 483 void unawait(IRCPlugin plugin, const IRCEvent.Type type) 484 in (Fiber.getThis, "Tried to call `unawait` from outside a Fiber") 485 { 486 return unawait(plugin, Fiber.getThis, type); 487 } 488 489 490 // unawait 491 /++ 492 Dequeues a [core.thread.fiber.Fiber|Fiber] from being called whenever the 493 next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches any of 494 the passed [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. 495 496 Params: 497 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 498 fiber = [core.thread.fiber.Fiber|Fiber] to dequeue from being executed 499 when the next [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 500 types = The kinds of [dialect.defs.IRCEvent|IRCEvent] that should trigger 501 the passed awaiting fiber, in an array with elements of type 502 [dialect.defs.IRCEvent.Type|IRCEvent.Type]. 503 504 See_Also: 505 [unawaitImpl] 506 [await] 507 +/ 508 void unawait(IRCPlugin plugin, Fiber fiber, const IRCEvent.Type[] types) 509 in (fiber, "Tried to call `unawait` with a null Fiber") 510 { 511 foreach (immutable type; types) 512 { 513 unawait(plugin, fiber, type); 514 } 515 } 516 517 518 // unawait 519 /++ 520 Dequeues a [core.thread.fiber.Fiber|Fiber] from being called whenever the 521 next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches any of the passed 522 [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. Overload that implicitly dequeues 523 [core.thread.fiber.Fiber.getThis|Fiber.getThis]. 524 525 Params: 526 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 527 types = The kinds of [dialect.defs.IRCEvent|IRCEvent] that should trigger 528 this implicit awaiting fiber (in the current context), in an array 529 with elements of type [dialect.defs.IRCEvent.Type|IRCEvent.Type]. 530 531 See_Also: 532 [unawaitImpl] 533 [await] 534 +/ 535 void unawait(IRCPlugin plugin, const IRCEvent.Type[] types) 536 in (Fiber.getThis, "Tried to call `unawait` from outside a Fiber") 537 { 538 foreach (immutable type; types) 539 { 540 unawait(plugin, Fiber.getThis, type); 541 } 542 } 543 544 545 // unawait 546 /++ 547 Dequeues a `void delegate(IRCEvent)` delegate from being called whenever 548 the next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches the passed 549 [dialect.defs.IRCEvent.Type|IRCEvent.Type] type. 550 551 Params: 552 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 553 dg = Delegate to dequeue from being executed when the next 554 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 555 type = The kind of [dialect.defs.IRCEvent|IRCEvent] that would trigger the 556 passed awaiting delegate. 557 558 See_Also: 559 [unawaitImpl] 560 [await] 561 +/ 562 void unawait(IRCPlugin plugin, void delegate(IRCEvent) dg, const IRCEvent.Type type) 563 in ((dg !is null), "Tried to call `unawait` with a null delegate") 564 { 565 return unawaitImpl(dg, plugin.state.awaitingDelegates, type); 566 } 567 568 569 // unawait 570 /++ 571 Dequeues a `void delegate(IRCEvent)` delegate from being called whenever 572 the next parsed and triggering [dialect.defs.IRCEvent|IRCEvent] matches any 573 of the passed [dialect.defs.IRCEvent.Type|IRCEvent.Type] types. Overload that 574 takes a array of types. 575 576 Params: 577 plugin = The current [kameloso.plugins.common.core.IRCPlugin|IRCPlugin]. 578 dg = Delegate to dequeue from being executed when the next 579 [dialect.defs.IRCEvent|IRCEvent] of type `type` comes along. 580 types = An array of the kinds of [dialect.defs.IRCEvent|IRCEvent]s that 581 would trigger the passed awaiting delegate. 582 583 See_Also: 584 [unawaitImpl] 585 [await] 586 +/ 587 void unawait(IRCPlugin plugin, void delegate(IRCEvent) dg, const IRCEvent.Type[] types) 588 in ((dg !is null), "Tried to call `unawait` with a null delegate") 589 { 590 foreach (immutable type; types) 591 { 592 unawaitImpl(dg, plugin.state.awaitingDelegates, type); 593 } 594 }