| 1 | module picoev |
| 2 | |
| 3 | #include <errno.h> |
| 4 | #include <sys/types.h> |
| 5 | #include <sys/event.h> |
| 6 | |
| 7 | fn C.kevent(i32, changelist voidptr, nchanges i32, eventlist voidptr, nevents i32, timeout &C.timespec) i32 |
| 8 | fn C.kqueue() i32 |
| 9 | fn C.EV_SET(kev voidptr, ident i32, filter i16, flags u16, fflags u32, data voidptr, udata voidptr) |
| 10 | |
| 11 | pub struct C.kevent { |
| 12 | pub mut: |
| 13 | ident int |
| 14 | // uintptr_t |
| 15 | filter i16 |
| 16 | flags u16 |
| 17 | fflags u32 |
| 18 | data voidptr |
| 19 | // intptr_t |
| 20 | udata voidptr |
| 21 | } |
| 22 | |
| 23 | @[heap] |
| 24 | pub struct KqueueLoop { |
| 25 | mut: |
| 26 | id int |
| 27 | now i64 |
| 28 | kq_id int |
| 29 | // -1 if not changed |
| 30 | changed_fds int |
| 31 | events [1024]C.kevent |
| 32 | changelist [256]C.kevent |
| 33 | } |
| 34 | |
| 35 | type LoopType = KqueueLoop |
| 36 | |
| 37 | // create_kqueue_loop creates a new kernel event queue with loop_id=`id`. |
| 38 | pub fn create_kqueue_loop(id int) !&KqueueLoop { |
| 39 | mut loop := &KqueueLoop{ |
| 40 | id: id |
| 41 | } |
| 42 | |
| 43 | loop.kq_id = C.kqueue() |
| 44 | if loop.kq_id == -1 { |
| 45 | return error('could not create kqueue loop!') |
| 46 | } |
| 47 | loop.changed_fds = -1 |
| 48 | return loop |
| 49 | } |
| 50 | |
| 51 | // ev_set sets a new `kevent` with file descriptor `index`. |
| 52 | @[inline] |
| 53 | pub fn (mut pv Picoev) ev_set(index int, operation int, events int) { |
| 54 | mut filter := 0 |
| 55 | if events & picoev_read != 0 { |
| 56 | filter |= C.EVFILT_READ |
| 57 | } |
| 58 | if events & picoev_write != 0 { |
| 59 | filter |= C.EVFILT_WRITE |
| 60 | } |
| 61 | filter = i16(filter) |
| 62 | |
| 63 | C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0, 0) |
| 64 | } |
| 65 | |
| 66 | // backend_build uses the lower 8 bits to store the old events and the higher 8 |
| 67 | // bits to store the next file descriptor in `Target.backend`. |
| 68 | @[inline] |
| 69 | fn backend_build(next_fd int, events u32) int { |
| 70 | return int((u32(next_fd) << 8) | (events & 0xff)) |
| 71 | } |
| 72 | |
| 73 | // get the lower 8 bits. |
| 74 | @[inline] |
| 75 | fn backend_get_old_events(backend int) int { |
| 76 | return backend & 0xff |
| 77 | } |
| 78 | |
| 79 | // get the higher 8 bits. |
| 80 | @[inline] |
| 81 | fn backend_get_next_fd(backend int) int { |
| 82 | return backend >> 8 |
| 83 | } |
| 84 | |
| 85 | // apply pending processes all changes for the file descriptors and updates `loop.changelist` |
| 86 | // if `aplly_all` is `true` the changes are immediately applied. |
| 87 | fn (mut pv Picoev) apply_pending_changes(apply_all bool) int { |
| 88 | mut total, mut nevents := 0, 0 |
| 89 | |
| 90 | for pv.loop.changed_fds != -1 { |
| 91 | mut target := pv.file_descriptors[pv.loop.changed_fds] |
| 92 | old_events := backend_get_old_events(target.backend) |
| 93 | if target.events != old_events { |
| 94 | // events have been changed |
| 95 | if old_events != 0 { |
| 96 | pv.ev_set(total, C.EV_DISABLE, old_events) |
| 97 | total++ |
| 98 | } |
| 99 | if target.events != 0 { |
| 100 | pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events)) |
| 101 | total++ |
| 102 | } |
| 103 | // Apply the changes if the total changes exceed the changelist size |
| 104 | if total + 1 >= pv.loop.changelist.len { |
| 105 | nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL) |
| 106 | assert nevents == 0 |
| 107 | total = 0 |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | pv.loop.changed_fds = backend_get_next_fd(target.backend) |
| 112 | target.backend = -1 |
| 113 | } |
| 114 | |
| 115 | if apply_all && total != 0 { |
| 116 | nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL) |
| 117 | assert nevents == 0 |
| 118 | total = 0 |
| 119 | } |
| 120 | |
| 121 | return total |
| 122 | } |
| 123 | |
| 124 | // updates the events associated with a file descriptor in the event loop. |
| 125 | @[direct_array_access] |
| 126 | fn (mut pv Picoev) update_events(fd int, events int) int { |
| 127 | // check if fd is in range |
| 128 | assert fd < max_fds |
| 129 | |
| 130 | mut target := pv.file_descriptors[fd] |
| 131 | |
| 132 | // initialize if adding the fd |
| 133 | if events & picoev_add != 0 { |
| 134 | target.backend = -1 |
| 135 | } |
| 136 | |
| 137 | // return if nothing to do |
| 138 | if (events == picoev_del && target.backend == -1) |
| 139 | || (events != picoev_del && events & picoev_readwrite == target.events) { |
| 140 | return 0 |
| 141 | } |
| 142 | |
| 143 | // add to changed list if not yet being done |
| 144 | if target.backend == -1 { |
| 145 | target.backend = backend_build(pv.loop.changed_fds, target.events) |
| 146 | pv.loop.changed_fds = fd |
| 147 | } |
| 148 | |
| 149 | // update events |
| 150 | target.events = u32(events & picoev_readwrite) |
| 151 | // apply immediately if is a DELETE |
| 152 | if events & picoev_del != 0 { |
| 153 | pv.apply_pending_changes(true) |
| 154 | } |
| 155 | |
| 156 | return 0 |
| 157 | } |
| 158 | |
| 159 | // performs a single iteration of the select-based event loop. |
| 160 | @[direct_array_access] |
| 161 | fn (mut pv Picoev) poll_once(max_wait_in_sec int) int { |
| 162 | ts := C.timespec{ |
| 163 | tv_sec: max_wait_in_sec |
| 164 | tv_nsec: 0 |
| 165 | } |
| 166 | |
| 167 | mut total, mut nevents := 0, 0 |
| 168 | // apply changes later when the callback is called. |
| 169 | total = pv.apply_pending_changes(false) |
| 170 | |
| 171 | nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, |
| 172 | pv.loop.events.len, &ts) |
| 173 | if nevents == -1 { |
| 174 | // the errors we can only rescue |
| 175 | assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR |
| 176 | return -1 |
| 177 | } |
| 178 | |
| 179 | for i := 0; i < nevents; i++ { |
| 180 | event := pv.loop.events[i] |
| 181 | target := pv.file_descriptors[event.ident] |
| 182 | |
| 183 | // changelist errors are fatal |
| 184 | assert event.flags & C.EV_ERROR == 0 |
| 185 | |
| 186 | if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 { |
| 187 | read_events := match int(event.filter) { |
| 188 | C.EVFILT_READ { |
| 189 | picoev_read |
| 190 | } |
| 191 | C.EVFILT_WRITE { |
| 192 | picoev_write |
| 193 | } |
| 194 | else { |
| 195 | 0 |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // do callback! |
| 200 | unsafe { target.cb(target.fd, read_events, &pv) } |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return 0 |
| 205 | } |
| 206 | |