| ... | @@ -168,6 +168,139 @@ test "null_writer" { | ... | @@ -168,6 +168,139 @@ test "null_writer" { |
| 168 | null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; | 168 | null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; |
| 169 | } | 169 | } |
| 170 | | 170 | |
| | 171 | pub fn poll( |
| | 172 | allocator: std.mem.Allocator, |
| | 173 | comptime StreamEnum: type, |
| | 174 | files: PollFiles(StreamEnum), |
| | 175 | ) Poller(StreamEnum) { |
| | 176 | const enum_fields = @typeInfo(StreamEnum).Enum.fields; |
| | 177 | var result: Poller(StreamEnum) = undefined; |
| | 178 | inline for (0..enum_fields.len) |i| { |
| | 179 | result.fifos[i] = .{ |
| | 180 | .allocator = allocator, |
| | 181 | .buf = &.{}, |
| | 182 | .head = 0, |
| | 183 | .count = 0, |
| | 184 | }; |
| | 185 | result.poll_fds[i] = .{ |
| | 186 | .fd = @field(files, enum_fields[i].name).file.handle, |
| | 187 | .events = switch (@field(files, enum_fields[i].name).direction) { |
| | 188 | .in => os.POLL.IN, |
| | 189 | .out => os.POLL.OUT, |
| | 190 | }, |
| | 191 | .revents = undefined, |
| | 192 | }; |
| | 193 | } |
| | 194 | return result; |
| | 195 | } |
| | 196 | |
| | 197 | pub fn Poller(comptime StreamEnum: type) type { |
| | 198 | return struct { |
| | 199 | const enum_fields = @typeInfo(StreamEnum).Enum.fields; |
| | 200 | const Fifo = std.fifo.LinearFifo(u8, .Dynamic); |
| | 201 | |
| | 202 | fifos: [enum_fields.len]Fifo, |
| | 203 | //directions: [enum_fields.len]PollFile.Direction, |
| | 204 | //handles: [enum_fields.len]std.fs.File.Handle, |
| | 205 | poll_fds: [enum_fields.len]std.os.pollfd, |
| | 206 | |
| | 207 | const Self = @This(); |
| | 208 | |
| | 209 | pub fn poll(self: *Self) !void { |
| | 210 | if (builtin.os.tag == .windows) { |
| | 211 | return pollWindows(self); |
| | 212 | } else { |
| | 213 | return pollPosix(self); |
| | 214 | } |
| | 215 | } |
| | 216 | |
| | 217 | pub inline fn fifo(self: *Self, comptime which: StreamEnum) *Fifo { |
| | 218 | return &self.fifos[@enumToInt(which)]; |
| | 219 | } |
| | 220 | |
| | 221 | pub fn done(self: Self) bool { |
| | 222 | for (self.poll_fds) |poll_fd| { |
| | 223 | if (poll_fd.fd != -1) return false; |
| | 224 | } else return true; |
| | 225 | } |
| | 226 | |
| | 227 | fn pollWindows(self: *Self) !void { |
| | 228 | _ = self; |
| | 229 | @compileError("TODO"); |
| | 230 | } |
| | 231 | |
| | 232 | fn pollPosix(self: *Self) !void { |
| | 233 | // We ask for ensureUnusedCapacity with this much extra space. This |
| | 234 | // has more of an effect on small reads because once the reads |
| | 235 | // start to get larger the amount of space an ArrayList will |
| | 236 | // allocate grows exponentially. |
| | 237 | const bump_amt = 512; |
| | 238 | |
| | 239 | const err_mask = os.POLL.ERR | os.POLL.NVAL | os.POLL.HUP; |
| | 240 | |
| | 241 | const events_len = try os.poll(&self.poll_fds, std.math.maxInt(i32)); |
| | 242 | if (events_len == 0) return; |
| | 243 | |
| | 244 | inline for (0..enum_fields.len) |i| { |
| | 245 | // Try reading whatever is available before checking the error |
| | 246 | // conditions. |
| | 247 | // It's still possible to read after a POLL.HUP is received, |
| | 248 | // always check if there's some data waiting to be read first. |
| | 249 | if (self.poll_fds[i].revents & os.POLL.IN != 0) { |
| | 250 | const q = &self.fifos[i]; |
| | 251 | const buf = try q.writableWithSize(bump_amt); |
| | 252 | const amt = try os.read(self.poll_fds[i].fd, buf); |
| | 253 | q.update(amt); |
| | 254 | std.debug.print("read {d} bytes\n", .{amt}); |
| | 255 | if (amt == 0) { |
| | 256 | // Remove the fd when the EOF condition is met. |
| | 257 | self.poll_fds[i].fd = -1; |
| | 258 | } |
| | 259 | } else if (self.poll_fds[i].revents & err_mask != 0) { |
| | 260 | // Exclude the fds that signaled an error. |
| | 261 | self.poll_fds[i].fd = -1; |
| | 262 | } else if (self.poll_fds[i].revents & os.POLL.OUT != 0) { |
| | 263 | const q = &self.fifos[i]; |
| | 264 | const amt = try os.write(self.poll_fds[i].fd, q.readableSlice(0)); |
| | 265 | q.discard(amt); |
| | 266 | if (amt == 0) { |
| | 267 | self.poll_fds[i].fd = -1; |
| | 268 | } |
| | 269 | } |
| | 270 | } |
| | 271 | } |
| | 272 | }; |
| | 273 | } |
| | 274 | |
| | 275 | /// Given an enum, returns a struct with fields of that enum, each field |
| | 276 | /// representing an I/O stream for polling. |
| | 277 | pub fn PollFiles(comptime StreamEnum: type) type { |
| | 278 | const enum_fields = @typeInfo(StreamEnum).Enum.fields; |
| | 279 | var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined; |
| | 280 | for (&struct_fields, enum_fields) |*struct_field, enum_field| { |
| | 281 | struct_field.* = .{ |
| | 282 | .name = enum_field.name, |
| | 283 | .type = PollFile, |
| | 284 | .default_value = null, |
| | 285 | .is_comptime = false, |
| | 286 | .alignment = @alignOf(PollFile), |
| | 287 | }; |
| | 288 | } |
| | 289 | return @Type(.{ .Struct = .{ |
| | 290 | .layout = .Auto, |
| | 291 | .fields = &struct_fields, |
| | 292 | .decls = &.{}, |
| | 293 | .is_tuple = false, |
| | 294 | } }); |
| | 295 | } |
| | 296 | |
| | 297 | pub const PollFile = struct { |
| | 298 | file: File, |
| | 299 | direction: Direction, |
| | 300 | |
| | 301 | pub const Direction = enum { in, out }; |
| | 302 | }; |
| | 303 | |
| 171 | test { | 304 | test { |
| 172 | _ = @import("io/bit_reader.zig"); | 305 | _ = @import("io/bit_reader.zig"); |
| 173 | _ = @import("io/bit_writer.zig"); | 306 | _ = @import("io/bit_writer.zig"); |