a modern tui library written in zig
1const std = @import("std");
2const assert = std.debug.assert;
3const atomic = std.atomic;
4const Futex = std.Thread.Futex;
5
6const log = std.log.scoped(.queue);
7
8/// Thread safe. Fixed size. Blocking push and pop.
9pub fn Queue(
10 comptime T: type,
11 comptime size: usize,
12) type {
13 return struct {
14 buf: [size]T = undefined,
15
16 read_index: usize = 0,
17 write_index: usize = 0,
18
19 mutex: std.Thread.Mutex = .{},
20 // blocks when the buffer is full or empty
21 futex: atomic.Value(u32) = atomic.Value(u32).init(0),
22
23 const Self = @This();
24
25 pub fn pop(self: *Self) T {
26 self.mutex.lock();
27 defer self.mutex.unlock();
28 if (self.isEmpty()) {
29 // If we don't have any items, we unlock and wait
30 self.mutex.unlock();
31 Futex.wait(&self.futex, 0);
32 // regain our lock
33 self.mutex.lock();
34 }
35 if (self.isFull()) {
36 // If we are full, wake up the push
37 Futex.wake(&self.futex, 1);
38 }
39 const i = self.read_index;
40 self.read_index += 1;
41 self.read_index = self.read_index % self.buf.len;
42 return self.buf[i];
43 }
44
45 /// push an item into the queue. Blocks until the message has been put
46 /// in the queue
47 pub fn push(self: *Self, item: T) void {
48 self.mutex.lock();
49 defer self.mutex.unlock();
50 if (self.isFull()) {
51 self.mutex.unlock();
52 Futex.wait(&self.futex, 0);
53 self.mutex.lock();
54 }
55 if (self.isEmpty()) {
56 Futex.wake(&self.futex, 1);
57 }
58 const i = self.write_index;
59 self.write_index += 1;
60 self.write_index = self.write_index % self.buf.len;
61 self.buf[i] = item;
62 }
63
64 /// push an item into the queue. If the queue is full, this returns
65 /// immediately and the item has not been place in the queue
66 pub fn tryPush(self: *Self, item: T) bool {
67 self.mutex.lock();
68 if (self.isFull()) {
69 self.mutex.unlock();
70 return false;
71 }
72 self.mutex.unlock();
73 self.push(item);
74 return true;
75 }
76
77 /// Returns `true` if the ring buffer is empty and `false` otherwise.
78 fn isEmpty(self: Self) bool {
79 return self.write_index == self.read_index;
80 }
81
82 /// Returns `true` if the ring buffer is full and `false` otherwise.
83 fn isFull(self: Self) bool {
84 return self.mask2(self.write_index + self.buf.len) == self.read_index;
85 }
86
87 /// Returns the length
88 fn len(self: Self) usize {
89 const wrap_offset = 2 * self.buf.len * @intFromBool(self.write_index < self.read_index);
90 const adjusted_write_index = self.write_index + wrap_offset;
91 return adjusted_write_index - self.read_index;
92 }
93
94 /// Returns `index` modulo the length of the backing slice.
95 fn mask(self: Self, index: usize) usize {
96 return index % self.buf.len;
97 }
98
99 /// Returns `index` modulo twice the length of the backing slice.
100 fn mask2(self: Self, index: usize) usize {
101 return index % (2 * self.buf.len);
102 }
103 };
104}
105
106test "Queue: simple push / pop" {
107 var queue: Queue(u8, 16) = .{};
108 queue.push(1);
109 const pop = queue.pop();
110 try std.testing.expectEqual(1, pop);
111}