|
| 1 | +module picoev |
| 2 | + |
| 3 | +#include <errno.h> |
| 4 | +#include <sys/types.h> |
| 5 | +#include <sys/event.h> |
| 6 | + |
| 7 | +fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int |
| 8 | +fn C.kqueue() int |
| 9 | +fn C.EV_SET(kev voidptr, ident int, 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 | + // vfmt off |
| 55 | + filter := i16( |
| 56 | + (if events & picoev_read != 0 { C.EVFILT_READ } else { 0 }) |
| 57 | + | |
| 58 | + (if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 }) |
| 59 | + ) |
| 60 | + // vfmt on |
| 61 | + C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0, |
| 62 | + 0) |
| 63 | +} |
| 64 | + |
| 65 | +// backend_build uses the lower 8 bits to store the old events and the higher 8 |
| 66 | +// bits to store the next file descriptor in `Target.backend` |
| 67 | +[inline] |
| 68 | +fn backend_build(next_fd int, events u32) int { |
| 69 | + return int((u32(next_fd) << 8) | (events & 0xff)) |
| 70 | +} |
| 71 | + |
| 72 | +// get the lower 8 bits |
| 73 | +[inline] |
| 74 | +fn backend_get_old_events(backend int) int { |
| 75 | + return backend & 0xff |
| 76 | +} |
| 77 | + |
| 78 | +// get the higher 8 bits |
| 79 | +[inline] |
| 80 | +fn backend_get_next_fd(backend int) int { |
| 81 | + return backend >> 8 |
| 82 | +} |
| 83 | + |
| 84 | +// apply pending processes all changes for the file descriptors and updates `loop.changelist` |
| 85 | +// if `aplly_all` is `true` the changes are immediately applied |
| 86 | +fn (mut pv Picoev) apply_pending_changes(apply_all bool) int { |
| 87 | + mut total, mut nevents := 0, 0 |
| 88 | + |
| 89 | + for pv.loop.changed_fds != -1 { |
| 90 | + mut target := pv.file_descriptors[pv.loop.changed_fds] |
| 91 | + old_events := backend_get_old_events(target.backend) |
| 92 | + if target.events != old_events { |
| 93 | + // events have been changed |
| 94 | + if old_events != 0 { |
| 95 | + pv.ev_set(total, C.EV_DISABLE, old_events) |
| 96 | + total++ |
| 97 | + } |
| 98 | + if target.events != 0 { |
| 99 | + pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events)) |
| 100 | + total++ |
| 101 | + } |
| 102 | + // Apply the changes if the total changes exceed the changelist size |
| 103 | + if total + 1 >= pv.loop.changelist.len { |
| 104 | + nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, |
| 105 | + 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 | +[direct_array_access] |
| 125 | +fn (mut pv Picoev) update_events(fd int, events int) int { |
| 126 | + // check if fd is in range |
| 127 | + assert fd < max_fds |
| 128 | + |
| 129 | + mut target := pv.file_descriptors[fd] |
| 130 | + |
| 131 | + // initialize if adding the fd |
| 132 | + if events & picoev_add != 0 { |
| 133 | + target.backend = -1 |
| 134 | + } |
| 135 | + |
| 136 | + // return if nothing to do |
| 137 | + if (events == picoev_del && target.backend == -1) |
| 138 | + || (events != picoev_del && events & picoev_readwrite == target.events) { |
| 139 | + return 0 |
| 140 | + } |
| 141 | + |
| 142 | + // add to changed list if not yet being done |
| 143 | + if target.backend == -1 { |
| 144 | + target.backend = backend_build(pv.loop.changed_fds, target.events) |
| 145 | + pv.loop.changed_fds = fd |
| 146 | + } |
| 147 | + |
| 148 | + // update events |
| 149 | + target.events = u32(events & picoev_readwrite) |
| 150 | + // apply immediately if is a DELETE |
| 151 | + if events & picoev_del != 0 { |
| 152 | + pv.apply_pending_changes(true) |
| 153 | + } |
| 154 | + |
| 155 | + return 0 |
| 156 | +} |
| 157 | + |
| 158 | +[direct_array_access] |
| 159 | +fn (mut pv Picoev) poll_once(max_wait int) int { |
| 160 | + ts := C.timespec{ |
| 161 | + tv_sec: max_wait |
| 162 | + tv_nsec: 0 |
| 163 | + } |
| 164 | + |
| 165 | + mut total, mut nevents := 0, 0 |
| 166 | + // apply changes later when the callback is called. |
| 167 | + total = pv.apply_pending_changes(false) |
| 168 | + |
| 169 | + nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len, |
| 170 | + &ts) |
| 171 | + if nevents == -1 { |
| 172 | + // the errors we can only rescue |
| 173 | + assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR |
| 174 | + return -1 |
| 175 | + } |
| 176 | + |
| 177 | + for i := 0; i < nevents; i++ { |
| 178 | + event := pv.loop.events[i] |
| 179 | + target := pv.file_descriptors[event.ident] |
| 180 | + |
| 181 | + // changelist errors are fatal |
| 182 | + assert event.flags & C.EV_ERROR == 0 |
| 183 | + |
| 184 | + if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 { |
| 185 | + read_events := match int(event.filter) { |
| 186 | + C.EVFILT_READ { |
| 187 | + picoev_read |
| 188 | + } |
| 189 | + C.EVFILT_WRITE { |
| 190 | + picoev_write |
| 191 | + } |
| 192 | + else { |
| 193 | + 0 |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + // do callback! |
| 198 | + unsafe { target.cb(target.fd, read_events, &pv) } |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + return 0 |
| 203 | +} |
0 commit comments