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 }