Peter-Barrow/magic-rings-zig
No description provided.
ab5cf5feb936fa3b72c95d3ad0c0c67791937ba1
master
Magic-Rings-Zig is a high-performance ring buffer library for Zig that implements magic ring buffers with advanced struct-of-arrays support. It provides seamless wraparound access without modulo arithmetic and supports both single-field and multi-field ring buffers optimized for columnar data processing.
The library supports Linux, FreeBSD (via memfd_create
or shm_open
/shm_unlink
), and Windows platforms with cross-platform shared memory capabilities for inter-process communication, built on top of shared-memory-zig.
STATUS: Stable Core with Active Development - The core functionality is stable and battle-tested, with ongoing development of advanced features. Built and tested with Zig 0.14.0, tracking subsequent releases.
A typical ring buffer requires modulo arithmetic for every access:
Traditional Ring Buffer:
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <-- Buffer slots (indices)
+---+---+---+---+---+---+---+---+---+---+
^ ^
| |
HEAD TAIL
The magic ring buffer uses virtual memory mapping to create a seamless view:
Virtual Memory Layout:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
\__________ First Mapping __________/ \_____________Mirror ____________/
While maintaining a single physical memory allocation:
Physical Memory Mapping:
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+
\__________ Original Buffer ________/
This allows contiguous access across the buffer boundary without special wraparound handling.
Create a build.zig.zon
file:
.{
.name = "my-project",
.version = "0.0.0",
.dependencies = .{
.magic_rings = .{
.url = "https://github.com/Peter-Barrow/magic-rings-zig/archive/<git-ref-here>.tar.gz",
.hash = "...",
},
},
}
Add to your build.zig
:
const magic_rings = b.dependency("magic_rings", .{});
exe.root_module.addImport("magic_rings", magic_rings.module("magic_rings"));
const std = @import("std");
const magic_rings = @import("magic_rings");
// Create a ring buffer for u64 values with custom header
const Header = struct { sample_rate: f64, channels: u32 };
const Ring = magic_rings.MagicRingWithHeader(u64, Header);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Create a new ring buffer
var ring = try Ring.create("my_buffer", 1024, allocator);
defer ring.close() catch {};
// Set custom header fields
ring.header.sample_rate = 44100.0;
ring.header.channels = 2;
// Push data
_ = ring.push(42);
_ = ring.push(123);
// Get slices that seamlessly wrap around
const data = ring.sliceFromTail(2); // [42, 123]
// Push bulk data
const values = [_]u64{ 1, 2, 3, 4, 5 };
_ = ring.pushValues(&values);
// Define a point structure
const Point = struct {
x: f64,
y: f64,
timestamp: u64,
};
// Create multi-field ring buffer
const MultiRing = magic_rings.MultiMagicRing(Point, struct {});
var multi = try MultiRing.create("points", 1000, allocator);
defer multi.close() catch {};
// Push complete structs (gets decomposed into separate field buffers)
const point = Point{ .x = 1.5, .y = 2.5, .timestamp = 12345 };
_ = multi.push(point);
// Access individual fields efficiently
const x_values = multi.sliceField(.x, 0, 10); // Get 10 x coordinates
const recent_timestamps = multi.sliceFieldToHead(.timestamp, 5); // Last 5 timestamps
// Get synchronized slices across all fields
const recent_data = multi.sliceToHead(5);
// recent_data.x contains last 5 x values
// recent_data.y contains last 5 y values
// recent_data.timestamp contains last 5 timestamps
// Push columnar data efficiently
const columnar_data = MultiRing.Slice{
.x = &[_]f64{ 1.0, 2.0, 3.0 },
.y = &[_]f64{ 4.0, 5.0, 6.0 },
.timestamp = &[_]u64{ 100, 101, 102 },
};
_ = multi.pushSlice(columnar_data);
// Process 1: Create and write
var ring = try Ring.create("/shared_buffer", 1024, null);
_ = ring.push(42);
// Process 2: Open and read
var ring2 = try Ring.open("/shared_buffer", null);
const value = ring2.valueAt(0); // 42
Platform | Shared Memory | Anonymous Memory |
---|---|---|
Linux | shm_open / memfd_create |
memfd_create |
FreeBSD | shm_open |
memfd_create |
Windows | CreateFileMapping |
CreateFileMapping |
create(name, length, allocator)
- Create new ring bufferopen(name, allocator)
- Open existing ring buffer close()
- Clean up resourcespush(value)
- Add single elementpushValues(slice)
- Add multiple elementsslice(start, stop)
- Get range with wraparoundsliceFromTail(count)
- Get oldest elementssliceToHead(count)
- Get newest elementsvalueAt(index)
- Get element at logical indexsliceField(field, start, stop)
- Access specific fieldpushField(field, value)
- Push to specific fieldpush(struct_value)
- Push complete struct (decomposed)pushSlice(columnar_data)
- Efficient bulk columnar insertContributions are welcome! Please ensure:
MIT