egedit: do not save cursor movement in undo -- this is my stupid habit, and it comple...
[iv.d.git] / _obsolete_dont_use / evloop.d
blobd3f4eb64ad5e6ed0cbb63e8f65524cab01331ce7
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*/;
19 private:
21 import iv.alice;
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 // ////////////////////////////////////////////////////////////////////////// //
36 enum SIGWINCH = 28;
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;
50 doQuit = true;
51 if (global) doGlobalQuit = true;
52 raise(SIGINT);
56 // ////////////////////////////////////////////////////////////////////////// //
57 // register signals
58 void installSignalHandlers () @trusted /*nothrow*/ /*@nogc*/ {
59 if (csigfd < 0) {
60 import core.sys.posix.signal;
61 import core.sys.linux.sys.signalfd;
62 sigset_t mask;
63 sigemptyset(&mask);
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 {
81 Periodic,
82 Oneshot
86 struct Timer {
87 ulong id;
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;
101 * add new timer.
103 * Params:
104 * interval = timer interval
105 * onTimer = callback
106 * type = timer type
108 * Returns:
109 * timer id (always positive)
111 * Throws:
112 * TimeException on invalid interval
114 public ulong addTimer (Duration interval, void delegate (ulong id) onTimer, TimerType type=TimerType.Periodic) @trusted
115 in {
116 assert(type >= TimerType.min && type <= TimerType.max);
118 body {
119 long iv = interval.total!"msecs";
120 if (iv < 1) throw new TimeException("invalid timer interval");
121 ulong res;
122 Timer tm;
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;
129 // add to list
130 uint idx = uint.max;
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;
134 idx = timersUsed++;
136 timers[idx] = tm;
137 timerId2Idx[res] = idx;
138 return res;
142 public void removeTimer (ulong id) @trusted {
143 auto idx = id in timerId2Idx;
144 if (idx !is null) {
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 {
154 None = 0,
155 CanRead = 0x01,
156 CanWrite = 0x02,
157 Timeout = 0x04,
158 Error = 0x80
162 struct FDInfo {
163 int fd;
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
181 pfds.length = 32;
182 pfds[0].fd = -1;
183 pfdUsed = 1;
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;
191 } else {
192 // no timeout
193 nfo.timeout = -1;
194 nfo.toTime = -1;
200 * add fd to event loop. can be called from event loop callbacks.
202 * Params:
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
208 * Returns:
209 * nothing
211 * Throws:
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");
219 FDInfo nfo;
220 nfo.fd = fd;
221 nfo.onEvent = eventCB;
222 nfo.changeCount = curChangeCount;
223 ushort events = 0;
224 if (flg&FDFlags.CanRead) events |= POLLIN;
225 if (flg&FDFlags.CanWrite) events |= POLLOUT;
226 nfo.events = events;
227 fixTimeout(nfo, flg, timeout);
228 if (events == 0 && nfo.timeout < 0) throw new Exception("invalid flags");
229 uint idx = uint.max;
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;
233 idx = pfdUsed++;
235 nfo.pfdIndex = idx;
236 pfds[idx].fd = fd;
237 pfds[idx].events = nfo.events;
238 pfds[idx].revents = 0;
239 fdset[fd] = nfo;
244 * add fd to event loop. can be called from event loop callbacks.
246 * Params:
247 * fd = file descriptor (fd); must not be negative
248 * flg = wanted events
249 * eventCB = event callback
251 * Returns:
252 * nothing
254 * Throws:
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");
263 FDInfo nfo;
264 nfo.fd = fd;
265 nfo.onEvent = eventCB;
266 nfo.changeCount = curChangeCount;
267 ushort events = 0;
268 if (flg&FDFlags.CanRead) events |= POLLIN;
269 if (flg&FDFlags.CanWrite) events |= POLLOUT;
270 nfo.events = events;
271 fixTimeout(nfo, flg, Duration.zero);
272 if (events == 0 && nfo.timeout < 0) throw new Exception("invalid flags");
273 uint idx = uint.max;
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;
277 idx = pfdUsed++;
279 nfo.pfdIndex = idx;
280 pfds[idx].fd = fd;
281 pfds[idx].events = nfo.events;
282 pfds[idx].revents = 0;
283 fdset[fd] = nfo;
284 version(unittest) dumpFDs();
288 version(unittest)
289 void dumpFDs () {
290 import std.stdio;
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;
295 if (nfo is null) {
296 writefln("idx=%s; fd=%s; events=0x%02x; revents=0x%02x", idx, pfds[idx].fd, pfds[idx].events, pfds[idx].revents);
297 } else {
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("=============================");
306 * set fd flags.
308 * Params:
309 * fd = file descriptor (fd); must not be negative
310 * flg = wanted events
311 * timeout = timeout interval if Timeout flag is set
313 * Returns:
314 * nothing
316 * Throws:
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");
324 ushort events = 0;
325 if (flg&FDFlags.CanRead) events |= POLLIN;
326 if (flg&FDFlags.CanWrite) events |= POLLOUT;
327 fi.events = events;
328 fixTimeout(*fi, flg, timeout);
329 if (events == 0 && fi.timeout < 0) {
330 // remove invalid fd
331 removeFD(fd);
332 throw new Exception("invalid flags");
334 pfds[fi.pfdIndex].events = fi.events;
335 fi.changeCount = curChangeCount;
340 * set fd callback.
342 * Params:
343 * fd = file descriptor (fd); must not be negative
344 * eventCB = event callback
346 * Returns:
347 * nothing
349 * Throws:
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.
364 * Params:
365 * fd = file descriptor (fd); must not be negative
367 * Returns:
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;
373 if (fi !is null) {
374 pfds[fi.pfdIndex].fd = -1; // mark as free
375 while (pfdUsed > 0 && pfds[pfdUsed-1].fd < 0) --pfdUsed;
376 fdset.remove(fd);
377 return true;
379 return false;
384 * is fd in list?
386 * Params:
387 * fd = file descriptor (fd); must not be negative
389 * Returns:
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) {
407 // wrapped, fix cc
408 curChangeCount = 1;
409 try { // for GDC
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;
419 // skip just added
420 if (timers[idx].changeCount < curChangeCount) {
421 if (timers[idx].shotTime <= curTime) {
422 // shot it!
423 auto tm = timers[idx];
424 if (tm.interval < 0) {
425 timers[idx].id = 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
443 auto end = pfdUsed;
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)) {
450 flg = FDFlags.Error;
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
461 // skip just added
462 if (nfo.changeCount < curChangeCount) {
463 auto fd = nfo.fd;
464 auto ev = nfo.onEvent;
465 // autoremove on error
466 if (flg&FDFlags.Error) removeFD(fd);
467 // shot it
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;
474 // fix timeout
475 if (nfo.timeout >= 0) {
476 if (nfo.timeout > 0) {
477 while (nfo.toTime <= curTime) nfo.toTime += nfo.timeout;
478 } else {
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();
505 pfds[0].fd = csigfd;
506 pfds[0].events = POLLIN;
508 scope(exit) doQuit = false;
510 // loop
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) {
521 // signal arrived
522 signalfd_siginfo si;
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) {
528 wschanged = true;
529 } else if (si.ssi_signo == SIGINT) {
530 doQuit = true;
531 } else {
532 doGlobalQuit = true;
535 if (wschanged && onSizeChanged !is null) {
536 try onSizeChanged(); catch (Exception) {}
538 if (doGlobalQuit || doQuit) break; // just exit
546 unittest {
547 import std.stdio;
548 writeln("started...");
549 addTimer(1500.msecs, (id) { writeln("*** timer ***"); } );
550 eventLoop();
551 writeln("done...");
556 unittest {
557 import core.sys.posix.unistd : STDIN_FILENO;
558 import iv.rawtty;
559 import std.stdio;
560 writeln("started...");
562 auto oldMode = ttySetRaw();
563 if (oldMode == TTYMode.BAD) throw new Exception("not a tty");
564 scope(exit) ttySetMode(oldMode);
566 int count = 3;
568 addTimer(1500.msecs, (id) {
569 writeln("*** timer ***");
570 if (--count <= 0) sendQuitSignal();
571 } );
573 removeFD(STDIN_FILENO);
574 addFD(STDIN_FILENO, FDFlags.CanRead, (int fd, FDFlags flags) {
575 // keyboard
576 auto s = ttyReadKey();
577 //if (s !is null && onKeyPressed !is null) onKeyPressed(s);
578 writeln("+++ ", s, " +++");
580 eventLoop();
582 writeln("done...");