Project template

This commit is contained in:
2025-02-02 16:41:39 +01:00
commit b98288605b
50 changed files with 16702 additions and 0 deletions

21
vendor/zpool/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 zig-gamedev contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

96
vendor/zpool/README.md vendored Normal file
View File

@@ -0,0 +1,96 @@
# [zpool](https://github.com/zig-gamedev/zpool)
Generic pool & handle implementation for Zig. Based on [Andre Weissflog's "Handles Are The Better Pointers"](https://floooh.github.io/2018/06/17/handles-vs-pointers.html).
Exposing API resources using pools and handles is a common way to avoid exposing
implementation details to calling code and providing some insulation against
stale references in data structures maintained by the caller.
When the caller is provided a handle instead of an opaque pointer, the API
implementation is free to move resources around, replace them, and even discard
them.
```zig
Pool(index_bits: u8, cycle_bits: u8, TResource: type, TColumns: type)
Handle(index_bits: u8, cycle_bits: u8, TResource: type)
```
The generic `Pool` type has configurable bit distribution for the
`Handle`'s `index`/`cycle` fields, and supports multiple columns of data to
be indexed by a handle, using `std.MultiArrayList` to store all of the pool
data in a single memory allocation. The `TResource` parameter ensures the pool
and handle types can be distinct types even when other parameters are the same.
## Getting started
Example `build.zig`:
```zig
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{ ... });
const zpool = b.dependency("zpool", .{});
exe.root_module.addImport("zpool", zpool.module("root"));
}
```
Now in your code you may import and use `zpool`:
```zig
const Pool = @import("zpool").Pool;
const ImagePtr = graphics.Image;
const ImageInfo = graphics.ImageInfo;
pub const ImagePool = Pool(16, 16, ImagePtr, struct {
ptr: ImagePtr,
info: ImageInfo,
});
pub const ImageHandle = ImagePool.Handle;
```
```zig
var imagePool = ImagePool.initMaxCapacity(allocator);
defer pool.deinit();
```
```zig
pub fn acquireImage(info: ImageInfo) !ImageHandle {
const handle : ImageHandle = try imagePool.add(.{
.ptr = graphics.createImage(info),
.info = info,
});
return handle;
}
pub fn drawImage(handle: ImageHandle) !void {
// get the stored ImagePtr
const ptr : ImagePtr = try imagePool.getColumn(handle, .ptr);
graphics.drawImage(ptr);
}
pub fn resizeImage(handle: ImageHandle, width: u16, height: u16) !void {
// get a pointer to the stored ImageInfo
const info : *ImageInfo = try imagePool.getColumnPtr(handle, .info);
const old_width = info.width;
const old_height = info.height;
const old_pixels = // allocate memory to store old pixels
// get the stored ImagePtr
const ptr = try imagePool.getColumn(handle, .ptr);
graphics.readPixels(ptr, old_pixels);
const new_pixels = // allocate memory to store new pixels
super_eagle.resizeImage(
old_width, old_height, old_pixels,
new_width, new_height, new_pixels);
graphics.writePixels(ptr, new_width, new_height, new_pixels);
// update the stored ImageInfo
info.width = new_width;
info.height = new_height;
}
```

22
vendor/zpool/build.zig vendored Normal file
View File

@@ -0,0 +1,22 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
_ = b.addModule("root", .{
.root_source_file = b.path("src/main.zig"),
});
const test_step = b.step("test", "Run zpool tests");
const tests = b.addTest(.{
.name = "zpool-tests",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(tests);
test_step.dependOn(&b.addRunArtifact(tests).step);
}

11
vendor/zpool/build.zig.zon vendored Normal file
View File

@@ -0,0 +1,11 @@
.{
.name = "zpool",
.version = "0.11.0-dev",
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"README.md",
"LICENSE",
},
}

174
vendor/zpool/src/embedded_ring_queue.zig vendored Normal file
View File

@@ -0,0 +1,174 @@
const std = @import("std");
pub fn EmbeddedRingQueue(comptime TElement: type) type {
const assert = std.debug.assert;
return struct {
const Self = @This();
pub const Error = error{
Empty,
Full,
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub const Element = TElement;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
head: usize = 0,
tail: usize = 0,
storage: []Element = &.{},
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn init(buffer: []Element) Self {
return .{ .storage = buffer };
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn capacity(self: Self) usize {
return self.storage.len;
}
pub fn len(self: Self) usize {
return self.tail -% self.head;
}
pub fn empty(self: Self) bool {
return self.len() == 0;
}
pub fn full(self: Self) bool {
return self.len() == self.capacity();
}
pub fn clear(self: *Self) void {
self.head = 0;
self.tail = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn enqueue(self: *Self, value: Element) Error!void {
if (self.enqueueIfNotFull(value)) {
return;
}
return Error.Full;
}
pub fn dequeue(self: *Self) Error!Element {
var value: Element = undefined;
if (self.dequeueIfNotEmpty(&value)) {
return value;
}
return Error.Empty;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn enqueueIfNotFull(self: *Self, value: Element) bool {
if (self.full()) {
return false;
}
self.enqueueUnchecked(value);
return true;
}
pub fn dequeueIfNotEmpty(self: *Self, value: *Element) bool {
if (self.empty()) {
return false;
}
self.dequeueUnchecked(value);
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn enqueueAssumeNotFull(self: *Self, value: Element) void {
assert(!self.full());
self.enqueueUnchecked(value);
}
pub fn dequeueAssumeNotEmpty(self: *Self) Element {
assert(!self.empty());
var value: Element = undefined;
self.dequeueUnchecked(&value);
return value;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn enqueueUnchecked(self: *Self, value: Element) void {
const tail_index = self.tail % self.storage.len;
self.storage[tail_index] = value;
self.tail +%= 1;
}
pub fn dequeueUnchecked(self: *Self, value: *Element) void {
const head_index = self.head % self.storage.len;
value.* = self.storage[head_index];
self.head +%= 1;
}
};
}
//------------------------------------------------------------------------------
const expectEqual = std.testing.expectEqual;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
test "EmbeddedRingQueue basics" {
var buffer: [16]usize = undefined;
var queue = EmbeddedRingQueue(usize).init(buffer[0..]);
try expectEqual(buffer.len, queue.capacity());
try expectEqual(@as(usize, 0), queue.len());
try expectEqual(true, queue.empty());
try expectEqual(false, queue.full());
for (buffer, 0..) |_, i| {
try expectEqual(i, queue.len());
try queue.enqueue(i);
try expectEqual(i, buffer[i]);
}
try expectEqual(buffer.len, queue.capacity());
try expectEqual(buffer.len, queue.len());
try expectEqual(false, queue.empty());
try expectEqual(true, queue.full());
for (buffer, 0..) |_, i| {
try expectEqual(buffer.len - i, queue.len());
const j = try queue.dequeue();
try expectEqual(i, j);
}
try expectEqual(buffer.len, queue.capacity());
try expectEqual(@as(usize, 0), queue.len());
try expectEqual(true, queue.empty());
try expectEqual(false, queue.full());
for (buffer, 0..) |_, i| {
try expectEqual(i, queue.len());
try queue.enqueue(i);
try expectEqual(i, buffer[i]);
}
try expectEqual(buffer.len, queue.capacity());
try expectEqual(buffer.len, queue.len());
try expectEqual(false, queue.empty());
try expectEqual(true, queue.full());
queue.clear();
try expectEqual(buffer.len, queue.capacity());
try expectEqual(@as(usize, 0), queue.len());
try expectEqual(true, queue.empty());
try expectEqual(false, queue.full());
}
//------------------------------------------------------------------------------

278
vendor/zpool/src/handle.zig vendored Normal file
View File

@@ -0,0 +1,278 @@
const std = @import("std");
/// Returns a struct consisting of an array `index` and a semi-unique `cycle`,
/// which exists to distinguish handles with the same array `index`.
///
/// The `cycle` value is only unique within the incremental period of an
/// unsigned integer with `cycle_bits`, so a larger number of `cycle_bits`
/// provides a larger scope of identifiable conflicts between handles for the
/// same `index`.
///
/// `Handle` is generic because while the `{ index, cycle }` pattern is widely
/// applicable, a good distribution of bits between `index` and `cycle` and the
/// overall size of a handle are highly dependent on the lifecycle of the
/// resource being identified by a handle and the systems consuming handles.
///
/// Reasonable values for `index_bits` depend on the maximum number of
/// uniquely identifiable resources your API will to identify with handles.
/// Generally this is directly tied to the length of the array(s) in which
/// you will store data to be referenced by a handle's `index`.
///
/// Reasonable values for `cycle_bits` depend on the frequency with which your
/// API expects to be issuing handles, and how many cycles of your application
/// are likely to elapse before an expired handle will likely no longer be
/// retained by the API caller's data structures.
///
/// For example, a `Handle(16, 16)` may be sufficient for a GPU resource like
/// a texture or buffer, where 64k instances of that resource is a reasonable
/// upper bound.
///
/// A `Handle(22, 10)` may be more appropriate to identify an entity in a
/// system where we can safely assume that 4 million entities, is a lot, and
/// that API callers can discover and discard expired entity handles within
/// 1024 frames of an entity being destroyed and its handle's `index` being
/// reissued for use by a distinct entity.
///
/// `TResource` identifies type of resource referenced by a handle, and
/// provides a type-safe distinction between two otherwise equivalently
/// configured `Handle` types, such as:
/// * `const BufferHandle = Handle(16, 16, Buffer);`
/// * `const TextureHandle = Handle(16, 16, Texture);`
///
/// The total size of a handle will always be the size of an addressable
/// unsigned integer of type `u8`, `u16`, `u32`, `u64`, `u128`, or `u256`.
pub fn Handle(
comptime index_bits: u8,
comptime cycle_bits: u8,
comptime TResource: type,
) type {
if (index_bits == 0) @compileError("index_bits must be greater than 0");
if (cycle_bits == 0) @compileError("cycle_bits must be greater than 0");
const id_bits: u16 = @as(u16, index_bits) + @as(u16, cycle_bits);
const Id = switch (id_bits) {
8 => u8,
16 => u16,
32 => u32,
64 => u64,
128 => u128,
256 => u256,
else => @compileError("index_bits + cycle_bits must sum to exactly " ++
"8, 16, 32, 64, 128, or 256 bits"),
};
const field_bits = @max(index_bits, cycle_bits);
const utils = @import("utils.zig");
const UInt = utils.UInt;
const AddressableUInt = utils.AddressableUInt;
return extern struct {
const Self = @This();
const HandleType = Self;
const IndexType = UInt(index_bits);
const CycleType = UInt(cycle_bits);
const HandleUnion = extern union {
id: Id,
bits: packed struct {
cycle: CycleType, // least significant bits
index: IndexType, // most significant bits
},
};
pub const Resource = TResource;
pub const AddressableCycle = AddressableUInt(field_bits);
pub const AddressableIndex = AddressableUInt(field_bits);
pub const max_cycle = ~@as(CycleType, 0);
pub const max_index = ~@as(IndexType, 0);
pub const max_count = @as(Id, max_index - 1) + 2;
id: Id = 0,
pub const nil = Self{ .id = 0 };
pub fn init(i: IndexType, c: CycleType) Self {
const u = HandleUnion{ .bits = .{
.cycle = c,
.index = i,
} };
return .{ .id = u.id };
}
pub fn cycle(self: Self) CycleType {
const u = HandleUnion{ .id = self.id };
return u.bits.cycle;
}
pub fn index(self: Self) IndexType {
const u = HandleUnion{ .id = self.id };
return u.bits.index;
}
/// Unpacks the `index` and `cycle` bit fields that comprise
/// `Handle.id` into an `AddressableHandle`, which stores
/// the `index` and `cycle` values in pointer-addressable fields.
pub fn addressable(self: Self) AddressableHandle {
const u = HandleUnion{ .id = self.id };
return .{
.cycle = u.bits.cycle,
.index = u.bits.index,
};
}
/// When you want to directly access the `index` and `cycle` of a
/// handle, first convert it to an `AddressableHandle` by calling
/// `Handle.addressable()`.
/// An `AddressableHandle` can be converted back into a "compact"
/// `Handle` by calling `AddressableHandle.compact()`.
pub const AddressableHandle = struct {
cycle: AddressableCycle = 0,
index: AddressableIndex = 0,
/// Returns the corresponding `Handle`
pub fn handle(self: AddressableHandle) HandleType {
const u = HandleUnion{ .bits = .{
.cycle = @as(CycleType, @intCast(self.cycle)),
.index = @as(IndexType, @intCast(self.index)),
} };
return .{ .id = u.id };
}
};
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
const n = @typeName(Resource);
const a = self.addressable();
return writer.print("{s}[{}#{}]", .{ n, a.index, a.cycle });
}
};
}
////////////////////////////////////////////////////////////////////////////////
test "Handle sizes and alignments" {
const expectEqual = std.testing.expectEqual;
{
const H = Handle(4, 4, void);
try expectEqual(@sizeOf(u8), @sizeOf(H));
try expectEqual(@alignOf(u8), @alignOf(H));
try expectEqual(4, @bitSizeOf(H.IndexType));
try expectEqual(4, @bitSizeOf(H.CycleType));
try expectEqual(8, @bitSizeOf(H.AddressableIndex));
try expectEqual(8, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u16), @sizeOf(A));
try expectEqual(@alignOf(u8), @alignOf(A));
}
{
const H = Handle(6, 2, void);
try expectEqual(@sizeOf(u8), @sizeOf(H));
try expectEqual(@alignOf(u8), @alignOf(H));
try expectEqual(6, @bitSizeOf(H.IndexType));
try expectEqual(2, @bitSizeOf(H.CycleType));
try expectEqual(8, @bitSizeOf(H.AddressableIndex));
try expectEqual(8, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u16), @sizeOf(A));
try expectEqual(@alignOf(u8), @alignOf(A));
}
{
const H = Handle(8, 8, void);
try expectEqual(@sizeOf(u16), @sizeOf(H));
try expectEqual(@alignOf(u16), @alignOf(H));
try expectEqual(8, @bitSizeOf(H.IndexType));
try expectEqual(8, @bitSizeOf(H.CycleType));
try expectEqual(8, @bitSizeOf(H.AddressableIndex));
try expectEqual(8, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u16), @sizeOf(A));
try expectEqual(@alignOf(u8), @alignOf(A));
}
{
const H = Handle(12, 4, void);
try expectEqual(@sizeOf(u16), @sizeOf(H));
try expectEqual(@alignOf(u16), @alignOf(H));
try expectEqual(12, @bitSizeOf(H.IndexType));
try expectEqual(4, @bitSizeOf(H.CycleType));
try expectEqual(16, @bitSizeOf(H.AddressableIndex));
try expectEqual(16, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u32), @sizeOf(A));
try expectEqual(@alignOf(u16), @alignOf(A));
}
{
const H = Handle(16, 16, void);
try expectEqual(@sizeOf(u32), @sizeOf(H));
try expectEqual(@alignOf(u32), @alignOf(H));
try expectEqual(16, @bitSizeOf(H.IndexType));
try expectEqual(16, @bitSizeOf(H.CycleType));
try expectEqual(16, @bitSizeOf(H.AddressableIndex));
try expectEqual(16, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u32), @sizeOf(A));
try expectEqual(@alignOf(u16), @alignOf(A));
}
{
const H = Handle(22, 10, void);
try expectEqual(@sizeOf(u32), @sizeOf(H));
try expectEqual(@alignOf(u32), @alignOf(H));
try expectEqual(22, @bitSizeOf(H.IndexType));
try expectEqual(10, @bitSizeOf(H.CycleType));
try expectEqual(32, @bitSizeOf(H.AddressableIndex));
try expectEqual(32, @bitSizeOf(H.AddressableCycle));
const A = H.AddressableHandle;
try expectEqual(@sizeOf(u64), @sizeOf(A));
try expectEqual(@alignOf(u32), @alignOf(A));
}
}
////////////////////////////////////////////////////////////////////////////////
test "Handle sort order" {
const expect = std.testing.expect;
const handle = Handle(4, 4, void).init;
const a = handle(0, 3);
const b = handle(1, 1);
// id order is consistent with index order, even when cycle order is not
try expect(a.id < b.id);
try expect(a.index() < b.index());
try expect(a.cycle() > b.cycle());
}
////////////////////////////////////////////////////////////////////////////////
test "Handle.format()" {
const bufPrint = std.fmt.bufPrint;
const expectEqualStrings = std.testing.expectEqualStrings;
const Foo = struct {};
const H = Handle(12, 4, Foo);
const h = H.init(0, 1);
var buffer = [_]u8{0} ** 128;
const s = try bufPrint(buffer[0..], "{}", .{h});
try expectEqualStrings("handle.test.Handle.format().Foo[0#1]", s);
}

8
vendor/zpool/src/main.zig vendored Normal file
View File

@@ -0,0 +1,8 @@
pub const Handle = @import("handle.zig").Handle;
pub const Pool = @import("pool.zig").Pool;
// ensure transitive closure of test coverage
comptime {
_ = Handle;
_ = Pool;
}

1430
vendor/zpool/src/pool.zig vendored Normal file

File diff suppressed because it is too large Load Diff

106
vendor/zpool/src/utils.zig vendored Normal file
View File

@@ -0,0 +1,106 @@
const std = @import("std");
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pub fn asTypeId(comptime typeInfo: std.builtin.Type) std.builtin.TypeId {
return @as(std.builtin.TypeId, typeInfo);
}
pub fn typeIdOf(comptime T: type) std.builtin.TypeId {
return asTypeId(@typeInfo(T));
}
pub fn isStruct(comptime T: type) bool {
return typeIdOf(T) == std.builtin.TypeId.@"struct";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// UInt(bits) returns an unsigned integer type of the requested bit width.
pub fn UInt(comptime bits: u8) type {
const unsigned = std.builtin.Signedness.unsigned;
return @Type(.{ .int = .{ .signedness = unsigned, .bits = bits } });
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Returns an unsigned integer type with ***at least*** `min_bits`,
/// that is also large enough to be addressable by a normal pointer.
/// The returned type will always be one of the following:
/// * `u8`
/// * `u16`
/// * `u32`
/// * `u64`
/// * `u128`
/// * `u256`
pub fn AddressableUInt(comptime min_bits: u8) type {
return switch (min_bits) {
0...8 => u8,
9...16 => u16,
17...32 => u32,
33...64 => u64,
65...128 => u128,
129...255 => u256,
};
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Given: `Struct = struct { foo: u32, bar: u64 }`
/// Returns: `StructOfSlices = struct { foo: []u32, bar: []u64 }`
pub fn StructOfSlices(comptime Struct: type) type {
const StructField = std.builtin.Type.StructField;
// same number of fields in the new struct
const struct_fields = @typeInfo(Struct).@"struct".fields;
comptime var struct_of_slices_fields: []const StructField = &.{};
inline for (struct_fields) |struct_field| {
// u32 -> []u32
const element_type = struct_field.type;
const slice_type_info = std.builtin.Type{
.pointer = .{
.child = element_type,
.alignment = @alignOf(element_type),
.size = .slice,
.is_const = false,
.is_volatile = false,
.address_space = .generic,
.is_allowzero = false,
.sentinel_ptr = null,
},
};
const FieldType = @Type(slice_type_info);
// Struct.foo: u32 -> StructOfSlices.foo : []u32
const slice_field = std.builtin.Type.StructField{
.name = struct_field.name,
.type = FieldType,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(FieldType),
};
// Struct.foo: u32 -> StructOfSlices.foo : []u32
struct_of_slices_fields = struct_of_slices_fields ++ [1]StructField{slice_field};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = struct_of_slices_fields,
.decls = &.{},
.is_tuple = false,
} });
}
test "StructOfSlices" {
const expectEqual = std.testing.expectEqual;
const Struct = struct { a: u16, b: u16, c: u16 };
try expectEqual(@sizeOf(u16) * 3, @sizeOf(Struct));
const SOS = StructOfSlices(Struct);
try expectEqual(@sizeOf([]u16) * 3, @sizeOf(SOS));
}