| ... | ... | @@ -9,30 +9,77 @@ const debug = std.debug; |
| 9 | 9 | const assert = debug.assert; |
| 10 | 10 | const testing = std.testing; |
| 11 | 11 | |
| 12 | | pub fn FixedSizeFifo(comptime T: type) type { |
| 12 | pub const FifoBufferType = union(enum) { |
| 13 | /// The buffer is internal to the fifo; it is of the specified size. |
| 14 | Static: usize, |
| 15 | |
| 16 | /// The buffer is passed as a slice to the initialiser. |
| 17 | Slice, |
| 18 | |
| 19 | /// The buffer is managed dynamically using a `mem.Allocator`. |
| 20 | Dynamic, |
| 21 | }; |
| 22 | |
| 23 | pub fn FixedSizeFifo( |
| 24 | comptime T: type, |
| 25 | comptime buffer_type: FifoBufferType, |
| 26 | ) type { |
| 13 | 27 | const autoalign = false; |
| 14 | 28 | |
| 15 | | const powers_of_two = true; |
| 29 | const powers_of_two = switch (buffer_type) { |
| 30 | .Static => std.math.isPowerOfTwo(buffer_type.Static), |
| 31 | .Slice => false, // Any size slice could be passed in |
| 32 | .Dynamic => true, // This could be configurable in future |
| 33 | }; |
| 16 | 34 | |
| 17 | 35 | return struct { |
| 18 | | allocator: *Allocator, |
| 19 | | buf: []T, |
| 36 | allocator: if (buffer_type == .Dynamic) *Allocator else void, |
| 37 | buf: if (buffer_type == .Static) [buffer_type.Static]T else []T, |
| 20 | 38 | head: usize, |
| 21 | 39 | count: usize, |
| 22 | 40 | |
| 23 | 41 | const Self = @This(); |
| 24 | 42 | |
| 25 | | pub fn init(allocator: *Allocator) Self { |
| 26 | | return Self{ |
| 27 | | .allocator = allocator, |
| 28 | | .buf = [_]T{}, |
| 29 | | .head = 0, |
| 30 | | .count = 0, |
| 31 | | }; |
| 32 | | } |
| 43 | // Type of Self argument for slice operations. |
| 44 | // If buffer is inline (Static) then we need to ensure we haven't |
| 45 | // returned a slice into a copy on the stack |
| 46 | const SliceSelfArg = if (buffer_type == .Static) *Self else Self; |
| 47 | |
| 48 | pub usingnamespace switch (buffer_type) { |
| 49 | .Static => struct { |
| 50 | pub fn init() Self { |
| 51 | return .{ |
| 52 | .allocator = {}, |
| 53 | .buf = undefined, |
| 54 | .head = 0, |
| 55 | .count = 0, |
| 56 | }; |
| 57 | } |
| 58 | }, |
| 59 | .Slice => struct { |
| 60 | pub fn init(buf: []T) Self { |
| 61 | return .{ |
| 62 | .allocator = {}, |
| 63 | .buf = buf, |
| 64 | .head = 0, |
| 65 | .count = 0, |
| 66 | }; |
| 67 | } |
| 68 | }, |
| 69 | .Dynamic => struct { |
| 70 | pub fn init(allocator: *Allocator) Self { |
| 71 | return .{ |
| 72 | .allocator = allocator, |
| 73 | .buf = [_]T{}, |
| 74 | .head = 0, |
| 75 | .count = 0, |
| 76 | }; |
| 77 | } |
| 78 | }, |
| 79 | }; |
| 33 | 80 | |
| 34 | 81 | pub fn deinit(self: *Self) void { |
| 35 | | self.allocator.free(self.buf); |
| 82 | if (buffer_type == .Dynamic) self.allocator.free(self.buf); |
| 36 | 83 | self.* = undefined; |
| 37 | 84 | } |
| 38 | 85 | |
| ... | ... | @@ -63,18 +110,24 @@ pub fn FixedSizeFifo(comptime T: type) type { |
| 63 | 110 | /// Reduce allocated capacity to `size`. |
| 64 | 111 | pub fn shrink(self: *Self, size: usize) void { |
| 65 | 112 | assert(size >= self.count); |
| 66 | | self.realign(); |
| 67 | | self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) { |
| 68 | | error.OutOfMemory => return, // no problem, capacity is still correct then. |
| 69 | | }; |
| 113 | if (buffer_type == .Dynamic) { |
| 114 | self.realign(); |
| 115 | self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) { |
| 116 | error.OutOfMemory => return, // no problem, capacity is still correct then. |
| 117 | }; |
| 118 | } |
| 70 | 119 | } |
| 71 | 120 | |
| 72 | 121 | /// Ensure that the buffer can fit at least `size` items |
| 73 | 122 | pub fn ensureCapacity(self: *Self, size: usize) !void { |
| 74 | 123 | if (self.buf.len >= size) return; |
| 75 | | self.realign(); |
| 76 | | const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size; |
| 77 | | self.buf = try self.allocator.realloc(self.buf, new_size); |
| 124 | if (buffer_type == .Dynamic) { |
| 125 | self.realign(); |
| 126 | const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size; |
| 127 | self.buf = try self.allocator.realloc(self.buf, new_size); |
| 128 | } else { |
| 129 | return error.OutOfMemory; |
| 130 | } |
| 78 | 131 | } |
| 79 | 132 | |
| 80 | 133 | /// Makes sure at least `size` items are unused |
| ... | ... | @@ -90,7 +143,7 @@ pub fn FixedSizeFifo(comptime T: type) type { |
| 90 | 143 | } |
| 91 | 144 | |
| 92 | 145 | /// Returns a writable slice from the 'read' end of the fifo |
| 93 | | fn readableSliceMut(self: Self, offset: usize) []T { |
| 146 | fn readableSliceMut(self: SliceSelfArg, offset: usize) []T { |
| 94 | 147 | if (offset > self.count) return [_]T{}; |
| 95 | 148 | |
| 96 | 149 | const start = self.head + offset; |
| ... | ... | @@ -107,7 +160,7 @@ pub fn FixedSizeFifo(comptime T: type) type { |
| 107 | 160 | } |
| 108 | 161 | |
| 109 | 162 | /// Returns a readable slice from `offset` |
| 110 | | pub fn readableSlice(self: Self, offset: usize) []const T { |
| 163 | pub fn readableSlice(self: SliceSelfArg, offset: usize) []const T { |
| 111 | 164 | return self.readableSliceMut(offset); |
| 112 | 165 | } |
| 113 | 166 | |
| ... | ... | @@ -173,7 +226,7 @@ pub fn FixedSizeFifo(comptime T: type) type { |
| 173 | 226 | |
| 174 | 227 | /// Returns the first section of writable buffer |
| 175 | 228 | /// Note that this may be of length 0 |
| 176 | | pub fn writableSlice(self: Self, offset: usize) []T { |
| 229 | pub fn writableSlice(self: SliceSelfArg, offset: usize) []T { |
| 177 | 230 | if (offset > self.buf.len) return [_]T{}; |
| 178 | 231 | |
| 179 | 232 | const tail = self.head + offset + self.count; |
| ... | ... | @@ -279,10 +332,8 @@ pub fn FixedSizeFifo(comptime T: type) type { |
| 279 | 332 | }; |
| 280 | 333 | } |
| 281 | 334 | |
| 282 | | const ByteFifo = FixedSizeFifo(u8); |
| 283 | | |
| 284 | | test "ByteFifo" { |
| 285 | | var fifo = ByteFifo.init(debug.global_allocator); |
| 335 | test "FixedSizeFifo(u8, .Dynamic)" { |
| 336 | var fifo = FixedSizeFifo(u8, .Dynamic).init(debug.global_allocator); |
| 286 | 337 | defer fifo.deinit(); |
| 287 | 338 | |
| 288 | 339 | try fifo.write("HELLO"); |
| ... | ... | @@ -348,18 +399,26 @@ test "ByteFifo" { |
| 348 | 399 | |
| 349 | 400 | test "FixedSizeFifo" { |
| 350 | 401 | inline for ([_]type{ u1, u8, u16, u64 }) |T| { |
| 351 | | var fifo = FixedSizeFifo(T).init(debug.global_allocator); |
| 352 | | defer fifo.deinit(); |
| 353 | | |
| 354 | | try fifo.write([_]T{ 0, 1, 1, 0, 1 }); |
| 355 | | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 356 | | |
| 357 | | { |
| 358 | | testing.expectEqual(@as(T, 0), try fifo.readItem()); |
| 359 | | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 360 | | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 361 | | testing.expectEqual(@as(T, 0), try fifo.readItem()); |
| 362 | | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 402 | inline for ([_]FifoBufferType{ FifoBufferType{ .Static = 32 }, .Slice, .Dynamic }) |bt| { |
| 403 | const FifoType = FixedSizeFifo(T, bt); |
| 404 | var buf: if (bt == .Slice) [32]T else void = undefined; |
| 405 | var fifo = switch (bt) { |
| 406 | .Static => FifoType.init(), |
| 407 | .Slice => FifoType.init(buf[0..]), |
| 408 | .Dynamic => FifoType.init(debug.global_allocator), |
| 409 | }; |
| 410 | defer fifo.deinit(); |
| 411 | |
| 412 | try fifo.write([_]T{ 0, 1, 1, 0, 1 }); |
| 413 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 414 | |
| 415 | { |
| 416 | testing.expectEqual(@as(T, 0), try fifo.readItem()); |
| 417 | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 418 | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 419 | testing.expectEqual(@as(T, 0), try fifo.readItem()); |
| 420 | testing.expectEqual(@as(T, 1), try fifo.readItem()); |
| 421 | } |
| 363 | 422 | } |
| 364 | 423 | } |
| 365 | 424 | } |