1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // severely outdated event loop
18 module iv
.evloop
/*is aliced*/;
22 public import core
.time
; // for timers, Duration
23 //import core.time : MonoTime, TimeException;
26 // ////////////////////////////////////////////////////////////////////////// //
27 public long mtimeToMSecs (in MonoTime mt
) @safe pure nothrow {
28 return convClockFreq(mt
.ticks
, MonoTime
.ticksPerSecond
, 1_000);
32 public long currentMSecs () @safe nothrow => mtimeToMSecs(MonoTime
.currTime
);
35 // ////////////////////////////////////////////////////////////////////////// //
39 __gshared
int csigfd
= -1;
41 __gshared
bool doQuit
= false;
42 __gshared
bool doGlobalQuit
= false;
45 // ////////////////////////////////////////////////////////////////////////// //
46 public bool isGlobalQuit () @trusted nothrow @nogc => doGlobalQuit
;
48 public void sendQuitSignal (bool global
=false) @trusted @nogc {
49 import core
.sys
.posix
.signal
: raise
, SIGINT
;
51 if (global
) doGlobalQuit
= true;
56 // ////////////////////////////////////////////////////////////////////////// //
58 void installSignalHandlers () @trusted /*nothrow*/ /*@nogc*/ {
60 import core
.sys
.posix
.signal
;
61 import core
.sys
.linux
.sys
.signalfd
;
64 sigaddset(&mask
, SIGTERM
);
65 sigaddset(&mask
, SIGHUP
);
66 sigaddset(&mask
, SIGQUIT
);
67 sigaddset(&mask
, SIGINT
);
68 sigaddset(&mask
, SIGWINCH
);
69 sigprocmask(SIG_BLOCK
, &mask
, null); //we block the signals
70 csigfd
= signalfd(-1, &mask
, SFD_NONBLOCK
); // sorry
75 // ////////////////////////////////////////////////////////////////////////// //
76 __gshared curChangeCount
= 0;
79 // ////////////////////////////////////////////////////////////////////////// //
80 public enum TimerType
{
88 long interval
; // <0: oneshot
89 long shotTime
; // time when it should shot, in msecs
90 void delegate (ulong id
) onTimer
;
91 ulong changeCount
; // process this only if changeCount < curChangeCount
94 __gshared Timer
[] timers
;
95 __gshared
uint timersUsed
;
96 __gshared
uint[ulong] timerId2Idx
;
97 __gshared
ulong lastTimerId
;
104 * interval = timer interval
109 * timer id (always positive)
112 * TimeException on invalid interval
114 public ulong addTimer (Duration interval
, void delegate (ulong id
) onTimer
, TimerType type
=TimerType
.Periodic
) @trusted
116 assert(type
>= TimerType
.min
&& type
<= TimerType
.max
);
119 long iv
= interval
.total
!"msecs";
120 if (iv
< 1) throw new TimeException("invalid timer interval");
123 res
= tm
.id
= ++lastTimerId
;
124 if (lastTimerId
< res
) assert(0); // overflow, fuck it
125 tm
.interval
= (type
== TimerType
.Periodic ? iv
: -1);
126 tm
.shotTime
= currentMSecs()+iv
;
127 tm
.onTimer
= onTimer
;
128 tm
.changeCount
= curChangeCount
;
131 foreach (/*auto*/ i
; 0..timersUsed
) if (timers
[i
].id
== 0) { idx
= i
; break; }
132 if (idx
== uint.max
) {
133 if (timersUsed
>= timers
.length
) timers
.length
= timers
.length
+32;
137 timerId2Idx
[res
] = idx
;
142 public void removeTimer (ulong id
) @trusted {
143 auto idx
= id
in timerId2Idx
;
145 timers
[*idx
].id
= 0; // mark as free
146 timerId2Idx
.remove(id
);
147 while (timersUsed
> 0 && timers
[timersUsed
-1].id
== 0) --timersUsed
;
152 // ////////////////////////////////////////////////////////////////////////// //
153 public enum FDFlags
{
164 long toTime
; // >=0: time when timeout comes (NOT timeout interval)
165 long timeout
; // <0: no timeout
166 ushort events
; // for poll; can be 0 if we need only timeout
167 uint pfdIndex
; // index in pfds
168 void delegate (int fd
, FDFlags flags
) onEvent
;
169 ulong changeCount
; // process this only if changeCount < curChangeCount
173 import core
.sys
.posix
.poll
: pollfd
, poll
, POLLIN
, POLLOUT
, POLLERR
, POLLHUP
, POLLNVAL
/*, POLLRDHUP*/;
175 __gshared pollfd
[] pfds
;
176 __gshared
uint pfdUsed
;
177 __gshared FDInfo
[int] fdset
;
179 shared static this () {
180 // [0] is reserved for csigfd
187 void fixTimeout (ref FDInfo nfo
, FDFlags flg
, Duration timeout
) @safe nothrow {
188 if ((flg
&FDFlags
.Timeout
) && timeout
!= Duration
.max
&& timeout
> Duration
.zero
) {
189 nfo
.timeout
= timeout
.total
!"msecs";
190 nfo
.toTime
= currentMSecs()+nfo
.timeout
;
200 * add fd to event loop. can be called from event loop callbacks.
203 * fd = file descriptor (fd); must not be negative
204 * flg = wanted events
205 * timeout = timeout interval if Timeout flag is set
206 * eventCB = event callback
212 * Exception if fd is already in list
214 public void addFD (int fd
, FDFlags flg
, Duration timeout
, void delegate (int fd
, FDFlags flags
) eventCB
) @trusted {
215 if ((flg
&(FDFlags
.CanRead|FDFlags
.CanWrite|FDFlags
.Timeout
)) == 0) throw new Exception("invalid flags");
216 if (fd
< 0 || fd
== csigfd
) throw new Exception("invalid fd");
217 auto fi
= fd
in fdset
;
218 if (fi
!is null) throw new Exception("duplicate fd");
221 nfo
.onEvent
= eventCB
;
222 nfo
.changeCount
= curChangeCount
;
224 if (flg
&FDFlags
.CanRead
) events |
= POLLIN
;
225 if (flg
&FDFlags
.CanWrite
) events |
= POLLOUT
;
227 fixTimeout(nfo
, flg
, timeout
);
228 if (events
== 0 && nfo
.timeout
< 0) throw new Exception("invalid flags");
230 foreach (/*auto*/ i
; 1..pfdUsed
) if (pfds
[i
].fd
< 0) { idx
= i
; break; }
231 if (idx
== uint.max
) {
232 if (pfdUsed
>= pfds
.length
) pfds
.length
= pfds
.length
+32;
237 pfds
[idx
].events
= nfo
.events
;
238 pfds
[idx
].revents
= 0;
244 * add fd to event loop. can be called from event loop callbacks.
247 * fd = file descriptor (fd); must not be negative
248 * flg = wanted events
249 * eventCB = event callback
255 * Exception if fd is already in list
257 public void addFD (int fd
, FDFlags flg
, void delegate (int fd
, FDFlags flags
) eventCB
) @trusted {
258 flg
&= ~FDFlags
.Timeout
;
259 if ((flg
&(FDFlags
.CanRead|FDFlags
.CanWrite|FDFlags
.Timeout
)) == 0) throw new Exception("invalid flags");
260 if (fd
< 0 || fd
== csigfd
) throw new Exception("invalid fd");
261 auto fi
= fd
in fdset
;
262 if (fi
!is null) throw new Exception("duplicate fd");
265 nfo
.onEvent
= eventCB
;
266 nfo
.changeCount
= curChangeCount
;
268 if (flg
&FDFlags
.CanRead
) events |
= POLLIN
;
269 if (flg
&FDFlags
.CanWrite
) events |
= POLLOUT
;
271 fixTimeout(nfo
, flg
, Duration
.zero
);
272 if (events
== 0 && nfo
.timeout
< 0) throw new Exception("invalid flags");
274 foreach (/*auto*/ i
; 1..pfdUsed
) if (pfds
[i
].fd
< 0) { idx
= i
; break; }
275 if (idx
== uint.max
) {
276 if (pfdUsed
>= pfds
.length
) pfds
.length
= pfds
.length
+32;
281 pfds
[idx
].events
= nfo
.events
;
282 pfds
[idx
].revents
= 0;
284 version(unittest) dumpFDs();
291 writefln("=== used pfds: %s ===", pfdUsed
);
292 foreach (/*auto*/ idx
; 0..pfdUsed
) {
293 if (pfds
[idx
].fd
< 0) continue;
294 auto nfo
= pfds
[idx
].fd
in fdset
;
296 writefln("idx=%s; fd=%s; events=0x%02x; revents=0x%02x", idx
, pfds
[idx
].fd
, pfds
[idx
].events
, pfds
[idx
].revents
);
298 writefln("idx=%s; fd=%s; events=0x%02x; revents=0x%02x; ev=0x%02x", idx
, pfds
[idx
].fd
, pfds
[idx
].events
, pfds
[idx
].revents
, nfo
.events
);
301 writeln("=============================");
309 * fd = file descriptor (fd); must not be negative
310 * flg = wanted events
311 * timeout = timeout interval if Timeout flag is set
317 * Exception if fd is not in list
319 public void setFDFlags (int fd
, FDFlags flg
, Duration timeout
) @trusted {
320 if ((flg
&(FDFlags
.CanRead|FDFlags
.CanWrite|FDFlags
.Timeout
)) == 0) throw new Exception("invalid flags");
321 if (fd
< 0 || fd
== csigfd
) throw new Exception("invalid fd");
322 auto fi
= fd
in fdset
;
323 if (fi
is null) throw new Exception("unknown fd");
325 if (flg
&FDFlags
.CanRead
) events |
= POLLIN
;
326 if (flg
&FDFlags
.CanWrite
) events |
= POLLOUT
;
328 fixTimeout(*fi
, flg
, timeout
);
329 if (events
== 0 && fi
.timeout
< 0) {
332 throw new Exception("invalid flags");
334 pfds
[fi
.pfdIndex
].events
= fi
.events
;
335 fi
.changeCount
= curChangeCount
;
343 * fd = file descriptor (fd); must not be negative
344 * eventCB = event callback
350 * Exception if fd is not in list
352 public void setFDCallback (int fd
, void delegate (int fd
, FDFlags flags
) eventCB
) @trusted {
353 if (fd
< 0 || fd
== csigfd
) throw new Exception("invalid fd");
354 auto fi
= fd
in fdset
;
355 if (fi
is null) throw new Exception("unknown fd");
356 fi
.onEvent
= eventCB
;
357 fi
.changeCount
= curChangeCount
;
362 * remove fd from event loop. can be called from event loop callbacks.
365 * fd = file descriptor (fd); must not be negative
368 * true if fd was removed
370 public bool removeFD (int fd
) @trusted {
371 if (fd
< 0 || fd
== csigfd
) return false;
372 auto fi
= fd
in fdset
;
374 pfds
[fi
.pfdIndex
].fd
= -1; // mark as free
375 while (pfdUsed
> 0 && pfds
[pfdUsed
-1].fd
< 0) --pfdUsed
;
387 * fd = file descriptor (fd); must not be negative
390 * true if fd is in list
392 public bool hasFD (int fd
) @trusted nothrow {
393 if (fd
< 0 || fd
== csigfd
) return false;
394 auto fi
= fd
in fdset
;
395 return (fi
!is null);
399 // ////////////////////////////////////////////////////////////////////////// //
400 // return next poll timeout in milliseconds
401 // shots timers that needs to be shot
402 int processAll () /*@trusted nothrow*/ {
403 auto curTime
= currentMSecs();
404 long tout
= long.max
;
406 if (++curChangeCount
== 0) {
410 foreach (ref fi
; fdset
.byValue
) fi
.changeCount
= (fi
.changeCount
== ulong.max ?
1 : 0);
411 foreach (ref tm
; timers
) if (tm
.id
> 0) tm
.changeCount
= (tm
.changeCount
== ulong.max ?
1 : 0);
412 } catch (Exception
) {}
415 // process and shot timers
416 if (timersUsed
> 0) {
417 foreach (/*auto*/ idx
; 0..timersUsed
) {
418 if (timers
[idx
].id
== 0) continue;
420 if (timers
[idx
].changeCount
< curChangeCount
) {
421 if (timers
[idx
].shotTime
<= curTime
) {
423 auto tm
= timers
[idx
];
424 if (tm
.interval
< 0) {
426 timerId2Idx
.remove(tm
.id
);
428 try { if (tm
.onTimer
!is null) tm
.onTimer(tm
.id
); } catch (Exception
) {}
429 if (tm
.id
!in timerId2Idx
) continue; // removed
430 if (tm
.id
== timers
[idx
].id
) {
431 // try to keep the same interval
432 while (timers
[idx
].shotTime
<= curTime
) timers
[idx
].shotTime
+= timers
[idx
].interval
;
436 if (timers
[idx
].id
!= 0 && timers
[idx
].shotTime
< tout
) tout
= timers
[idx
].shotTime
;
438 while (timersUsed
> 0 && timers
[timersUsed
-1].id
== 0) --timersUsed
;
441 version(unittest) dumpFDs();
442 // process and shot fds
444 foreach (immutable uint idx
; 1..pfdUsed
) {
445 if (pfds
[idx
].fd
< 0) continue; // nothing to do
446 FDFlags flg
= FDFlags
.None
;
447 auto rev
= pfds
[idx
].revents
;
448 pfds
[idx
].revents
= 0;
449 if (rev
&(/*POLLRDHUP|*/POLLERR|POLLHUP|POLLNVAL
)) {
451 } else if (rev
&(POLLIN|POLLOUT
)) {
452 if (rev
&POLLIN
) flg |
= FDFlags
.CanRead
;
453 if (rev
&POLLOUT
) flg |
= FDFlags
.CanWrite
;
455 // check timeout if necessary
456 auto nfo
= pfds
[idx
].fd
in fdset
;
457 if (nfo
is null) assert(0);
458 if (nfo
.timeout
>= 0 && nfo
.toTime
<= curTime
) flg |
= FDFlags
.Timeout
;
459 //{ import std.stdio; writefln("idx=%s; flg=0x%02x; cc=%s; ccc=%s; evs=0x%04x", idx, flg, nfo.changeCount, curChangeCount, rev); }
460 if (flg
== FDFlags
.None
) continue; // nothing to do with this one
462 if (nfo
.changeCount
< curChangeCount
) {
464 auto ev
= nfo
.onEvent
;
465 // autoremove on error
466 if (flg
&FDFlags
.Error
) removeFD(fd
);
468 if (ev
!is null) try ev(fd
, flg
); catch (Exception
) {}
470 if (pfds
[idx
].fd
< 0) continue; // removed
471 // nfo address can change, so refresh it
472 nfo
= pfds
[idx
].fd
in fdset
;
473 if (nfo
is null) continue;
475 if (nfo
.timeout
>= 0) {
476 if (nfo
.timeout
> 0) {
477 while (nfo
.toTime
<= curTime
) nfo
.toTime
+= nfo
.timeout
;
479 nfo
.toTime
= curTime
;
481 if (nfo
.toTime
< tout
) tout
= nfo
.toTime
;
484 foreach (/*auto*/ idx
; end
..pfdUsed
) pfds
[idx
].revents
= 0;
485 while (pfdUsed
> 0 && pfds
[pfdUsed
-1].fd
< 0) --pfdUsed
;
487 if (tout
!= tout
.max
) {
488 curTime
= currentMSecs();
489 return (tout
<= curTime ?
0 : cast(int)(tout
-curTime
));
491 return -1; // infinite
495 // ////////////////////////////////////////////////////////////////////////// //
496 public __gshared
void delegate () onSizeChanged
;
499 // ////////////////////////////////////////////////////////////////////////// //
500 public void eventLoop () {
501 import core
.stdc
.signal
: SIGINT
;
502 import core
.sys
.linux
.sys
.signalfd
: signalfd_siginfo
;
504 installSignalHandlers();
506 pfds
[0].events
= POLLIN
;
508 scope(exit
) doQuit
= false;
511 while (!doQuit
&& !doGlobalQuit
) {
512 //foreach (/+auto+/ idx; 0..pfdUsed) pfds[idx].revents = 0;
513 auto tomsec
= processAll();
514 if (doQuit || doGlobalQuit
) break;
515 //{ import std.stdio; writeln("tomsec=", tomsec); }
516 auto res
= poll(pfds
.ptr
, pfdUsed
, tomsec
);
517 if (res
< 0) break; // some error occured, shit
518 if (res
== 0) continue; // timeout, do nothing
519 version(unittest) dumpFDs();
520 if (pfds
[0].revents
&POLLIN
) {
523 bool wschanged
= false;
525 import core
.sys
.posix
.unistd
: read
;
526 while (read(cast(int)csigfd
, &si
, si
.sizeof
) > 0) {
527 if (si
.ssi_signo
== SIGWINCH
) {
529 } else if (si
.ssi_signo
== SIGINT
) {
535 if (wschanged
&& onSizeChanged
!is null) {
536 try onSizeChanged(); catch (Exception
) {}
538 if (doGlobalQuit || doQuit
) break; // just exit
548 writeln("started...");
549 addTimer(1500.msecs, (id) { writeln("*** timer ***"); } );
557 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
560 writeln("started...");
562 auto oldMode
= ttySetRaw();
563 if (oldMode
== TTYMode
.BAD
) throw new Exception("not a tty");
564 scope(exit
) ttySetMode(oldMode
);
568 addTimer(1500.msecs
, (id
) {
569 writeln("*** timer ***");
570 if (--count
<= 0) sendQuitSignal();
573 removeFD(STDIN_FILENO
);
574 addFD(STDIN_FILENO
, FDFlags
.CanRead
, (int fd
, FDFlags flags
) {
576 auto s
= ttyReadKey();
577 //if (s !is null && onKeyPressed !is null) onKeyPressed(s);
578 writeln("+++ ", s
, " +++");