this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

irc: refactor irc.Message and memory allocations

Reduce memory allocations in the clients by using a thread-safe blocking
write buffer. This buffer limits the amount of in-flight messages we can
have, but is also statically allocated so we don't require allocating
each message (even if we won't be keeping it).

Reduce Message memory footprint by parsing on the fly - this should be
just about as fast, maybe faster since we *should* use the CPU cache
a bit better.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>

+465 -292
+4
build.zig
··· 56 56 .target = target, 57 57 .optimize = optimize, 58 58 }); 59 + exe_unit_tests.root_module.addImport("vaxis", vaxis_dep.module("vaxis")); 60 + exe_unit_tests.root_module.addImport("tls", tls_dep.module("tls")); 61 + exe_unit_tests.root_module.addImport("zeit", zeit_dep.module("zeit")); 62 + exe_unit_tests.root_module.addImport("ziglua", ziglua_dep.module("ziglua")); 59 63 60 64 const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); 61 65
+2 -2
build.zig.zon
··· 11 11 .hash = "12206c252ee00b9dd0214989dfa35a6eea29ac7d65d4053817f161f4c23b6e09dd89", 12 12 }, 13 13 .zeit = .{ 14 - .url = "git+https://github.com/rockorager/zeit?ref=v0.2.0#afa89d65999cdd31adb324b7c492598fd5e295b9", 15 - .hash = "1220db353accdd473cc9aea9a3b876a340fce917f2b0d24f7237a9d2793824509ced", 14 + .url = "git+https://github.com/rockorager/zeit?ref=main#4635734f9a0b36c978620a86c8e252256d8ade62", 15 + .hash = "1220f31bde4e818dcd82deb65c24af9c15ddab0ac5ef4600325fd51744c8c91356a5", 16 16 }, 17 17 .tls = .{ 18 18 .url = "git+https://github.com/ianic/tls.zig#36ae29b0e51fae564222767ffd5d7f60f9a8dd0b",
+105 -103
src/app.zig
··· 161 161 var loop: comlink.EventLoop = .{ .vaxis = &self.vx, .tty = &self.tty }; 162 162 try loop.init(); 163 163 try loop.start(); 164 - defer { 165 - while (loop.queue.tryPop()) |event| { 166 - switch (event) { 167 - .message => |msg| msg.deinit(self.alloc), 168 - else => {}, 169 - } 170 - } 171 - loop.stop(); 172 - } 164 + defer loop.stop(); 173 165 174 166 try self.vx.enterAltScreen(writer); 175 167 try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s); ··· 303 295 client.thread = try std.Thread.spawn(.{}, irc.Client.readLoop, .{ client, &loop }); 304 296 try self.clients.append(client); 305 297 }, 306 - .message => |msg| { 307 - var keep_message: bool = false; 308 - defer { 309 - if (!keep_message) msg.deinit(self.alloc); 310 - } 311 - switch (msg.command) { 298 + .irc => |irc_event| { 299 + const msg: irc.Message = .{ .bytes = irc_event.msg.slice() }; 300 + const client = irc_event.client; 301 + defer irc_event.msg.deinit(); 302 + switch (msg.command()) { 312 303 .unknown => {}, 313 304 .CAP => { 314 305 // syntax: <client> <ACK/NACK> :caps ··· 319 310 var cap_iter = mem.splitScalar(u8, caps, ' '); 320 311 while (cap_iter.next()) |cap| { 321 312 if (mem.eql(u8, ack_or_nak, "ACK")) { 322 - msg.client.ack(cap); 313 + client.ack(cap); 323 314 if (mem.eql(u8, cap, "sasl")) 324 - try msg.client.queueWrite("AUTHENTICATE PLAIN\r\n"); 315 + try client.queueWrite("AUTHENTICATE PLAIN\r\n"); 325 316 } else if (mem.eql(u8, ack_or_nak, "NAK")) { 326 317 log.debug("CAP not supported {s}", .{cap}); 327 318 } ··· 334 325 // AUTHENTICATE info 335 326 if (!mem.eql(u8, param, "+")) continue; 336 327 var buf: [4096]u8 = undefined; 337 - const config = msg.client.config; 328 + const config = client.config; 338 329 const sasl = try std.fmt.bufPrint( 339 330 &buf, 340 331 "{s}\x00{s}\x00{s}", ··· 351 342 "AUTHENTICATE {s}\r\n", 352 343 .{encoded}, 353 344 ); 354 - try msg.client.queueWrite(auth); 345 + try client.queueWrite(auth); 355 346 if (config.network_id) |id| { 356 347 const bind = try std.fmt.bufPrint( 357 348 &buf, 358 349 "BOUNCER BIND {s}\r\n", 359 350 .{id}, 360 351 ); 361 - try msg.client.queueWrite(bind); 352 + try client.queueWrite(bind); 362 353 } 363 - try msg.client.queueWrite("CAP END\r\n"); 354 + try client.queueWrite("CAP END\r\n"); 364 355 } 365 356 }, 366 357 .RPL_WELCOME => { ··· 378 369 "CHATHISTORY TARGETS timestamp={s} timestamp={s} 50\r\n", 379 370 .{ now_fmt, past_fmt }, 380 371 ); 381 - try msg.client.queueWrite(targets); 372 + try client.queueWrite(targets); 382 373 // on_connect callback 383 - try lua.onConnect(lua_state, msg.client); 374 + try lua.onConnect(lua_state, client); 384 375 }, 385 376 .RPL_YOURHOST => {}, 386 377 .RPL_CREATED => {}, ··· 391 382 _ = iter.next() orelse continue; // client 392 383 while (iter.next()) |token| { 393 384 if (mem.eql(u8, token, "WHOX")) 394 - msg.client.supports.whox = true 385 + client.supports.whox = true 395 386 else if (mem.startsWith(u8, token, "PREFIX")) { 396 387 const prefix = blk: { 397 388 const idx = mem.indexOfScalar(u8, token, ')') orelse ··· 399 390 break :blk try self.alloc.dupe(u8, "@+"); 400 391 break :blk try self.alloc.dupe(u8, token[idx + 1 ..]); 401 392 }; 402 - msg.client.supports.prefix = prefix; 393 + client.supports.prefix = prefix; 403 394 } 404 395 } 405 396 }, ··· 411 402 const channel_name = iter.next() orelse continue :loop; // channel 412 403 const topic = iter.next() orelse continue :loop; // topic 413 404 414 - var channel = try msg.client.getOrCreateChannel(channel_name); 405 + var channel = try client.getOrCreateChannel(channel_name); 415 406 if (channel.topic) |old_topic| { 416 407 self.alloc.free(old_topic); 417 408 } ··· 430 421 const nick = iter.next() orelse continue :loop; // nick 431 422 const flags = iter.next() orelse continue :loop; // nick 432 423 433 - const user_ptr = try msg.client.getOrCreateUser(nick); 424 + const user_ptr = try client.getOrCreateUser(nick); 434 425 if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true; 435 - var channel = try msg.client.getOrCreateChannel(channel_name); 426 + var channel = try client.getOrCreateChannel(channel_name); 436 427 437 428 const prefix = for (flags) |c| { 438 - if (std.mem.indexOfScalar(u8, msg.client.supports.prefix, c)) |_| { 429 + if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| { 439 430 break c; 440 431 } 441 432 } else ' '; ··· 450 441 const nick = iter.next() orelse continue; 451 442 const flags = iter.next() orelse continue; 452 443 453 - const user_ptr = try msg.client.getOrCreateUser(nick); 444 + const user_ptr = try client.getOrCreateUser(nick); 454 445 if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true; 455 - var channel = try msg.client.getOrCreateChannel(channel_name); 446 + var channel = try client.getOrCreateChannel(channel_name); 456 447 457 448 const prefix = for (flags) |c| { 458 - if (std.mem.indexOfScalar(u8, msg.client.supports.prefix, c)) |_| { 449 + if (std.mem.indexOfScalar(u8, client.supports.prefix, c)) |_| { 459 450 break c; 460 451 } 461 452 } else ' '; ··· 468 459 _ = iter.next() orelse continue :loop; // client 469 460 const channel_name = iter.next() orelse continue :loop; // channel 470 461 if (mem.eql(u8, channel_name, "*")) continue; 471 - var channel = try msg.client.getOrCreateChannel(channel_name); 462 + var channel = try client.getOrCreateChannel(channel_name); 472 463 channel.in_flight.who = false; 473 464 }, 474 465 .RPL_NAMREPLY => { ··· 478 469 _ = iter.next() orelse continue; // symbol 479 470 const channel_name = iter.next() orelse continue; // channel 480 471 const names = iter.next() orelse continue; 481 - var channel = try msg.client.getOrCreateChannel(channel_name); 472 + var channel = try client.getOrCreateChannel(channel_name); 482 473 var name_iter = std.mem.splitScalar(u8, names, ' '); 483 474 while (name_iter.next()) |name| { 484 - const nick, const prefix = for (msg.client.supports.prefix) |ch| { 475 + const nick, const prefix = for (client.supports.prefix) |ch| { 485 476 if (name[0] == ch) { 486 477 break .{ name[1..], name[0] }; 487 478 } ··· 491 482 log.debug("HAS PREFIX {s}", .{name}); 492 483 } 493 484 494 - const user_ptr = try msg.client.getOrCreateUser(nick); 485 + const user_ptr = try client.getOrCreateUser(nick); 495 486 496 487 try channel.addMember(user_ptr, .{ .prefix = prefix, .sort = false }); 497 488 } ··· 503 494 var iter = msg.paramIterator(); 504 495 _ = iter.next() orelse continue; // client 505 496 const channel_name = iter.next() orelse continue; // channel 506 - var channel = try msg.client.getOrCreateChannel(channel_name); 497 + var channel = try client.getOrCreateChannel(channel_name); 507 498 channel.in_flight.names = false; 508 499 }, 509 500 .BOUNCER => { ··· 513 504 const id = iter.next() orelse continue; 514 505 const attr = iter.next() orelse continue; 515 506 // check if we already have this network 516 - for (self.clients.items, 0..) |client, i| { 517 - if (client.config.network_id) |net_id| { 507 + for (self.clients.items, 0..) |cl, i| { 508 + if (cl.config.network_id) |net_id| { 518 509 if (mem.eql(u8, net_id, id)) { 519 510 if (mem.eql(u8, attr, "*")) { 520 511 // * means the network was 521 512 // deleted 522 - client.deinit(); 513 + cl.deinit(); 523 514 _ = self.clients.swapRemove(i); 524 515 } 525 516 continue :loop; ··· 534 525 break :name try self.alloc.dupe(u8, kv[n + 1 ..]); 535 526 } else null; 536 527 537 - var cfg = msg.client.config; 528 + var cfg = client.config; 538 529 cfg.network_id = try self.alloc.dupe(u8, id); 539 530 cfg.name = name; 540 531 loop.postEvent(.{ .connect = cfg }); ··· 542 533 } 543 534 }, 544 535 .AWAY => { 545 - const src = msg.source orelse continue :loop; 536 + const src = msg.source() orelse continue :loop; 546 537 var iter = msg.paramIterator(); 547 538 const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len; 548 - const user = try msg.client.getOrCreateUser(src[0..n]); 539 + const user = try client.getOrCreateUser(src[0..n]); 549 540 // If there are any params, the user is away. Otherwise 550 541 // they are back. 551 542 user.away = if (iter.next()) |_| true else false; ··· 558 549 const batch_type = iter.next() orelse continue; 559 550 if (mem.eql(u8, batch_type, "chathistory")) { 560 551 const target = iter.next() orelse continue; 561 - var channel = try msg.client.getOrCreateChannel(target); 552 + var channel = try client.getOrCreateChannel(target); 562 553 channel.at_oldest = true; 563 554 const duped_tag = try self.alloc.dupe(u8, tag[1..]); 564 - try msg.client.batches.put(duped_tag, channel); 555 + try client.batches.put(duped_tag, channel); 565 556 } 566 557 }, 567 558 '-' => { 568 - const key = msg.client.batches.getKey(tag[1..]) orelse continue; 569 - var chan = msg.client.batches.get(key) orelse @panic("key should exist here"); 559 + const key = client.batches.getKey(tag[1..]) orelse continue; 560 + var chan = client.batches.get(key) orelse @panic("key should exist here"); 570 561 chan.history_requested = false; 571 - _ = msg.client.batches.remove(key); 562 + _ = client.batches.remove(key); 572 563 self.alloc.free(key); 573 564 }, 574 565 else => {}, ··· 583 574 assert(target.len > 0); 584 575 if (target[0] == '#') continue; 585 576 586 - var channel = try msg.client.getOrCreateChannel(target); 587 - const user_ptr = try msg.client.getOrCreateUser(target); 588 - const me_ptr = try msg.client.getOrCreateUser(msg.client.config.nick); 577 + var channel = try client.getOrCreateChannel(target); 578 + const user_ptr = try client.getOrCreateUser(target); 579 + const me_ptr = try client.getOrCreateUser(client.config.nick); 589 580 try channel.addMember(user_ptr, .{}); 590 581 try channel.addMember(me_ptr, .{}); 591 582 // we set who_requested so we don't try to request ··· 597 588 "MARKREAD {s}\r\n", 598 589 .{channel.name}, 599 590 ); 600 - try msg.client.queueWrite(mark_read); 601 - try msg.client.requestHistory(.after, channel); 591 + try client.queueWrite(mark_read); 592 + try client.requestHistory(.after, channel); 602 593 }, 603 594 .JOIN => { 604 595 // get the user 605 - const src = msg.source orelse continue :loop; 596 + const src = msg.source() orelse continue :loop; 606 597 const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len; 607 - const user = try msg.client.getOrCreateUser(src[0..n]); 598 + const user = try client.getOrCreateUser(src[0..n]); 608 599 609 600 // get the channel 610 601 var iter = msg.paramIterator(); 611 602 const target = iter.next() orelse continue; 612 - var channel = try msg.client.getOrCreateChannel(target); 603 + var channel = try client.getOrCreateChannel(target); 613 604 614 605 // If it's our nick, we request chat history 615 - if (mem.eql(u8, user.nick, msg.client.config.nick)) 616 - try msg.client.requestHistory(.after, channel) 606 + if (mem.eql(u8, user.nick, client.config.nick)) 607 + try client.requestHistory(.after, channel) 617 608 else 618 609 try channel.addMember(user, .{}); 619 610 }, ··· 630 621 log.err("couldn't convert timestamp: {}", .{err}); 631 622 continue; 632 623 }; 633 - var channel = try msg.client.getOrCreateChannel(target); 624 + var channel = try client.getOrCreateChannel(target); 634 625 channel.last_read = last_read.unixTimestamp(); 635 626 const last_msg = channel.messages.getLastOrNull() orelse continue; 636 - const time = last_msg.time orelse continue; 637 - if (time.instant().unixTimestamp() > channel.last_read) 627 + const time = last_msg.time() orelse continue; 628 + if (time.unixTimestamp() > channel.last_read) 638 629 channel.has_unread = true 639 630 else 640 631 channel.has_unread = false; 641 632 }, 642 633 .PART => { 643 634 // get the user 644 - const src = msg.source orelse continue :loop; 635 + const src = msg.source() orelse continue :loop; 645 636 const n = std.mem.indexOfScalar(u8, src, '!') orelse src.len; 646 - const user = try msg.client.getOrCreateUser(src[0..n]); 637 + const user = try client.getOrCreateUser(src[0..n]); 647 638 648 639 // get the channel 649 640 var iter = msg.paramIterator(); 650 641 const target = iter.next() orelse continue; 651 642 652 - if (mem.eql(u8, user.nick, msg.client.config.nick)) { 653 - for (msg.client.channels.items, 0..) |channel, i| { 643 + if (mem.eql(u8, user.nick, client.config.nick)) { 644 + for (client.channels.items, 0..) |channel, i| { 654 645 if (!mem.eql(u8, channel.name, target)) continue; 655 - var chan = msg.client.channels.orderedRemove(i); 646 + var chan = client.channels.orderedRemove(i); 656 647 chan.deinit(self.alloc); 657 648 break; 658 649 } 659 650 } else { 660 - const channel = try msg.client.getOrCreateChannel(target); 651 + const channel = try client.getOrCreateChannel(target); 661 652 channel.removeMember(user); 662 653 } 663 654 }, 664 655 .PRIVMSG, .NOTICE => { 665 - keep_message = true; 666 656 // syntax: <target> :<message> 667 - var iter = msg.paramIterator(); 657 + const msg2: irc.Message = .{ 658 + .bytes = try self.alloc.dupe(u8, msg.bytes), 659 + }; 660 + var iter = msg2.paramIterator(); 668 661 const target = blk: { 669 662 const tgt = iter.next() orelse continue; 670 - if (mem.eql(u8, tgt, msg.client.config.nick)) { 663 + if (mem.eql(u8, tgt, client.config.nick)) { 671 664 // If the target is us, it likely has our 672 665 // hostname in it. 673 - const source = msg.source orelse continue; 666 + const source = msg2.source() orelse continue; 674 667 const n = mem.indexOfScalar(u8, source, '!') orelse source.len; 675 668 break :blk source[0..n]; 676 669 } else break :blk tgt; ··· 681 674 // from the batch start. We also never notify from a 682 675 // batched message. Batched messages also require 683 676 // sorting 684 - var tag_iter = msg.tagIterator(); 677 + var tag_iter = msg2.tagIterator(); 685 678 while (tag_iter.next()) |tag| { 686 679 if (mem.eql(u8, tag.key, "batch")) { 687 - const entry = msg.client.batches.getEntry(tag.value) orelse @panic("TODO"); 680 + const entry = client.batches.getEntry(tag.value) orelse @panic("TODO"); 688 681 var channel = entry.value_ptr.*; 689 - try channel.messages.append(msg); 682 + try channel.messages.append(msg2); 690 683 std.sort.insertion(irc.Message, channel.messages.items, {}, irc.Message.compareTime); 691 684 channel.at_oldest = false; 692 - const time = msg.time orelse continue; 693 - if (time.instant().unixTimestamp() > channel.last_read) { 685 + const time = msg2.time() orelse continue; 686 + if (time.unixTimestamp() > channel.last_read) { 694 687 channel.has_unread = true; 695 688 const content = iter.next() orelse continue; 696 - if (std.mem.indexOf(u8, content, msg.client.config.nick)) |_| { 689 + if (std.mem.indexOf(u8, content, client.config.nick)) |_| { 697 690 channel.has_unread_highlight = true; 698 691 } 699 692 } ··· 701 694 } 702 695 } else { 703 696 // standard handling 704 - var channel = try msg.client.getOrCreateChannel(target); 705 - try channel.messages.append(msg); 697 + var channel = try client.getOrCreateChannel(target); 698 + try channel.messages.append(msg2); 706 699 const content = iter.next() orelse continue; 707 700 var has_highlight = false; 708 701 { 709 702 const sender: []const u8 = blk: { 710 - const src = msg.source orelse break :blk ""; 703 + const src = msg2.source() orelse break :blk ""; 711 704 const l = std.mem.indexOfScalar(u8, src, '!') orelse 712 705 std.mem.indexOfScalar(u8, src, '@') orelse 713 706 src.len; 714 707 break :blk src[0..l]; 715 708 }; 716 - try lua.onMessage(lua_state, msg.client, channel.name, sender, content); 709 + try lua.onMessage(lua_state, client, channel.name, sender, content); 717 710 } 718 - if (std.mem.indexOf(u8, content, msg.client.config.nick)) |_| { 711 + if (std.mem.indexOf(u8, content, client.config.nick)) |_| { 719 712 var buf: [64]u8 = undefined; 720 - const title_or_err = if (msg.source) |source| 713 + const title_or_err = if (msg2.source()) |source| 721 714 std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, source }) 722 715 else 723 716 std.fmt.bufPrint(&buf, "{s}", .{channel.name}); ··· 729 722 try self.vx.notify(writer, title, content); 730 723 has_highlight = true; 731 724 } 732 - const time = msg.time orelse continue; 733 - if (time.instant().unixTimestamp() > channel.last_read) { 725 + const time = msg2.time() orelse continue; 726 + if (time.unixTimestamp() > channel.last_read) { 734 727 channel.has_unread_highlight = has_highlight; 735 728 channel.has_unread = true; 736 729 } ··· 882 875 } 883 876 884 877 fn draw(self: *App, input: *TextInput) !void { 878 + var arena = std.heap.ArenaAllocator.init(self.alloc); 879 + defer arena.deinit(); 880 + const allocator = arena.allocator(); 885 881 886 882 // reset window state 887 883 const win = self.vx.window(); ··· 1148 1144 }); 1149 1145 1150 1146 var prev_sender: ?[]const u8 = null; 1151 - var prev_time: ?zeit.Time = null; 1147 + var prev_time: ?zeit.Instant = null; 1152 1148 var i: usize = channel.messages.items.len -| self.state.messages.scroll_offset; 1153 1149 var y_off: usize = message_list_win.height -| 1; 1154 1150 while (i > 0) { ··· 1157 1153 // syntax: <target> <message> 1158 1154 1159 1155 const sender: []const u8 = blk: { 1160 - const src = message.source orelse break :blk ""; 1156 + const src = message.source() orelse break :blk ""; 1161 1157 const l = std.mem.indexOfScalar(u8, src, '!') orelse 1162 1158 std.mem.indexOfScalar(u8, src, '@') orelse 1163 1159 src.len; ··· 1169 1165 // more than some interval ago, then we'll print the 1170 1166 // previous sender and keep going 1171 1167 defer prev_sender = sender; 1172 - defer prev_time = message.time; 1168 + defer prev_time = message.time(); 1173 1169 1174 1170 const time_gap: bool = time_gap: { 1175 1171 // We are iterating through the messages in reverse, 1176 1172 // so the timestamp of the "previous" message is 1177 1173 // greater than the timestamp of the current message 1178 - const t1 = if (message.time) |t| t.instant().timestamp else break :time_gap false; 1179 - const t2 = if (prev_time) |t| t.instant().timestamp else break :time_gap false; 1174 + const t1 = if (message.time()) |t| t.timestamp else break :time_gap false; 1175 + const t2 = if (prev_time) |t| t.timestamp else break :time_gap false; 1180 1176 break :time_gap @divTrunc(t2 - t1, std.time.ns_per_min) > 5; 1181 1177 }; 1182 1178 ··· 1211 1207 1212 1208 if (y_off == 0) break; 1213 1209 1214 - try self.formatMessageContent(message); 1210 + try self.formatMessageContent(client, message); 1215 1211 defer self.content_segments.clearRetainingCapacity(); 1216 1212 // print the content first 1217 1213 const print_result = try message_offset_win.print(self.content_segments.items, .{ ··· 1279 1275 .width = .{ .limit = 6 }, 1280 1276 }); 1281 1277 1282 - if (message.time_buf) |buf| { 1278 + if (message.localTime(&self.tz)) |instant| { 1283 1279 var date: bool = false; 1284 - if (i != 0 and channel.messages.items[i - 1].time != null) { 1285 - const time = message.time.?; 1286 - const prev = channel.messages.items[i - 1].time.?; 1280 + const time = instant.time(); 1281 + var buf = try std.fmt.allocPrint( 1282 + allocator, 1283 + "{d:0>2}:{d:0>2}", 1284 + .{ time.hour, time.minute }, 1285 + ); 1286 + if (i != 0 and channel.messages.items[i - 1].time() != null) { 1287 + const prev = channel.messages.items[i - 1].localTime(&self.tz).?.time(); 1287 1288 if (time.day != prev.day) { 1288 1289 date = true; 1289 - message.time_buf = try std.fmt.bufPrint( 1290 - message.time_buf.?, 1290 + buf = try std.fmt.allocPrint( 1291 + allocator, 1291 1292 "{d:0>2}/{d:0>2}", 1292 1293 .{ @intFromEnum(time.month), time.day }, 1293 1294 ); ··· 1295 1296 } 1296 1297 if (i == 0) { 1297 1298 date = true; 1298 - message.time_buf = try std.fmt.bufPrint( 1299 - message.time_buf.?, 1299 + buf = try std.fmt.allocPrint( 1300 + allocator, 1300 1301 "{d:0>2}/{d:0>2}", 1301 - .{ @intFromEnum(message.time.?.month), message.time.?.day }, 1302 + .{ @intFromEnum(time.month), time.day }, 1302 1303 ); 1303 1304 } 1304 1305 const fg: vaxis.Color = if (date) ··· 1489 1490 } 1490 1491 1491 1492 /// generate vaxis.Segments for the message content 1492 - fn formatMessageContent(self: *App, msg: irc.Message) !void { 1493 + fn formatMessageContent(self: *App, client: *irc.Client, msg: irc.Message) !void { 1493 1494 const ColorState = enum { 1494 1495 ground, 1495 1496 fg, ··· 1505 1506 slash, 1506 1507 consume, 1507 1508 }; 1509 + 1508 1510 var iter = msg.paramIterator(); 1509 1511 _ = iter.next() orelse return error.InvalidMessage; 1510 1512 const content = iter.next() orelse return error.InvalidMessage; ··· 1521 1523 { 1522 1524 // get the user of this message 1523 1525 const sender: []const u8 = blk: { 1524 - const src = msg.source orelse break :blk ""; 1526 + const src = msg.source() orelse break :blk ""; 1525 1527 const l = std.mem.indexOfScalar(u8, src, '!') orelse 1526 1528 std.mem.indexOfScalar(u8, src, '@') orelse 1527 1529 src.len; 1528 1530 break :blk src[0..l]; 1529 1531 }; 1530 - const user = try msg.client.getOrCreateUser(sender); 1532 + const user = try client.getOrCreateUser(sender); 1531 1533 style.italic = true; 1532 1534 const user_style: vaxis.Style = .{ 1533 1535 .fg = user.color,
+6 -1
src/comlink.zig
··· 74 74 mouse: vaxis.Mouse, 75 75 winsize: vaxis.Winsize, 76 76 focus_out, 77 - message: irc.Message, 77 + irc: IrcEvent, 78 78 connect: irc.Client.Config, 79 79 redraw, 80 80 paste_start, 81 81 paste_end, 82 + }; 83 + 84 + pub const IrcEvent = struct { 85 + client: *irc.Client, 86 + msg: irc.Slice, 82 87 }; 83 88 84 89 /// An event our write thread will handle
+251 -186
src/irc.zig
··· 3 3 const tls = @import("tls"); 4 4 const vaxis = @import("vaxis"); 5 5 const zeit = @import("zeit"); 6 + const bytepool = @import("pool.zig"); 6 7 7 8 const testing = std.testing; 9 + 10 + pub const MessagePool = bytepool.BytePool(max_raw_msg_size * 4); 11 + pub const Slice = MessagePool.Slice; 8 12 9 13 const assert = std.debug.assert; 10 14 11 15 const log = std.log.scoped(.irc); 12 16 17 + /// maximum size message we can write 13 18 pub const maximum_message_size = 512; 19 + 20 + /// maximum size message we can receive 21 + const max_raw_msg_size = 512 + 8191; // see modernircdocs 14 22 15 23 pub const Buffer = union(enum) { 16 24 client: *Client, ··· 125 133 alloc.free(topic); 126 134 } 127 135 for (self.messages.items) |msg| { 128 - msg.deinit(alloc); 136 + alloc.free(msg.bytes); 129 137 } 130 138 self.messages.deinit(); 131 139 } ··· 139 147 var r: i64 = 0; 140 148 var iter = std.mem.reverseIterator(self.messages.items); 141 149 while (iter.next()) |msg| { 142 - if (msg.source) |source| { 150 + if (msg.source()) |source| { 143 151 const bang = std.mem.indexOfScalar(u8, source, '!') orelse source.len; 144 152 const nick = source[0..bang]; 145 153 146 - if (l == 0 and msg.time != null and std.mem.eql(u8, lhs.user.nick, nick)) { 154 + if (l == 0 and msg.time() != null and std.mem.eql(u8, lhs.user.nick, nick)) { 147 155 log.debug("L!!", .{}); 148 - l = msg.time.?.instant().unixTimestamp(); 149 - } else if (r == 0 and msg.time != null and std.mem.eql(u8, rhs.user.nick, nick)) 150 - r = msg.time.?.instant().unixTimestamp(); 156 + l = msg.time().?.unixTimestamp(); 157 + } else if (r == 0 and msg.time() != null and std.mem.eql(u8, rhs.user.nick, nick)) 158 + r = msg.time().?.unixTimestamp(); 151 159 } 152 160 if (l > 0 and r > 0) break; 153 161 } ··· 203 211 204 212 /// an irc message 205 213 pub const Message = struct { 206 - client: *Client, 207 - src: []const u8, 208 - tags: ?[]const u8, 209 - source: ?[]const u8, 210 - command: Command, 211 - params: ?[]const u8, 212 - time: ?zeit.Time = null, 213 - time_buf: ?[]u8 = null, 214 - 215 - pub fn compareTime(_: void, lhs: Message, rhs: Message) bool { 216 - const lhs_t = lhs.time orelse return false; 217 - const rhs_t = rhs.time orelse return false; 218 - 219 - const rhs_instant = rhs_t.instant(); 220 - const lhs_instant = lhs_t.instant(); 221 - 222 - return lhs_instant.timestamp < rhs_instant.timestamp; 223 - } 214 + bytes: []const u8, 224 215 225 216 pub const ParamIterator = struct { 226 217 params: ?[]const u8, ··· 266 257 }; 267 258 268 259 pub const TagIterator = struct { 269 - tags: ?[]const u8, 260 + tags: []const u8, 270 261 index: usize = 0, 271 262 272 263 // tags are a list of key=value pairs delimited by semicolons. 273 264 // key[=value] [; key[=value]] 274 265 pub fn next(self: *TagIterator) ?Tag { 275 - const tags = self.tags orelse return null; 276 - if (self.index >= tags.len) return null; 266 + if (self.index >= self.tags.len) return null; 277 267 278 268 // find next delimiter 279 - const end = std.mem.indexOfScalarPos(u8, tags, self.index, ';') orelse tags.len; 280 - var kv_delim = std.mem.indexOfScalarPos(u8, tags, self.index, '=') orelse end; 269 + const end = std.mem.indexOfScalarPos(u8, self.tags, self.index, ';') orelse self.tags.len; 270 + var kv_delim = std.mem.indexOfScalarPos(u8, self.tags, self.index, '=') orelse end; 281 271 // it's possible to have tags like this: 282 272 // @bot;account=botaccount;+typing=active 283 273 // where the first tag doesn't have a value. Guard against the ··· 287 277 defer self.index = end + 1; 288 278 289 279 return .{ 290 - .key = tags[self.index..kv_delim], 291 - .value = if (end == kv_delim) "" else tags[kv_delim + 1 .. end], 280 + .key = self.tags[self.index..kv_delim], 281 + .value = if (end == kv_delim) "" else self.tags[kv_delim + 1 .. end], 292 282 }; 293 283 } 294 284 }; 295 285 296 - pub fn init(src: []const u8, client: *Client) !Message { 286 + pub fn tagIterator(msg: Message) TagIterator { 287 + const src = msg.bytes; 288 + if (src[0] != '@') return .{ .tags = "" }; 289 + 290 + assert(src.len > 1); 291 + const n = std.mem.indexOfScalarPos(u8, src, 1, ' ') orelse src.len; 292 + return .{ .tags = src[1..n] }; 293 + } 294 + 295 + pub fn source(msg: Message) ?[]const u8 { 296 + const src = msg.bytes; 297 297 var i: usize = 0; 298 - const tags: ?[]const u8 = blk: { 299 - if (src[i] != '@') break :blk null; 300 - const n = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse return error.InvalidMessage; 301 - const tags = src[i + 1 .. n]; 302 - i = n; 303 - // consume whitespace 304 - while (i < src.len) : (i += 1) { 305 - if (src[i] != ' ') break; 306 - } 307 - break :blk tags; 308 - }; 309 298 310 - const instant: ?zeit.Time = blk: { 311 - if (tags == null) break :blk null; 312 - var tag_iter = TagIterator{ .tags = tags }; 313 - while (tag_iter.next()) |tag| { 314 - if (!std.mem.eql(u8, tag.key, "time")) continue; 315 - const instant = try zeit.instant(.{ 316 - .source = .{ .iso8601 = tag.value }, 317 - .timezone = &client.app.tz, 318 - }); 299 + // get past tags 300 + if (src[0] == '@') { 301 + assert(src.len > 1); 302 + i = std.mem.indexOfScalarPos(u8, src, 1, ' ') orelse return null; 303 + } 319 304 320 - break :blk instant.time(); 321 - } else break :blk null; 322 - }; 305 + // consume whitespace 306 + while (i < src.len) : (i += 1) { 307 + if (src[i] != ' ') break; 308 + } 323 309 324 - const source: ?[]const u8 = blk: { 325 - if (src[i] != ':') break :blk null; 326 - const n = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse return error.InvalidMessage; 327 - const source = src[i + 1 .. n]; 328 - i = n; 329 - // consume whitespace 330 - while (i < src.len) : (i += 1) { 331 - if (src[i] != ' ') break; 332 - } 333 - break :blk source; 334 - }; 310 + // Start of source 311 + if (src[i] == ':') { 312 + assert(src.len > i); 313 + i += 1; 314 + const end = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse src.len; 315 + return src[i..end]; 316 + } 335 317 336 - const n = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse src.len; 318 + return null; 319 + } 337 320 338 - const cmd = Command.parse(src[i..n]); 321 + pub fn command(msg: Message) Command { 322 + const src = msg.bytes; 323 + var i: usize = 0; 339 324 340 - i = n; 325 + // get past tags 326 + if (src[0] == '@') { 327 + assert(src.len > 1); 328 + i = std.mem.indexOfScalarPos(u8, src, 1, ' ') orelse return .unknown; 329 + } 330 + // consume whitespace 331 + while (i < src.len) : (i += 1) { 332 + if (src[i] != ' ') break; 333 + } 341 334 335 + // get past source 336 + if (src[i] == ':') { 337 + assert(src.len > i); 338 + i += 1; 339 + i = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse return .unknown; 340 + } 342 341 // consume whitespace 343 342 while (i < src.len) : (i += 1) { 344 343 if (src[i] != ' ') break; 345 344 } 346 345 347 - const params: ?[]const u8 = if (i == src.len) null else src[i..src.len]; 348 - 349 - return .{ 350 - .src = src, 351 - .tags = tags, 352 - .source = source, 353 - .command = cmd, 354 - .params = params, 355 - .client = client, 356 - .time = instant, 357 - }; 358 - } 359 - 360 - pub fn deinit(msg: Message, alloc: std.mem.Allocator) void { 361 - alloc.free(msg.src); 362 - if (msg.time_buf) |buf| alloc.free(buf); 346 + assert(src.len > i); 347 + // Find next space 348 + const end = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse src.len; 349 + return Command.parse(src[i..end]); 363 350 } 364 351 365 352 pub fn paramIterator(msg: Message) ParamIterator { 366 - return .{ .params = msg.params }; 367 - } 353 + const src = msg.bytes; 354 + var i: usize = 0; 368 355 369 - pub fn tagIterator(msg: Message) TagIterator { 370 - return .{ .tags = msg.tags }; 371 - } 356 + // get past tags 357 + if (src[0] == '@') { 358 + i = std.mem.indexOfScalarPos(u8, src, 0, ' ') orelse return .{ .params = "" }; 359 + } 360 + // consume whitespace 361 + while (i < src.len) : (i += 1) { 362 + if (src[i] != ' ') break; 363 + } 372 364 373 - test "simple message" { 374 - const msg = try Message.init("JOIN"); 365 + // get past source 366 + if (src[i] == ':') { 367 + assert(src.len > i); 368 + i += 1; 369 + i = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse return .{ .params = "" }; 370 + } 371 + // consume whitespace 372 + while (i < src.len) : (i += 1) { 373 + if (src[i] != ' ') break; 374 + } 375 375 376 - try testing.expect(msg.tags == null); 377 - try testing.expect(msg.source == null); 378 - try testing.expectEqualStrings("JOIN", msg.command); 379 - try testing.expect(msg.params == null); 380 - } 381 - 382 - test "simple message with extra whitespace" { 383 - const msg = try Message.init("JOIN "); 376 + // get past command 377 + i = std.mem.indexOfScalarPos(u8, src, i, ' ') orelse return .{ .params = "" }; 384 378 385 - try testing.expect(msg.tags == null); 386 - try testing.expect(msg.source == null); 387 - try testing.expectEqualStrings("JOIN", msg.command); 388 - try testing.expect(msg.params == null); 379 + assert(src.len > i); 380 + return .{ .params = src[i + 1 ..] }; 389 381 } 390 382 391 - test "well formed message with tags, source, params" { 392 - const msg = try Message.init("@key=value :example.chat JOIN abc def"); 393 - 394 - try testing.expectEqualStrings("key=value", msg.tags.?); 395 - try testing.expectEqualStrings("example.chat", msg.source.?); 396 - try testing.expectEqualStrings("JOIN", msg.command); 397 - try testing.expectEqualStrings("abc def", msg.params.?); 383 + /// Returns the value of the tag 'key', if present 384 + pub fn getTag(self: Message, key: []const u8) ?[]const u8 { 385 + var tag_iter = self.tagIterator(); 386 + while (tag_iter.next()) |tag| { 387 + if (!std.mem.eql(u8, tag.key, key)) continue; 388 + return tag.value; 389 + } 390 + return null; 398 391 } 399 392 400 - test "message with tags, source, params and extra whitespace" { 401 - const msg = try Message.init("@key=value :example.chat JOIN abc def"); 393 + pub fn time(self: Message) ?zeit.Instant { 394 + const val = self.getTag("time") orelse return null; 402 395 403 - try testing.expectEqualStrings("key=value", msg.tags.?); 404 - try testing.expectEqualStrings("example.chat", msg.source.?); 405 - try testing.expectEqualStrings("JOIN", msg.command); 406 - try testing.expectEqualStrings("abc def", msg.params.?); 407 - } 396 + // Return null if we can't parse the time 397 + const instant = zeit.instant(.{ 398 + .source = .{ .iso8601 = val }, 399 + .timezone = &zeit.utc, 400 + }) catch return null; 408 401 409 - test "param iterator: simple list" { 410 - var iter: Message.ParamIterator = .{ .params = "a b c" }; 411 - var i: usize = 0; 412 - while (iter.next()) |param| { 413 - switch (i) { 414 - 0 => try testing.expectEqualStrings("a", param), 415 - 1 => try testing.expectEqualStrings("b", param), 416 - 2 => try testing.expectEqualStrings("c", param), 417 - else => return error.TooManyParams, 418 - } 419 - i += 1; 420 - } 421 - try testing.expect(i == 3); 402 + return instant; 422 403 } 423 404 424 - test "param iterator: trailing colon" { 425 - var iter: Message.ParamIterator = .{ .params = "* LS :" }; 426 - var i: usize = 0; 427 - while (iter.next()) |param| { 428 - switch (i) { 429 - 0 => try testing.expectEqualStrings("*", param), 430 - 1 => try testing.expectEqualStrings("LS", param), 431 - 2 => try testing.expectEqualStrings("", param), 432 - else => return error.TooManyParams, 433 - } 434 - i += 1; 435 - } 436 - try testing.expect(i == 3); 405 + pub fn localTime(self: Message, tz: *const zeit.TimeZone) ?zeit.Instant { 406 + const utc = self.time() orelse return null; 407 + return utc.in(tz); 437 408 } 438 409 439 - test "param iterator: colon" { 440 - var iter: Message.ParamIterator = .{ .params = "* LS :sasl multi-prefix" }; 441 - var i: usize = 0; 442 - while (iter.next()) |param| { 443 - switch (i) { 444 - 0 => try testing.expectEqualStrings("*", param), 445 - 1 => try testing.expectEqualStrings("LS", param), 446 - 2 => try testing.expectEqualStrings("sasl multi-prefix", param), 447 - else => return error.TooManyParams, 448 - } 449 - i += 1; 450 - } 451 - try testing.expect(i == 3); 452 - } 410 + pub fn compareTime(_: void, lhs: Message, rhs: Message) bool { 411 + const lhs_time = lhs.time() orelse return false; 412 + const rhs_time = rhs.time() orelse return false; 453 413 454 - test "param iterator: colon and leading colon" { 455 - var iter: Message.ParamIterator = .{ .params = "* LS ::)" }; 456 - var i: usize = 0; 457 - while (iter.next()) |param| { 458 - switch (i) { 459 - 0 => try testing.expectEqualStrings("*", param), 460 - 1 => try testing.expectEqualStrings("LS", param), 461 - 2 => try testing.expectEqualStrings(":)", param), 462 - else => return error.TooManyParams, 463 - } 464 - i += 1; 465 - } 466 - try testing.expect(i == 3); 414 + return lhs_time.timestamp < rhs_time.timestamp; 467 415 } 468 416 }; 469 417 ··· 615 563 616 564 var buf: [16_384]u8 = undefined; 617 565 566 + // 4x max size. We will almost always be *way* under our maximum size, so we will have a 567 + // lot more potential messages than just 4 568 + var pool: MessagePool = .{}; 569 + pool.init(); 570 + 618 571 errdefer |err| { 619 572 log.err("client: {s} error: {}", .{ self.config.network_id.?, err }); 620 573 } ··· 654 607 var i: usize = 0; 655 608 while (std.mem.indexOfPos(u8, buf[0 .. n + start], i, "\r\n")) |idx| { 656 609 defer i = idx + 2; 657 - const line = try self.alloc.dupe(u8, buf[i..idx]); 610 + const buffer = pool.alloc(idx - i); 611 + // const line = try self.alloc.dupe(u8, buf[i..idx]); 612 + @memcpy(buffer.slice(), buf[i..idx]); 658 613 assert(std.mem.eql(u8, buf[idx .. idx + 2], "\r\n")); 659 - log.debug("[<-{s}] {s}", .{ self.config.name orelse self.config.server, line }); 660 - var msg = Message.init(line, self) catch |err| { 661 - log.err("[{s}] invalid message {}", .{ self.config.name orelse self.config.server, err }); 662 - self.alloc.free(line); 663 - continue; 664 - }; 665 - if (msg.time) |time| { 666 - msg.time_buf = try std.fmt.allocPrint( 667 - self.alloc, 668 - "{d:0>2}:{d:0>2}", 669 - .{ time.hour, time.minute }, 670 - ); 671 - } 672 - loop.postEvent(.{ .message = msg }); 614 + log.debug("[<-{s}] {s}", .{ self.config.name orelse self.config.server, buffer.slice() }); 615 + loop.postEvent(.{ .irc = .{ .client = self, .msg = buffer } }); 673 616 } 674 617 if (i != n) { 675 618 // we had a part of a line read. Copy it to the beginning of the ··· 981 924 try testing.expect(caseFold("a", "A")); 982 925 try testing.expect(caseFold("aBcDeFgH", "abcdefgh")); 983 926 } 927 + 928 + test "simple message" { 929 + const msg: Message = .{ .bytes = "JOIN" }; 930 + try testing.expect(msg.command() == .JOIN); 931 + } 932 + 933 + test "simple message with extra whitespace" { 934 + const msg: Message = .{ .bytes = "JOIN " }; 935 + try testing.expect(msg.command() == .JOIN); 936 + } 937 + 938 + test "well formed message with tags, source, params" { 939 + const msg: Message = .{ .bytes = "@key=value :example.chat JOIN abc def" }; 940 + 941 + var tag_iter = msg.tagIterator(); 942 + const tag = tag_iter.next(); 943 + try testing.expect(tag != null); 944 + try testing.expectEqualStrings("key", tag.?.key); 945 + try testing.expectEqualStrings("value", tag.?.value); 946 + try testing.expect(tag_iter.next() == null); 947 + 948 + const source = msg.source(); 949 + try testing.expect(source != null); 950 + try testing.expectEqualStrings("example.chat", source.?); 951 + try testing.expect(msg.command() == .JOIN); 952 + 953 + var param_iter = msg.paramIterator(); 954 + const p1 = param_iter.next(); 955 + const p2 = param_iter.next(); 956 + try testing.expect(p1 != null); 957 + try testing.expect(p2 != null); 958 + try testing.expectEqualStrings("abc", p1.?); 959 + try testing.expectEqualStrings("def", p2.?); 960 + 961 + try testing.expect(param_iter.next() == null); 962 + } 963 + 964 + test "message with tags, source, params and extra whitespace" { 965 + const msg: Message = .{ .bytes = "@key=value :example.chat JOIN abc def" }; 966 + 967 + var tag_iter = msg.tagIterator(); 968 + const tag = tag_iter.next(); 969 + try testing.expect(tag != null); 970 + try testing.expectEqualStrings("key", tag.?.key); 971 + try testing.expectEqualStrings("value", tag.?.value); 972 + try testing.expect(tag_iter.next() == null); 973 + 974 + const source = msg.source(); 975 + try testing.expect(source != null); 976 + try testing.expectEqualStrings("example.chat", source.?); 977 + try testing.expect(msg.command() == .JOIN); 978 + 979 + var param_iter = msg.paramIterator(); 980 + const p1 = param_iter.next(); 981 + const p2 = param_iter.next(); 982 + try testing.expect(p1 != null); 983 + try testing.expect(p2 != null); 984 + try testing.expectEqualStrings("abc", p1.?); 985 + try testing.expectEqualStrings("def", p2.?); 986 + 987 + try testing.expect(param_iter.next() == null); 988 + } 989 + 990 + test "param iterator: simple list" { 991 + var iter: Message.ParamIterator = .{ .params = "a b c" }; 992 + var i: usize = 0; 993 + while (iter.next()) |param| { 994 + switch (i) { 995 + 0 => try testing.expectEqualStrings("a", param), 996 + 1 => try testing.expectEqualStrings("b", param), 997 + 2 => try testing.expectEqualStrings("c", param), 998 + else => return error.TooManyParams, 999 + } 1000 + i += 1; 1001 + } 1002 + try testing.expect(i == 3); 1003 + } 1004 + 1005 + test "param iterator: trailing colon" { 1006 + var iter: Message.ParamIterator = .{ .params = "* LS :" }; 1007 + var i: usize = 0; 1008 + while (iter.next()) |param| { 1009 + switch (i) { 1010 + 0 => try testing.expectEqualStrings("*", param), 1011 + 1 => try testing.expectEqualStrings("LS", param), 1012 + 2 => try testing.expectEqualStrings("", param), 1013 + else => return error.TooManyParams, 1014 + } 1015 + i += 1; 1016 + } 1017 + try testing.expect(i == 3); 1018 + } 1019 + 1020 + test "param iterator: colon" { 1021 + var iter: Message.ParamIterator = .{ .params = "* LS :sasl multi-prefix" }; 1022 + var i: usize = 0; 1023 + while (iter.next()) |param| { 1024 + switch (i) { 1025 + 0 => try testing.expectEqualStrings("*", param), 1026 + 1 => try testing.expectEqualStrings("LS", param), 1027 + 2 => try testing.expectEqualStrings("sasl multi-prefix", param), 1028 + else => return error.TooManyParams, 1029 + } 1030 + i += 1; 1031 + } 1032 + try testing.expect(i == 3); 1033 + } 1034 + 1035 + test "param iterator: colon and leading colon" { 1036 + var iter: Message.ParamIterator = .{ .params = "* LS ::)" }; 1037 + var i: usize = 0; 1038 + while (iter.next()) |param| { 1039 + switch (i) { 1040 + 0 => try testing.expectEqualStrings("*", param), 1041 + 1 => try testing.expectEqualStrings("LS", param), 1042 + 2 => try testing.expectEqualStrings(":)", param), 1043 + else => return error.TooManyParams, 1044 + } 1045 + i += 1; 1046 + } 1047 + try testing.expect(i == 3); 1048 + }
+97
src/pool.zig
··· 1 + const std = @import("std"); 2 + 3 + const Condition = std.Thread.Condition; 4 + const Mutex = std.Thread.Mutex; 5 + 6 + pub fn BytePool(comptime size: usize) type { 7 + return struct { 8 + const Self = @This(); 9 + 10 + pub const Slice = struct { 11 + idx: usize, 12 + len: usize, 13 + pool: *Self, 14 + 15 + /// Frees resources associated with Buffer 16 + pub fn deinit(self: Slice) void { 17 + self.pool.mutex.lock(); 18 + defer self.pool.mutex.unlock(); 19 + @memset(self.pool.free_list[self.idx .. self.idx + self.len], true); 20 + // Signal that we may have capacity now 21 + self.pool.buffer_deinited.signal(); 22 + } 23 + 24 + /// Returns the actual slice of this buffer 25 + pub fn slice(self: Slice) []u8 { 26 + return self.pool.buffer[self.idx .. self.idx + self.len]; 27 + } 28 + }; 29 + 30 + buffer: [size]u8 = undefined, 31 + free_list: [size]bool = undefined, 32 + mutex: Mutex = .{}, 33 + /// The index of the next potentially available byte 34 + next_idx: usize = 0, 35 + 36 + buffer_deinited: Condition = .{}, 37 + 38 + pub fn init(self: *Self) void { 39 + @memset(&self.free_list, true); 40 + } 41 + 42 + /// Get a buffer of size n. Blocks until one is available 43 + pub fn alloc(self: *Self, n: usize) Slice { 44 + std.debug.assert(n < size); 45 + self.mutex.lock(); 46 + defer self.mutex.unlock(); 47 + while (true) { 48 + if (self.getBuffer(n)) |buf| return buf; 49 + self.buffer_deinited.wait(&self.mutex); 50 + } 51 + } 52 + 53 + fn getBuffer(self: *Self, n: usize) ?Slice { 54 + var start: usize = self.next_idx; 55 + var did_wrap: bool = false; 56 + while (true) { 57 + if (start + n >= self.buffer.len) { 58 + if (did_wrap) return null; 59 + did_wrap = true; 60 + start = 0; 61 + } 62 + 63 + const next_true = std.mem.indexOfScalarPos(bool, &self.free_list, start, true) orelse { 64 + if (did_wrap) return null; 65 + did_wrap = true; 66 + start = 0; 67 + continue; 68 + }; 69 + 70 + if (next_true + n >= self.buffer.len) { 71 + if (did_wrap) return null; 72 + did_wrap = true; 73 + start = 0; 74 + continue; 75 + } 76 + 77 + // Get our potential slice 78 + const maybe_slice = self.free_list[next_true .. next_true + n]; 79 + // Check that the entire thing is true 80 + if (std.mem.indexOfScalar(bool, maybe_slice, false)) |idx| { 81 + // We have a false, increment and look again 82 + start = next_true + idx + 1; 83 + continue; 84 + } 85 + // Set this slice in the free_list as not free 86 + @memset(maybe_slice, false); 87 + // Update next_idx 88 + self.next_idx = next_true + n; 89 + return .{ 90 + .idx = next_true, 91 + .len = n, 92 + .pool = self, 93 + }; 94 + } 95 + } 96 + }; 97 + }