Merge branch 'development'
[NeonServV5.git] / src / IOEngine_select.c
1 /* IOEngine_select.c - IOMultiplexer
2  * Copyright (C) 2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
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.
13  * 
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/>. 
16  */
17 #include "IOEngine.h"
18 #include <errno.h>
19 #include <time.h>
20 #ifdef WIN32
21 #define _WIN32_WINNT 0x501
22 #include <windows.h>
23 #include <winsock2.h>
24 #else
25 #include <string.h>
26 #include <stdio.h>
27 #endif
28
29 static int engine_select_timer_delay_fix;
30
31 static int engine_select_init() {
32     engine_select_timer_delay_fix = 0;
33     return 1;
34 }
35
36 static void engine_select_add(struct IODescriptor *iofd) {
37     #ifdef WIN32
38     if(iofd->type == IOTYPE_STDIN)
39         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT);
40     #endif
41     /* empty */
42 }
43
44 static void engine_select_remove(struct IODescriptor *iofd) {
45     /* empty */
46 }
47
48 static void engine_select_update(struct IODescriptor *iofd) {
49     /* empty */
50 }
51
52 static void engine_select_loop(struct timeval *timeout) {
53     fd_set read_fds;
54     fd_set write_fds;
55     unsigned int fds_size = 0;
56     struct IODescriptor *iofd, *tmp_iofd;
57     struct timeval now, tdiff;
58     int select_result;
59     int timer_fix;
60     
61     gettimeofday(&now, NULL);
62     
63     //clear fds
64     FD_ZERO(&read_fds);
65     FD_ZERO(&write_fds);
66     
67     select_result = 0;
68     for(iofd = first_descriptor; iofd; iofd = tmp_iofd) {
69         tmp_iofd = iofd->next;
70         if(iofd->type == IOTYPE_STDIN) {
71             #ifdef WIN32
72             //WIN32 doesn't support stdin within select
73             //just try to read the single events from the console
74             DWORD dwRead;
75             INPUT_RECORD inRecords[128];
76             unsigned int i;
77             int read_bytes = 0;
78             GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE), &dwRead);
79             if(dwRead)
80                 ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &inRecords[0], 128, &dwRead);
81             for (i = 0; i < dwRead; ++i) {
82                 if (inRecords[i].EventType == KEY_EVENT) {
83                     const char c = inRecords[i].Event.KeyEvent.uChar.AsciiChar;
84                     if (inRecords[i].Event.KeyEvent.bKeyDown && c != 0) {
85                         iofd->readbuf.buffer[iofd->readbuf.bufpos + read_bytes] = c;
86                         read_bytes++;
87                     }
88                 }
89             }
90             if(read_bytes)
91                 iohandler_events(iofd, read_bytes, 0);
92             if(read_bytes >= 128) {
93                 timeout->tv_sec = 0;
94                 timeout->tv_usec = 1;
95                 //minimal timeout
96             } else {
97                 timeout->tv_sec = 0;
98                 timeout->tv_usec = 100000;
99             }
100             #else
101             if(iofd->fd > fds_size)
102                 fds_size = iofd->fd;
103             FD_SET(iofd->fd, &read_fds);
104             select_result++;
105             #endif
106         }
107         else if(iofd->type == IOTYPE_SERVER || iofd->type == IOTYPE_CLIENT) {
108             if(iofd->state == IO_CLOSED) 
109                 continue;
110             if(iofd->fd > fds_size)
111                 fds_size = iofd->fd;
112             FD_SET(iofd->fd, &read_fds);
113             select_result++;
114             if(iohandler_wants_writes(iofd))
115                 FD_SET(iofd->fd, &write_fds);
116         }
117     }
118     
119     while(timer_priority) {
120         tdiff.tv_sec = timer_priority->timeout.tv_sec - now.tv_sec;
121         tdiff.tv_usec = timer_priority->timeout.tv_usec - now.tv_usec;
122         if(tdiff.tv_sec < 0 || (tdiff.tv_sec == 0 && tdiff.tv_usec <= 0)) {
123             if(timer_priority->constant_timeout) {
124                 tdiff.tv_sec = 0;
125                 iohandler_set_timeout(timer_priority, &tdiff);
126                 iohandler_events(timer_priority, 0, 0);
127             } else {
128                 iohandler_events(timer_priority, 0, 0);
129                 iohandler_close(timer_priority); //also sets timer_priority to the next timed element
130             }
131             continue;
132         } else if(tdiff.tv_usec < 0) {
133             tdiff.tv_sec--;
134             tdiff.tv_usec += 1000000; //1 sec
135         }
136         if(timeval_is_smaler((&tdiff), timeout)) {
137             timeout->tv_sec = tdiff.tv_sec;
138             timeout->tv_usec = tdiff.tv_usec + engine_select_timer_delay_fix;
139             if(timeout->tv_usec < 0) {
140                 timeout->tv_sec--;
141                 timeout->tv_usec += 1000000;
142             }
143         }
144         break;
145     }
146     
147     if(select_result) {
148         //select system call
149         select_result = select(fds_size + 1, &read_fds, &write_fds, NULL, timeout);
150     } else {
151         #ifdef WIN32
152         Sleep((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000) + 1);
153         #else
154         struct timespec usleep_time;
155         usleep_time.tv_sec = timeout->tv_sec;
156         usleep_time.tv_nsec = timeout->tv_usec * 1000;
157         nanosleep(&usleep_time, NULL);
158         #endif
159     }
160     
161     if (select_result < 0) {
162         if (errno != EINTR) {
163             iohandler_log(IOLOG_FATAL, "select() failed with errno %d %d: %s", select_result, errno, strerror(errno));
164             return;
165         }
166     }
167     
168     gettimeofday(&now, NULL);
169     
170     //check all descriptors
171     for(iofd = first_descriptor; iofd; iofd = tmp_iofd) {
172         tmp_iofd = iofd->next;
173         if(iofd->type == IOTYPE_SERVER || iofd->type == IOTYPE_CLIENT || iofd->type == IOTYPE_STDIN) {
174             if(FD_ISSET(iofd->fd, &read_fds) || FD_ISSET(iofd->fd, &write_fds)) {
175                 iohandler_events(iofd, FD_ISSET(iofd->fd, &read_fds), FD_ISSET(iofd->fd, &write_fds));
176                 continue;
177             }
178         }
179     }
180     
181     //check timers
182     while(timer_priority) {
183         tdiff.tv_sec = timer_priority->timeout.tv_sec - now.tv_sec;
184         tdiff.tv_usec = timer_priority->timeout.tv_usec - now.tv_usec;
185         timer_fix = (tdiff.tv_sec * 1000000) + tdiff.tv_usec;
186         if(timer_fix <= 100) {
187             if(timer_fix + 100 < engine_select_timer_delay_fix || timer_fix - 100 > engine_select_timer_delay_fix) {
188                 engine_select_timer_delay_fix = ((engine_select_timer_delay_fix * 19) + timer_fix) / 20;
189                 iohandler_log(IOLOG_DEBUG, "timer delay fix set to: %d us", engine_select_timer_delay_fix);
190             }
191             if(timer_priority->constant_timeout) {
192                 tdiff.tv_sec = 0;
193                 iohandler_set_timeout(timer_priority, &tdiff);
194                 iohandler_events(timer_priority, 0, 0);
195             } else {
196                 iohandler_events(timer_priority, 0, 0);
197                 iohandler_close(timer_priority); //also sets timer_priority to the next timed element
198             }
199             continue;
200         }
201         break;
202     }
203     
204 }
205
206 static void engine_select_cleanup() {
207     /* empty */
208 }
209
210 struct IOEngine engine_select = {
211     .name = "select",
212     .init = engine_select_init,
213     .add = engine_select_add,
214     .remove = engine_select_remove,
215     .update = engine_select_update,
216     .loop = engine_select_loop,
217     .cleanup = engine_select_cleanup,
218 };