a modern tui library written in zig
at v0.1.0 3.6 kB view raw
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}