Compare commits
23 Commits
dc839e098c
...
99b8cf1d8e
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b8cf1d8e | |||
| af5f035830 | |||
| af8d725e0c | |||
| ff87d58319 | |||
| 70076f2268 | |||
| 8dbac71007 | |||
| c14fc89c74 | |||
| 94c08ff3b6 | |||
| 084512258c | |||
| 1093209328 | |||
| 08217a0256 | |||
| 0f6d1a0842 | |||
| fe7f845359 | |||
| 043d3e1c22 | |||
| 753e2a850d | |||
|
|
e262e00829 | ||
| cbbe7f2ea1 | |||
| 3ba53ac724 | |||
| dc203cd700 | |||
| 91020a2741 | |||
| d2719a77f0 | |||
| 9f9ae95f54 | |||
| e7c8d89aa9 |
@@ -114,5 +114,3 @@ typedef uint64_t uintptr_t;
|
||||
|
||||
#define WCHAR_MIN (0)
|
||||
#define WCHAR_MAX (0x10FFFF)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn isCompatible(comptime ZigType: type, c_type: Type) bool {
|
||||
.unsigned_long => zig_int.signedness == .unsigned and zig_int.bits == 64,
|
||||
.unsigned_long_long => zig_int.signedness == .unsigned and zig_int.bits == 64,
|
||||
|
||||
.char => zig_int.bits = 8,
|
||||
.char => zig_int.bits == 8,
|
||||
|
||||
else => false,
|
||||
},
|
||||
|
||||
27
packages/datastore/build.zig
Normal file
27
packages/datastore/build.zig
Normal file
@@ -0,0 +1,27 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const vm_dep = b.dependency("vecmath", .{
|
||||
.target = target,
|
||||
});
|
||||
const vm_mod = vm_dep.module("vecmath");
|
||||
|
||||
const mod = b.addModule("datastore", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.imports = &.{
|
||||
.{ .name = "vecmath", .module = vm_mod },
|
||||
},
|
||||
});
|
||||
|
||||
const mod_tests = b.addTest(.{
|
||||
.root_module = mod,
|
||||
});
|
||||
|
||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_mod_tests.step);
|
||||
}
|
||||
16
packages/datastore/build.zig.zon
Normal file
16
packages/datastore/build.zig.zon
Normal file
@@ -0,0 +1,16 @@
|
||||
.{
|
||||
.name = .datastore,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
.fingerprint = 0x193de38ef5a38d70,
|
||||
.dependencies = .{
|
||||
.vecmath = .{
|
||||
.path = "../vecmath",
|
||||
},
|
||||
},
|
||||
}
|
||||
156
packages/datastore/src/ColumnType.zig
Normal file
156
packages/datastore/src/ColumnType.zig
Normal file
@@ -0,0 +1,156 @@
|
||||
const std = @import("std");
|
||||
const vm = @import("vecmath");
|
||||
|
||||
pub const ColumnType = enum {
|
||||
/// 1 byte, 0x00 or 0x01
|
||||
bool,
|
||||
/// 1 bit, bit-packed
|
||||
bit,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
f32,
|
||||
f64,
|
||||
/// RGBA u8
|
||||
color,
|
||||
/// RGBA f16
|
||||
color_hdr,
|
||||
/// Stored as ×8 AOSOA
|
||||
mat3x2,
|
||||
/// Stored as ×8 AOSOA
|
||||
mat4x4,
|
||||
/// Stored as ×8 AOSOA
|
||||
complex,
|
||||
/// Stored as ×8 AOSOA
|
||||
quaternion,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec2,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec2i,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec3,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec3i,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec4,
|
||||
/// Stored as ×8 AOSOA
|
||||
vec4i,
|
||||
/// 8 B (1 B len ++ <= 7 B str)
|
||||
str8,
|
||||
/// 16 B (1 B len ++ <= 15 B str)
|
||||
str16,
|
||||
/// 32 B (1 B len ++ <= 31 B str)
|
||||
str32,
|
||||
/// 64 B (1 B len ++ <= 63 B str)
|
||||
str64,
|
||||
/// 128 B (1B len ++ <= 127 B str)
|
||||
str128,
|
||||
/// 256 B (1 B len ++ <= 255 B str)
|
||||
str256,
|
||||
/// 512 B (2 B len ++ <= 510 B str)
|
||||
str512,
|
||||
/// 1 kiB (2 B len ++ <= 1022 B str)
|
||||
str1k,
|
||||
/// 2 kiB (2 B len ++ <= 2046 B str)
|
||||
str2k,
|
||||
/// 4 kiB (2 B len ++ <= 4094 B str)
|
||||
str4k,
|
||||
|
||||
pub fn valuesPerCluster(self: ColumnType) usize {
|
||||
return switch (self) {
|
||||
.bool => 1,
|
||||
.bit => 8,
|
||||
.u8 => 1,
|
||||
.u16 => 1,
|
||||
.u32 => 1,
|
||||
.u64 => 1,
|
||||
.i8 => 1,
|
||||
.i16 => 1,
|
||||
.i32 => 1,
|
||||
.i64 => 1,
|
||||
.f32 => 1,
|
||||
.f64 => 1,
|
||||
.color => 1,
|
||||
.color_hdr => 1,
|
||||
.mat3x2 => 8,
|
||||
.mat4x4 => 8,
|
||||
.complex => 8,
|
||||
.quaternion => 8,
|
||||
.vec2 => 8,
|
||||
.vec2i => 8,
|
||||
.vec3 => 8,
|
||||
.vec3i => 8,
|
||||
.vec4 => 8,
|
||||
.vec4i => 8,
|
||||
.str8 => 1,
|
||||
.str16 => 1,
|
||||
.str32 => 1,
|
||||
.str64 => 1,
|
||||
.str128 => 1,
|
||||
.str256 => 1,
|
||||
.str512 => 1,
|
||||
.str1k => 1,
|
||||
.str2k => 1,
|
||||
.str4k => 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clusterByteSize(self: ColumnType) usize {
|
||||
return switch (self) {
|
||||
.bool => 1,
|
||||
.bit => 1,
|
||||
.u8 => 1,
|
||||
.u16 => 2,
|
||||
.u32 => 4,
|
||||
.u64 => 8,
|
||||
.i8 => 1,
|
||||
.i16 => 2,
|
||||
.i32 => 4,
|
||||
.i64 => 8,
|
||||
.f32 => 4,
|
||||
.f64 => 8,
|
||||
.color => 4,
|
||||
.color_hdr => 8,
|
||||
.mat3x2 => 24 * 8,
|
||||
.mat4x4 => 64 * 8,
|
||||
.complex => 8 * 8,
|
||||
.quaternion => 16 * 8,
|
||||
.vec2 => 8 * 8,
|
||||
.vec2i => 8 * 8,
|
||||
.vec3 => 12 * 8,
|
||||
.vec3i => 12 * 8,
|
||||
.vec4 => 16 * 8,
|
||||
.vec4i => 16 * 8,
|
||||
.str8 => 8,
|
||||
.str16 => 16,
|
||||
.str32 => 32,
|
||||
.str64 => 64,
|
||||
.str128 => 128,
|
||||
.str256 => 256,
|
||||
.str512 => 512,
|
||||
.str1k => 1024,
|
||||
.str2k => 2048,
|
||||
.str4k => 4096,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn valuesPerPage(self: ColumnType, page_size: usize) usize {
|
||||
const clusters_per_page = page_size / self.clusterByteSize();
|
||||
return self.valuesPerCluster() * clusters_per_page;
|
||||
}
|
||||
|
||||
test valuesPerPage {
|
||||
const page_size = 4096;
|
||||
|
||||
try std.testing.expectEqual(page_size, valuesPerPage(.bool, page_size));
|
||||
try std.testing.expectEqual(8 * page_size, valuesPerPage(.bit, page_size));
|
||||
try std.testing.expectEqual(page_size / 4, valuesPerPage(.u32, page_size));
|
||||
try std.testing.expectEqual(page_size / 8, valuesPerPage(.u64, page_size));
|
||||
try std.testing.expectEqual(page_size / @sizeOf(vm.Vector3x8) * 8, valuesPerPage(.vec3, page_size));
|
||||
}
|
||||
};
|
||||
32
packages/datastore/src/TaggedValue.zig
Normal file
32
packages/datastore/src/TaggedValue.zig
Normal file
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vm = @import("vecmath");
|
||||
|
||||
pub const TaggedValue = union(enum) {
|
||||
/// Corresponds to `ColumnType.boolean` and `ColumnType.bit`.
|
||||
bool: bool,
|
||||
u8: u8,
|
||||
u16: u16,
|
||||
u32: u32,
|
||||
u64: u64,
|
||||
i8: i8,
|
||||
i16: i16,
|
||||
i32: i32,
|
||||
i64: i64,
|
||||
f32: f32,
|
||||
f64: f64,
|
||||
color: vm.Color,
|
||||
color_hdr: vm.ColorHdr,
|
||||
mat3x2: vm.Matrix3x2,
|
||||
mat4x4: vm.Matrix4x4,
|
||||
complex: vm.Complex,
|
||||
quaternion: vm.Quaternion,
|
||||
vec2: vm.Vector2,
|
||||
vec2i: vm.Vector2Int,
|
||||
vec3: vm.Vector3,
|
||||
vec3i: vm.Vector3Int,
|
||||
vec4: vm.Vector4,
|
||||
vec4i: vm.Vector4Int,
|
||||
/// Corresponds to all string types in `ColumnType`, from `ColumnType.str8`
|
||||
/// to `ColumnType.str4k`.
|
||||
str: []const u8,
|
||||
};
|
||||
738
packages/datastore/src/root.zig
Normal file
738
packages/datastore/src/root.zig
Normal file
@@ -0,0 +1,738 @@
|
||||
const std = @import("std");
|
||||
const vm = @import("vecmath");
|
||||
|
||||
pub const ColumnType = @import("ColumnType.zig").ColumnType;
|
||||
pub const TaggedValue = @import("TaggedValue.zig").TaggedValue;
|
||||
|
||||
pub const page_size = 4096;
|
||||
pub const page_align = std.mem.Alignment.fromByteUnits(page_size);
|
||||
|
||||
fn IdMixin(comptime Enum: type) type {
|
||||
const Tag = switch (@typeInfo(Enum)) {
|
||||
.@"enum" => |e| e.tag_type,
|
||||
else => @compileError("Expected " ++ @typeName(Enum) ++ " to be an enum."),
|
||||
};
|
||||
|
||||
return struct {
|
||||
pub inline fn fromInt(tag: Tag) Enum {
|
||||
return @enumFromInt(tag);
|
||||
}
|
||||
|
||||
pub inline fn toInt(self: Enum) Tag {
|
||||
return @intFromEnum(self);
|
||||
}
|
||||
|
||||
pub inline fn next(self: Enum) error{OutOfIds}!Enum {
|
||||
const tag = @intFromEnum(self);
|
||||
const next_tag = std.math.add(Tag, tag, 1) catch return error.OutOfIds;
|
||||
return @enumFromInt(next_tag);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn FixedString(comptime Len: type, comptime total_size: usize) type {
|
||||
return extern struct {
|
||||
len: Len,
|
||||
str: [max_str_len]u8,
|
||||
|
||||
const max_str_len = total_size - @sizeOf(Len);
|
||||
|
||||
pub fn slice(self: *@This()) []const u8 {
|
||||
return self.str[0..self.len];
|
||||
}
|
||||
|
||||
pub fn copy(self: *@This(), src: []const u8) void {
|
||||
std.debug.assert(src.len <= max_str_len);
|
||||
|
||||
self.len = @intCast(src.len);
|
||||
@memcpy(self.str[0..src.len], src);
|
||||
@memset(self.str[src.len..], 0);
|
||||
}
|
||||
|
||||
pub fn tryCopy(self: *@This(), src: []const u8) error{RangeError}!void {
|
||||
if (src.len > max_str_len) {
|
||||
return error.RangeError;
|
||||
}
|
||||
|
||||
self.copy(src);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const ColumnId = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const fromInt = IdMixin(ColumnId).fromInt;
|
||||
pub const toInt = IdMixin(ColumnId).toInt;
|
||||
pub const next = IdMixin(ColumnId).next;
|
||||
};
|
||||
|
||||
pub const TableId = enum(u16) {
|
||||
_,
|
||||
|
||||
pub const fromInt = IdMixin(TableId).fromInt;
|
||||
pub const toInt = IdMixin(TableId).toInt;
|
||||
pub const next = IdMixin(TableId).next;
|
||||
};
|
||||
|
||||
pub const RowId = enum(u64) {
|
||||
_,
|
||||
|
||||
pub const fromInt = IdMixin(RowId).fromInt;
|
||||
pub const toInt = IdMixin(RowId).toInt;
|
||||
pub const next = IdMixin(RowId).next;
|
||||
};
|
||||
|
||||
pub const TableDefinition = struct {
|
||||
id: TableId,
|
||||
column_definitions: []const ColumnDefinition,
|
||||
};
|
||||
|
||||
pub const ColumnDefinition = struct {
|
||||
id: ColumnId,
|
||||
type: ColumnType,
|
||||
};
|
||||
|
||||
pub const CellValue = struct {
|
||||
id: ColumnId,
|
||||
value: TaggedValue,
|
||||
};
|
||||
|
||||
pub const Database = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
|
||||
tables: std.AutoHashMapUnmanaged(TableId, Table),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, io: std.Io) !Database {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.io = io,
|
||||
|
||||
.tables = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Database) void {
|
||||
var it = self.tables.valueIterator();
|
||||
while (it.next()) |table| {
|
||||
table.deinit(self.allocator);
|
||||
}
|
||||
|
||||
self.tables.deinit(self.allocator);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn createTable(self: *Database, table_definition: TableDefinition) !void {
|
||||
const get_or_put_result = try self.tables.getOrPut(self.allocator, table_definition.id);
|
||||
|
||||
if (get_or_put_result.found_existing) {
|
||||
return error.DuplicateTableId;
|
||||
}
|
||||
|
||||
errdefer _ = self.tables.remove(table_definition.id);
|
||||
|
||||
get_or_put_result.value_ptr.* = try .init(table_definition, self.allocator);
|
||||
}
|
||||
|
||||
pub fn insertRow(self: *Database, table_id: TableId, row: []const CellValue) !RowId {
|
||||
const table = self.tables.getPtr(table_id) orelse return error.TableNotFound;
|
||||
|
||||
const row_id = table.next_row_id;
|
||||
|
||||
var it = table.columns.valueIterator();
|
||||
while (it.next()) |column| {
|
||||
var corresponding_cell_value: ?*const CellValue = null;
|
||||
for (row) |*cell_value| {
|
||||
if (column.id == cell_value.id) {
|
||||
if (corresponding_cell_value != null) {
|
||||
return error.DuplicateColumnId;
|
||||
}
|
||||
|
||||
corresponding_cell_value = cell_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (corresponding_cell_value) |cell_value| {
|
||||
try column.set(row_id, cell_value.value, self.allocator);
|
||||
} else {
|
||||
return error.MissingColumnId;
|
||||
}
|
||||
}
|
||||
|
||||
table.next_row_id = try row_id.next();
|
||||
return row_id;
|
||||
}
|
||||
|
||||
pub fn getTaggedValue(self: *Database, table_id: TableId, column_id: ColumnId, row_id: RowId) !TaggedValue {
|
||||
const table = self.tables.getPtr(table_id) orelse return error.TableNotFound;
|
||||
const column = table.columns.getPtr(column_id) orelse return error.ColumnNotFound;
|
||||
|
||||
if (row_id.toInt() >= table.next_row_id.toInt()) return error.RowIdNotFound;
|
||||
return column.getTagged(row_id);
|
||||
}
|
||||
|
||||
pub fn getValue(self: *Database, comptime T: type, table_id: TableId, column_id: ColumnId, row_id: RowId) !TaggedValue {
|
||||
const table = self.tables.getPtr(table_id) orelse return error.TableNotFound;
|
||||
const column = table.columns.getPtr(column_id) orelse return error.ColumnNotFound;
|
||||
|
||||
if (row_id.toInt() >= table.next_row_id.toInt()) return error.RowIdNotFound;
|
||||
return column.get(T, row_id);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Table = struct {
|
||||
id: TableId,
|
||||
columns: std.AutoHashMapUnmanaged(ColumnId, Column),
|
||||
next_row_id: RowId,
|
||||
|
||||
pub fn init(table_definition: TableDefinition, allocator: std.mem.Allocator) !Table {
|
||||
const column_count = std.math.cast(u32, table_definition.column_definitions.len) orelse return error.OutOfMemory;
|
||||
|
||||
var columns: std.AutoHashMapUnmanaged(ColumnId, Column) = .empty;
|
||||
try columns.ensureTotalCapacity(allocator, column_count);
|
||||
|
||||
errdefer {
|
||||
var it = columns.valueIterator();
|
||||
while (it.next()) |column| {
|
||||
column.deinit(allocator);
|
||||
}
|
||||
|
||||
columns.deinit(allocator);
|
||||
}
|
||||
|
||||
for (table_definition.column_definitions) |column_definition| {
|
||||
const get_or_put_result = columns.getOrPutAssumeCapacity(column_definition.id);
|
||||
|
||||
if (get_or_put_result.found_existing) {
|
||||
return error.DuplicateColumnId;
|
||||
}
|
||||
|
||||
errdefer _ = columns.remove(column_definition.id);
|
||||
|
||||
get_or_put_result.value_ptr.* = .init(column_definition);
|
||||
}
|
||||
|
||||
return .{
|
||||
.id = table_definition.id,
|
||||
.columns = columns,
|
||||
.next_row_id = .fromInt(0),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Table, allocator: std.mem.Allocator) void {
|
||||
var it = self.columns.valueIterator();
|
||||
while (it.next()) |column| {
|
||||
column.deinit(allocator);
|
||||
}
|
||||
|
||||
self.columns.deinit(allocator);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Column = struct {
|
||||
id: ColumnId,
|
||||
type: ColumnType,
|
||||
pages: std.ArrayList(Page),
|
||||
|
||||
pub fn init(column_definition: ColumnDefinition) Column {
|
||||
return .{
|
||||
.id = column_definition.id,
|
||||
.type = column_definition.type,
|
||||
.pages = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Column, allocator: std.mem.Allocator) void {
|
||||
for (self.pages.items) |*page| {
|
||||
page.deinit();
|
||||
}
|
||||
|
||||
self.pages.deinit(allocator);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn getTagged(self: *Column, row_id: RowId) TaggedValue {
|
||||
const values_per_page = self.type.valuesPerPage(page_size);
|
||||
|
||||
const page_index = @as(usize, row_id.toInt() / values_per_page);
|
||||
const value_index = @as(usize, row_id.toInt() % values_per_page);
|
||||
|
||||
const page = &self.pages.items[page_index];
|
||||
|
||||
return switch (self.type) {
|
||||
.bool => .{ .bool = page.get(bool, value_index) },
|
||||
.bit => .{ .bool = page.getBit(value_index) },
|
||||
.u8 => .{ .u8 = page.get(u8, value_index) },
|
||||
.u16 => .{ .u16 = page.get(u16, value_index) },
|
||||
.u32 => .{ .u32 = page.get(u32, value_index) },
|
||||
.u64 => .{ .u64 = page.get(u64, value_index) },
|
||||
.i8 => .{ .i8 = page.get(i8, value_index) },
|
||||
.i16 => .{ .i16 = page.get(i16, value_index) },
|
||||
.i32 => .{ .i32 = page.get(i32, value_index) },
|
||||
.i64 => .{ .i64 = page.get(i64, value_index) },
|
||||
.f32 => .{ .f32 = page.get(f32, value_index) },
|
||||
.f64 => .{ .f64 = page.get(f64, value_index) },
|
||||
.color => .{ .color = page.get(vm.Color, value_index) },
|
||||
.color_hdr => .{ .color_hdr = page.get(vm.ColorHdr, value_index) },
|
||||
.mat3x2 => .{ .mat3x2 = page.getClustered(vm.Matrix3x2, 8, value_index) },
|
||||
.mat4x4 => .{ .mat4x4 = page.getClustered(vm.Matrix4x4, 8, value_index) },
|
||||
.complex => .{ .complex = page.getClustered(vm.Complex, 8, value_index) },
|
||||
.quaternion => .{ .quaternion = page.getClustered(vm.Quaternion, 8, value_index) },
|
||||
.vec2 => .{ .vec2 = page.getClustered(vm.Vector2, 8, value_index) },
|
||||
.vec2i => .{ .vec2i = page.getClustered(vm.Vector2Int, 8, value_index) },
|
||||
.vec3 => .{ .vec3 = page.getClustered(vm.Vector3, 8, value_index) },
|
||||
.vec3i => .{ .vec3i = page.getClustered(vm.Vector3Int, 8, value_index) },
|
||||
.vec4 => .{ .vec4 = page.getClustered(vm.Vector4, 8, value_index) },
|
||||
.vec4i => .{ .vec4i = page.getClustered(vm.Vector4Int, 8, value_index) },
|
||||
.str8 => .{ .str = page.getPtr(FixedString(u8, 8), value_index).slice() },
|
||||
.str16 => .{ .str = page.getPtr(FixedString(u8, 16), value_index).slice() },
|
||||
.str32 => .{ .str = page.getPtr(FixedString(u8, 32), value_index).slice() },
|
||||
.str64 => .{ .str = page.getPtr(FixedString(u8, 64), value_index).slice() },
|
||||
.str128 => .{ .str = page.getPtr(FixedString(u8, 128), value_index).slice() },
|
||||
.str256 => .{ .str = page.getPtr(FixedString(u8, 256), value_index).slice() },
|
||||
.str512 => .{ .str = page.getPtr(FixedString(u16, 512), value_index).slice() },
|
||||
.str1k => .{ .str = page.getPtr(FixedString(u16, 1024), value_index).slice() },
|
||||
.str2k => .{ .str = page.getPtr(FixedString(u16, 2048), value_index).slice() },
|
||||
.str4k => .{ .str = page.getPtr(FixedString(u16, 4096), value_index).slice() },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get(self: *Column, comptime T: type, row_id: RowId) !T {
|
||||
const values_per_page = self.type.valuesPerPage(page_size);
|
||||
|
||||
const page_index = @as(usize, row_id.toInt() / values_per_page);
|
||||
const value_index = @as(usize, row_id.toInt() % values_per_page);
|
||||
|
||||
const page = &self.pages.items[page_index];
|
||||
|
||||
return switch (T) {
|
||||
bool => switch (self.type) {
|
||||
.bool => page.get(bool, value_index),
|
||||
.bit => page.getBit(value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
u8 => switch (self.type) {
|
||||
.u8 => page.get(u8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
u16 => switch (self.type) {
|
||||
.u16 => page.get(u16, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
u32 => switch (self.type) {
|
||||
.u32 => page.get(u32, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
u64 => switch (self.type) {
|
||||
.u64 => page.get(u64, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
i8 => switch (self.type) {
|
||||
.i8 => page.get(i8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
i16 => switch (self.type) {
|
||||
.i16 => page.get(i16, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
i32 => switch (self.type) {
|
||||
.i32 => page.get(i32, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
i64 => switch (self.type) {
|
||||
.i64 => page.get(i64, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
f32 => switch (self.type) {
|
||||
.f32 => page.get(f32, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
f64 => switch (self.type) {
|
||||
.f64 => page.get(f64, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Color => switch (self.type) {
|
||||
.color => page.get(vm.Color, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.ColorHdr => switch (self.type) {
|
||||
.color_hdr => page.get(vm.ColorHdr, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Matrix3x2 => switch (self.type) {
|
||||
.mat3x2 => page.getClustered(vm.Matrix3x2, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Matrix4x4 => switch (self.type) {
|
||||
.mat4x4 => page.getClustered(vm.Matrix4x4, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Complex => switch (self.type) {
|
||||
.complex => page.getClustered(vm.Complex, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Quaternion => switch (self.type) {
|
||||
.quaternion => page.getClustered(vm.Quaternion, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector2 => switch (self.type) {
|
||||
.vec2 => page.getClustered(vm.Vector2, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector2Int => switch (self.type) {
|
||||
.vec2i => page.getClustered(vm.Vector2Int, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector3 => switch (self.type) {
|
||||
.vec3 => page.getClustered(vm.Vector3, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector3Int => switch (self.type) {
|
||||
.vec3i => page.getClustered(vm.Vector3Int, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector4 => switch (self.type) {
|
||||
.vec4 => page.getClustered(vm.Vector4, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
vm.Vector4Int => switch (self.type) {
|
||||
.vec4i => page.getClustered(vm.Vector4Int, 8, value_index),
|
||||
else => error.TypeError,
|
||||
},
|
||||
[]const u8 => switch (self.type) {
|
||||
.str8 => page.getPtr(FixedString(u8, 8), value_index).slice(),
|
||||
.str16 => page.getPtr(FixedString(u8, 16), value_index).slice(),
|
||||
.str32 => page.getPtr(FixedString(u8, 32), value_index).slice(),
|
||||
.str64 => page.getPtr(FixedString(u8, 64), value_index).slice(),
|
||||
.str128 => page.getPtr(FixedString(u8, 128), value_index).slice(),
|
||||
.str256 => page.getPtr(FixedString(u8, 256), value_index).slice(),
|
||||
.str512 => page.getPtr(FixedString(u16, 512), value_index).slice(),
|
||||
.str1k => page.getPtr(FixedString(u16, 1024), value_index).slice(),
|
||||
.str2k => page.getPtr(FixedString(u16, 2048), value_index).slice(),
|
||||
.str4k => page.getPtr(FixedString(u16, 4096), value_index).slice(),
|
||||
else => error.TypeError,
|
||||
},
|
||||
else => @compileError("Type " ++ @typeName(T) ++ " not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set(self: *Column, row_id: RowId, value: TaggedValue, allocator: std.mem.Allocator) !void {
|
||||
const values_per_page = self.type.valuesPerPage(page_size);
|
||||
|
||||
const page_index = @as(usize, row_id.toInt() / values_per_page);
|
||||
const value_index = @as(usize, row_id.toInt() % values_per_page);
|
||||
|
||||
try self.pages.ensureTotalCapacity(allocator, page_index + 1);
|
||||
while (page_index >= self.pages.items.len) {
|
||||
self.pages.appendAssumeCapacity(try .init());
|
||||
}
|
||||
|
||||
const page = &self.pages.items[page_index];
|
||||
|
||||
return switch (value) {
|
||||
.bool => |x| switch (self.type) {
|
||||
.bool => page.set(bool, value_index, x),
|
||||
.bit => page.setBit(value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.u8 => |x| switch (self.type) {
|
||||
.u8 => page.set(u8, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.u16 => |x| switch (self.type) {
|
||||
.u16 => page.set(u16, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.u32 => |x| switch (self.type) {
|
||||
.u32 => page.set(u32, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.u64 => |x| switch (self.type) {
|
||||
.u64 => page.set(u64, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.i8 => |x| switch (self.type) {
|
||||
.i8 => page.set(i8, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.i16 => |x| switch (self.type) {
|
||||
.i16 => page.set(i16, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.i32 => |x| switch (self.type) {
|
||||
.i32 => page.set(i32, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.i64 => |x| switch (self.type) {
|
||||
.i64 => page.set(i64, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.f32 => |x| switch (self.type) {
|
||||
.f32 => page.set(f32, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.f64 => |x| switch (self.type) {
|
||||
.f64 => page.set(f64, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.color => |x| switch (self.type) {
|
||||
.color => page.set(vm.Color, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.color_hdr => |x| switch (self.type) {
|
||||
.color_hdr => page.set(vm.ColorHdr, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.mat3x2 => |x| switch (self.type) {
|
||||
.mat3x2 => page.setClustered(vm.Matrix3x2, 8, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.mat4x4 => |x| switch (self.type) {
|
||||
.mat4x4 => page.setClustered(vm.Matrix4x4, 8, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.complex => |x| switch (self.type) {
|
||||
.complex => page.set(vm.Complex, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.quaternion => |x| switch (self.type) {
|
||||
.quaternion => page.set(vm.Quaternion, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec2 => |x| switch (self.type) {
|
||||
.vec2 => page.set(vm.Vector2, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec2i => |x| switch (self.type) {
|
||||
.vec2i => page.set(vm.Vector2Int, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec3 => |x| switch (self.type) {
|
||||
.vec3 => page.set(vm.Vector3, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec3i => |x| switch (self.type) {
|
||||
.vec3i => page.set(vm.Vector3Int, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec4 => |x| switch (self.type) {
|
||||
.vec4 => page.set(vm.Vector4, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.vec4i => |x| switch (self.type) {
|
||||
.vec4i => page.set(vm.Vector4Int, value_index, x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
.str => |x| switch (self.type) {
|
||||
.str8 => page.getPtr(FixedString(u8, 8), value_index).tryCopy(x),
|
||||
.str16 => page.getPtr(FixedString(u8, 16), value_index).tryCopy(x),
|
||||
.str32 => page.getPtr(FixedString(u8, 32), value_index).tryCopy(x),
|
||||
.str64 => page.getPtr(FixedString(u8, 64), value_index).tryCopy(x),
|
||||
.str128 => page.getPtr(FixedString(u8, 128), value_index).tryCopy(x),
|
||||
.str256 => page.getPtr(FixedString(u8, 256), value_index).tryCopy(x),
|
||||
.str512 => page.getPtr(FixedString(u16, 512), value_index).tryCopy(x),
|
||||
.str1k => page.getPtr(FixedString(u16, 1024), value_index).tryCopy(x),
|
||||
.str2k => page.getPtr(FixedString(u16, 2048), value_index).tryCopy(x),
|
||||
.str4k => page.getPtr(FixedString(u16, 4096), value_index).tryCopy(x),
|
||||
else => error.TypeError,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Page = struct {
|
||||
ptr: *align(page_align.toByteUnits()) [page_size]u8,
|
||||
|
||||
pub fn init() !Page {
|
||||
const bytes = try std.heap.page_allocator.alignedAlloc(u8, page_align, page_size);
|
||||
return .{
|
||||
.ptr = bytes[0..page_size],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Page) void {
|
||||
const bytes = @as([]u8, std.mem.asBytes(self.ptr));
|
||||
std.heap.page_allocator.free(bytes);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn get(self: Page, comptime T: type, index: usize) T {
|
||||
const byte_offset = index * @sizeOf(T);
|
||||
std.debug.assert(byte_offset + @sizeOf(T) <= page_size);
|
||||
|
||||
return @as([*]T, @ptrCast(self.ptr))[index];
|
||||
}
|
||||
|
||||
pub fn getPtr(self: Page, comptime T: type, index: usize) *T {
|
||||
const byte_offset = index * @sizeOf(T);
|
||||
std.debug.assert(byte_offset + @sizeOf(T) <= page_size);
|
||||
|
||||
return &@as([*]T, @ptrCast(self.ptr))[index];
|
||||
}
|
||||
|
||||
pub fn getBit(self: Page, bit_index: usize) bool {
|
||||
const byte_index = bit_index / 8;
|
||||
const bit_shift = @as(u3, @intCast(bit_index % 8));
|
||||
|
||||
const mask = @as(u8, 1) << bit_shift;
|
||||
return self.ptr[byte_index] & mask != 0;
|
||||
}
|
||||
|
||||
pub fn getClustered(self: Page, comptime T: type, comptime values_per_cluster: usize, index: usize) T {
|
||||
const struct_info = switch (@typeInfo(T)) {
|
||||
.@"struct" => |s| s,
|
||||
else => @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."),
|
||||
};
|
||||
|
||||
if (struct_info.layout != .@"extern") {
|
||||
@compileError("Expected " ++ @typeName(T) ++ " to be an extern struct.");
|
||||
}
|
||||
|
||||
const cluster_index = index / values_per_cluster;
|
||||
const value_index = index % values_per_cluster;
|
||||
|
||||
const cluster_base_ptr = @as([*]u8, self.ptr) + cluster_index * @sizeOf(T) * values_per_cluster;
|
||||
|
||||
var ret: T = undefined;
|
||||
inline for (struct_info.fields) |field| {
|
||||
if (field.is_comptime) {
|
||||
@compileError("Unexpected comptime field " ++ @typeName(T) ++ "." ++ field.name ++ ".");
|
||||
}
|
||||
|
||||
const cluster_field_offset = @offsetOf(T, field.name) * values_per_cluster;
|
||||
const cluster_field_ptr = @as(*[values_per_cluster]field.type, @ptrCast(@alignCast(cluster_base_ptr + cluster_field_offset)));
|
||||
|
||||
@field(ret, field.name) = cluster_field_ptr[value_index];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn set(self: Page, comptime T: type, index: usize, value: T) void {
|
||||
const byte_offset = index * @sizeOf(T);
|
||||
std.debug.assert(byte_offset + @sizeOf(T) <= page_size);
|
||||
|
||||
@as([*]T, @ptrCast(self.ptr))[index] = value;
|
||||
}
|
||||
|
||||
pub fn setBit(self: Page, bit_index: usize, value: bool) void {
|
||||
const byte_index = bit_index / 8;
|
||||
const bit_shift = @as(u3, @intCast(bit_index % 8));
|
||||
|
||||
if (value) {
|
||||
const mask = @as(u8, 1) << bit_shift;
|
||||
self.ptr[byte_index] |= mask;
|
||||
} else {
|
||||
const mask = ~(@as(u8, 1) << bit_shift);
|
||||
self.ptr[byte_index] &= mask;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setClustered(self: Page, comptime T: type, comptime values_per_cluster: usize, index: usize, value: T) void {
|
||||
const struct_info = switch (@typeInfo(T)) {
|
||||
.@"struct" => |s| s,
|
||||
else => @compileError("Expected " ++ @typeName(T) ++ " to be an extern struct."),
|
||||
};
|
||||
|
||||
if (struct_info.layout != .@"extern") {
|
||||
@compileError("Expected " ++ @typeName(T) ++ " to be an extern struct.");
|
||||
}
|
||||
|
||||
const cluster_index = index / values_per_cluster;
|
||||
const value_index = index % values_per_cluster;
|
||||
|
||||
const cluster_base_ptr = @as([*]u8, self.ptr) + cluster_index * @sizeOf(T) * values_per_cluster;
|
||||
|
||||
inline for (struct_info.fields) |field| {
|
||||
if (field.is_comptime) {
|
||||
@compileError("Unexpected comptime field " ++ @typeName(T) ++ "." ++ field.name ++ ".");
|
||||
}
|
||||
|
||||
const cluster_field_offset = @offsetOf(T, field.name) * values_per_cluster;
|
||||
const cluster_field_ptr = @as(*[values_per_cluster]field.type, @ptrCast(@alignCast(cluster_base_ptr + cluster_field_offset)));
|
||||
|
||||
cluster_field_ptr[value_index] = @field(value, field.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
var db: Database = try .init(std.testing.allocator, std.testing.io);
|
||||
defer db.deinit();
|
||||
|
||||
const table_ids = struct {
|
||||
const users = TableId.fromInt(0);
|
||||
};
|
||||
|
||||
const column_ids = struct {
|
||||
const username = ColumnId.fromInt(0);
|
||||
const password_hash = ColumnId.fromInt(1);
|
||||
const color = ColumnId.fromInt(2);
|
||||
};
|
||||
|
||||
try db.createTable(.{
|
||||
.id = table_ids.users,
|
||||
.column_definitions = &.{
|
||||
.{
|
||||
.id = column_ids.username,
|
||||
.type = .str64,
|
||||
},
|
||||
.{
|
||||
.id = column_ids.password_hash,
|
||||
.type = .str128,
|
||||
},
|
||||
.{
|
||||
.id = column_ids.color,
|
||||
.type = .color,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const admin_id = try db.insertRow(table_ids.users, &.{
|
||||
.{
|
||||
.id = column_ids.username,
|
||||
.value = .{ .str = "admin" },
|
||||
},
|
||||
.{
|
||||
.id = column_ids.password_hash,
|
||||
.value = .{ .str = "$argon2id$v=19$m=16,t=2,p=1$R0l1WjlpM1BUSXZ3dU0xbQ$muWG51NiQjaIe6RTh6LJgk/7VJMvtbtJiN5Z11fEFbI" },
|
||||
},
|
||||
.{
|
||||
.id = column_ids.color,
|
||||
.value = .{ .color = .red },
|
||||
},
|
||||
});
|
||||
|
||||
const user_id = try db.insertRow(table_ids.users, &.{
|
||||
.{
|
||||
.id = column_ids.username,
|
||||
.value = .{ .str = "user" },
|
||||
},
|
||||
.{
|
||||
.id = column_ids.password_hash,
|
||||
.value = .{ .str = "$argon2id$v=19$m=16,t=2,p=1$RlNyZTR3dklRU1hVb3hHSA$8EDVcnkQouADrpvb5bONOqQWrUil1Jc/YaZGe3t34q0" },
|
||||
},
|
||||
.{
|
||||
.id = column_ids.color,
|
||||
.value = .{ .color = .blue },
|
||||
},
|
||||
});
|
||||
|
||||
const admin_username = try db.getValue([]const u8, table_ids.users, column_ids.username, admin_id);
|
||||
const admin_color = try db.getValue(vm.Color, table_ids.users, column_ids.color, admin_id);
|
||||
|
||||
const user_username = try db.getValue([]const u8, table_ids.users, column_ids.username, user_id);
|
||||
const user_color = try db.getValue(vm.Color, table_ids.users, column_ids.color, user_id);
|
||||
|
||||
try std.testing.expectEqualStrings("admin", admin_username);
|
||||
try std.testing.expectEqual(vm.Color.red, admin_color);
|
||||
|
||||
try std.testing.expectEqualStrings("user", user_username);
|
||||
try std.testing.expectEqual(vm.Color.blue, user_color);
|
||||
}
|
||||
@@ -17,8 +17,11 @@ pub fn build(b: *std.Build) void {
|
||||
},
|
||||
});
|
||||
|
||||
mod.addCSourceFile(.{
|
||||
.file = b.path("src/stbi/stb_image.c"),
|
||||
mod.addCSourceFiles(.{
|
||||
.files = &.{
|
||||
"src/stbi/stb_image.c",
|
||||
"src/stbtt/stb_truetype.c",
|
||||
},
|
||||
.flags = &.{
|
||||
"-std=c17",
|
||||
"-Wall",
|
||||
|
||||
@@ -451,7 +451,7 @@ pub const ChunkType = enum(u32) {
|
||||
_,
|
||||
|
||||
fn fromName(name: *const [4]u8) u32 {
|
||||
return @bitCast(name.*);
|
||||
return std.mem.bytesToValue(u32, name);
|
||||
}
|
||||
|
||||
pub fn fromBytes(bytes: *const [4]u8) ChunkType {
|
||||
@@ -459,18 +459,18 @@ pub const ChunkType = enum(u32) {
|
||||
}
|
||||
|
||||
pub fn toBytes(self: ChunkType) [4]u8 {
|
||||
return @bitCast(@intFromEnum(self));
|
||||
return std.mem.toBytes(self);
|
||||
}
|
||||
|
||||
pub fn ancillary(self: ChunkType) bool {
|
||||
pub fn isAncillary(self: ChunkType) bool {
|
||||
return self.toBytes()[0] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
pub fn private(self: ChunkType) bool {
|
||||
pub fn isPrivate(self: ChunkType) bool {
|
||||
return self.toBytes()[1] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
pub fn safeToCopy(self: ChunkType) bool {
|
||||
pub fn isSafeToCopy(self: ChunkType) bool {
|
||||
return self.toBytes()[3] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ pub const StandardKeyword = enum {
|
||||
Description,
|
||||
/// Copyright notice
|
||||
Copyright,
|
||||
/// Time Time of original image creation
|
||||
/// Time of original image creation
|
||||
Creation,
|
||||
/// Software used to create the image
|
||||
Software,
|
||||
|
||||
@@ -9,6 +9,7 @@ pub const png = @import("png.zig");
|
||||
pub const qoa = @import("qoa.zig");
|
||||
pub const qoi = @import("qoi.zig");
|
||||
pub const stbi = @import("stbi.zig");
|
||||
pub const stbtt = @import("stbtt.zig");
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
|
||||
297
packages/media/src/stbtt.zig
Normal file
297
packages/media/src/stbtt.zig
Normal file
@@ -0,0 +1,297 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
|
||||
mutex: std.Io.Mutex = .init,
|
||||
allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty,
|
||||
allocated_bytes: usize = 0,
|
||||
|
||||
const alignment: std.mem.Alignment = .@"16";
|
||||
const VoidPtr = ?*align(alignment.toByteUnits()) anyopaque;
|
||||
const log = std.log.scoped(.stbi);
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, io: std.Io) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.io = io,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||
if (self.allocated_bytes > 0) {
|
||||
log.warn("{d} byte(s) still allocated while deinitializing", .{self.allocated_bytes});
|
||||
}
|
||||
if (self.allocations.size > 0) {
|
||||
log.warn("{d} allocation(s) still tracked while deinitializing", .{self.allocations.size});
|
||||
var it = self.allocations.iterator();
|
||||
var index: usize = 0;
|
||||
while (it.next()) |entry| : (index += 1) {
|
||||
log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{
|
||||
index + 1,
|
||||
self.allocations.size,
|
||||
@intFromPtr(entry.key_ptr.*),
|
||||
entry.value_ptr.*,
|
||||
});
|
||||
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(entry.key_ptr.*)))[0..entry.value_ptr.*];
|
||||
self.allocator.free(memory);
|
||||
}
|
||||
}
|
||||
|
||||
self.allocations.deinit(self.allocator);
|
||||
}
|
||||
|
||||
const import = struct {
|
||||
const stbtt__buf = extern struct {
|
||||
data: ?[*]const u8,
|
||||
cursor: c_int,
|
||||
size: c_int,
|
||||
};
|
||||
|
||||
const stbtt_fontinfo = extern struct {
|
||||
userdata: ?*anyopaque,
|
||||
data: [*]const u8,
|
||||
fonstart: c_int,
|
||||
|
||||
numGlyphs: c_int,
|
||||
|
||||
loca: c_int,
|
||||
head: c_int,
|
||||
glyf: c_int,
|
||||
hhea: c_int,
|
||||
hmtx: c_int,
|
||||
kern: c_int,
|
||||
gpos: c_int,
|
||||
svg: c_int,
|
||||
index_map: c_int,
|
||||
indexToLocFormat: c_int,
|
||||
|
||||
cff: stbtt__buf,
|
||||
charstrings: stbtt__buf,
|
||||
gsubrs: stbtt__buf,
|
||||
subrs: stbtt__buf,
|
||||
fontdicts: stbtt__buf,
|
||||
fdselect: stbtt__buf,
|
||||
};
|
||||
|
||||
const stbtt_kerningentry = extern struct {
|
||||
glyph1: c_int,
|
||||
glyph2: c_int,
|
||||
advance: c_int,
|
||||
};
|
||||
|
||||
const STBTT_vmove = enum(u8) {
|
||||
STBTT_vmove = 1,
|
||||
STBTT_vline,
|
||||
STBTT_vcurve,
|
||||
STBTT_vcubic,
|
||||
};
|
||||
|
||||
const stbtt_vertex = extern struct {
|
||||
x: i16,
|
||||
y: i16,
|
||||
cx: i16,
|
||||
cy: i16,
|
||||
cx1: i16,
|
||||
cy1: i16,
|
||||
type: STBTT_vmove,
|
||||
padding: u8,
|
||||
};
|
||||
|
||||
const stbtt_kerning_table_iterator = extern struct {
|
||||
data: [*]u8,
|
||||
k: c_int,
|
||||
length: c_int,
|
||||
};
|
||||
|
||||
extern fn stbtt_GetNumberOfFonts(data: [*]const u8) c_int;
|
||||
extern fn stbtt_GetFontOffsetForIndex(index: c_int, data: [*]const u8) c_int;
|
||||
extern fn stbtt_InitFont(info: *stbtt_fontinfo, data: [*]const u8, offset: c_int) c_int;
|
||||
extern fn stbtt_FindGlyphIndex(info: *const stbtt_fontinfo, unicode_codepoint: c_int) c_int;
|
||||
extern fn stbtt_GetFontVMetrics(info: *const stbtt_fontinfo, ascent: ?*c_int, descent: ?*c_int, lineGap: ?*c_int) void;
|
||||
extern fn stbtt_GetFontVMetricsOS2(info: *const stbtt_fontinfo, typoAscent: ?*c_int, typoDescent: ?*c_int, typoLineGap: ?*c_int) c_int;
|
||||
extern fn stbtt_GetGlyphHMetrics(info: *const stbtt_fontinfo, glyph_index: c_int, advanceWidth: *c_int, leftSideBearing: *c_int) void;
|
||||
extern fn stbtt_GetGlyphKernAdvance(info: *const stbtt_fontinfo, glyph1: c_int, glyph2: c_int) c_int;
|
||||
extern fn stbtt_GetGlyphBox(info: *const stbtt_fontinfo, glyph_index: c_int, x0: *c_int, y0: *c_int, x1: *c_int, y1: *c_int) c_int;
|
||||
extern fn stbtt_GetKerningTableLength(info: *const stbtt_fontinfo) c_int;
|
||||
extern fn stbtt_GetKerningTable(info: *const stbtt_fontinfo, table: [*]stbtt_kerningentry, table_length: c_int) c_int;
|
||||
extern fn stbtt_IsGlyphEmpty(info: *const stbtt_fontinfo, glyph_index: c_int) c_int;
|
||||
extern fn stbtt_GetGlyphShape(info: *const stbtt_fontinfo, glyph_index: c_int, vertices: *[*]stbtt_vertex) c_int;
|
||||
extern fn stbtt_FreeShape(info: *const stbtt_fontinfo, vertices: [*]stbtt_vertex) void;
|
||||
|
||||
extern fn stbtt_UnitsPerEm(info: *const stbtt_fontinfo) c_int;
|
||||
extern fn stbtt_GetFontCapHeight(info: *const stbtt_fontinfo, capHeight: ?*c_int) c_int;
|
||||
extern fn stbtt_KerningTableIterator(info: *const stbtt_fontinfo, it: *stbtt_kerning_table_iterator) void;
|
||||
extern fn stbtt_KerningTableNext(it: *stbtt_kerning_table_iterator, entry: *stbtt_kerningentry) c_int;
|
||||
};
|
||||
|
||||
pub fn initFont(self: *Self, data: []const u8) !*Font {
|
||||
const info = try self.allocator.create(import.stbtt_fontinfo);
|
||||
errdefer self.allocator.destroy(info);
|
||||
|
||||
info.userdata = self;
|
||||
const res = import.stbtt_InitFont(info, data.ptr, 0);
|
||||
if (res == 0) return error.StbttError;
|
||||
|
||||
return @ptrCast(info);
|
||||
}
|
||||
|
||||
pub const Font = opaque {
|
||||
pub fn deinit(self: *Font) void {
|
||||
const info = @as(*import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
const stbtt = @as(*Self, @ptrCast(@alignCast(info.userdata.?)));
|
||||
stbtt.allocator.destroy(info);
|
||||
}
|
||||
|
||||
pub fn findGlyphIndex(self: *const Font, codepoint: u21) ?u16 {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
const res = import.stbtt_FindGlyphIndex(info, codepoint);
|
||||
return if (res != 0) @intCast(res) else null;
|
||||
}
|
||||
|
||||
pub fn getGlyphMetrics(self: *const Font, glyph: u16) GlyphMetrics {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
var advance_width: c_int = undefined;
|
||||
var left_side_bearing: c_int = undefined;
|
||||
import.stbtt_GetGlyphHMetrics(info, glyph, &advance_width, &left_side_bearing);
|
||||
return .{
|
||||
.advance_width = @intCast(advance_width),
|
||||
.left_side_bearing = @intCast(left_side_bearing),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getGlyphBox(self: *const Font, glyph: u16) ?GlyphBox {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
var x0: c_int = undefined;
|
||||
var y0: c_int = undefined;
|
||||
var x1: c_int = undefined;
|
||||
var y1: c_int = undefined;
|
||||
const res = import.stbtt_GetGlyphBox(info, glyph, &x0, &y0, &x1, &y1);
|
||||
return if (res != 0) .{
|
||||
.x_min = @intCast(x0),
|
||||
.y_min = @intCast(y0),
|
||||
.x_max = @intCast(x1),
|
||||
.y_max = @intCast(y1),
|
||||
} else null;
|
||||
}
|
||||
|
||||
pub fn isGlyphEmpty(self: *const Font, glyph: u16) bool {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
const res = import.stbtt_IsGlyphEmpty(info, glyph);
|
||||
return res != 0;
|
||||
}
|
||||
|
||||
pub fn getGlyphShape(self: *const Font, glyph: u16) []import.stbtt_vertex {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
var vertices_ptr: [*]import.stbtt_vertex = undefined;
|
||||
const vertices_len = @as(usize, @intCast(import.stbtt_GetGlyphShape(info, glyph, &vertices_ptr)));
|
||||
return if (vertices_len == 0) &.{} else vertices_ptr[0..vertices_len];
|
||||
}
|
||||
|
||||
pub fn freeShape(self: *const Font, vertices: []import.stbtt_vertex) void {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
if (vertices.len > 0) {
|
||||
import.stbtt_FreeShape(info, vertices.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unitsPerEm(self: *const Font) u16 {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
const res = import.stbtt_UnitsPerEm(info);
|
||||
return @intCast(res);
|
||||
}
|
||||
|
||||
pub fn getCapHeight(self: *const Font) ?i16 {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
|
||||
var cap_height: c_int = undefined;
|
||||
if (import.stbtt_GetFontCapHeight(info, &cap_height) != 0) {
|
||||
return @intCast(cap_height);
|
||||
}
|
||||
|
||||
if (self.findGlyphIndex('H')) |glyph| {
|
||||
if (self.getGlyphBox(glyph)) |box| {
|
||||
return box.y_max;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn kerningTableIterator(self: *const Font) KerningTableIterator {
|
||||
const info = @as(*const import.stbtt_fontinfo, @ptrCast(@alignCast(self)));
|
||||
var it: import.stbtt_kerning_table_iterator = undefined;
|
||||
import.stbtt_KerningTableIterator(info, &it);
|
||||
return .{ .it = it };
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const GlyphBox = struct {
|
||||
x_min: i16,
|
||||
y_min: i16,
|
||||
x_max: i16,
|
||||
y_max: i16,
|
||||
};
|
||||
|
||||
pub const GlyphMetrics = struct {
|
||||
advance_width: i16,
|
||||
left_side_bearing: i16,
|
||||
};
|
||||
|
||||
pub const KerningTableEntry = struct {
|
||||
glyph_prev: u16,
|
||||
glyph_next: u16,
|
||||
advance: i16,
|
||||
};
|
||||
|
||||
pub const KerningTableIterator = struct {
|
||||
it: import.stbtt_kerning_table_iterator,
|
||||
|
||||
pub fn next(self: *KerningTableIterator) ?KerningTableEntry {
|
||||
var entry: import.stbtt_kerningentry = undefined;
|
||||
return if (import.stbtt_KerningTableNext(&self.it, &entry) != 0) .{
|
||||
.glyph_prev = @intCast(entry.glyph1),
|
||||
.glyph_next = @intCast(entry.glyph2),
|
||||
.advance = @intCast(entry.advance),
|
||||
} else null;
|
||||
}
|
||||
};
|
||||
|
||||
// --- MALLOC INTERFACE --------------------------------------------------------
|
||||
|
||||
export fn castle_media_stbtt_malloc(self: *Self, size: usize) callconv(.c) VoidPtr {
|
||||
self.mutex.lock(self.io) catch return null;
|
||||
defer self.mutex.unlock(self.io);
|
||||
|
||||
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||
const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null;
|
||||
|
||||
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||
self.allocated_bytes += size;
|
||||
//log.debug("Allocated {d} bytes(s) at 0x{x}", .{ size, @intFromPtr(memory.ptr) });
|
||||
|
||||
return memory.ptr;
|
||||
}
|
||||
|
||||
export fn castle_media_stbtt_free(self: *Self, maybe_ptr: VoidPtr) callconv(.c) void {
|
||||
if (maybe_ptr) |ptr| {
|
||||
self.mutex.lockUncancelable(self.io);
|
||||
defer self.mutex.unlock(self.io);
|
||||
|
||||
const size = self.allocations.fetchRemove(ptr).?.value;
|
||||
self.allocated_bytes -= size;
|
||||
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..size];
|
||||
|
||||
self.allocator.free(memory);
|
||||
}
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
67
packages/media/src/stbtt/stb_truetype.c
Normal file
67
packages/media/src/stbtt/stb_truetype.c
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <stddef.h>
|
||||
|
||||
void *castle_media_stbtt_malloc(void *self, size_t size);
|
||||
void castle_media_stbtt_free(void *self, void *ptr);
|
||||
|
||||
#define STBTT_malloc(size, self) castle_media_stbtt_malloc(self, size)
|
||||
#define STBTT_free(ptr, self) castle_media_stbtt_free(self, ptr)
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "stb_truetype.h"
|
||||
|
||||
STBTT_DEF int stbtt_UnitsPerEm(const stbtt_fontinfo *info)
|
||||
{
|
||||
return ttUSHORT(info->data + info->head + 18);
|
||||
}
|
||||
|
||||
STBTT_DEF int stbtt_GetFontCapHeight(const stbtt_fontinfo *info, int *capHeight)
|
||||
{
|
||||
int tab = stbtt__find_table(info->data, info->fontstart, "OS/2");
|
||||
if (!tab || ttUSHORT(info->data+tab) < 2)
|
||||
return 0;
|
||||
if (capHeight) *capHeight = ttSHORT(info->data+tab + 88);
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef struct stbtt_kerning_table_iterator {
|
||||
stbtt_uint8 *data;
|
||||
int k;
|
||||
int length;
|
||||
} stbtt_kerning_table_iterator;
|
||||
|
||||
STBTT_DEF void stbtt_KerningTableIterator(const stbtt_fontinfo *info, stbtt_kerning_table_iterator *it)
|
||||
{
|
||||
stbtt_uint8 *data = info->data + info->kern;
|
||||
|
||||
it->data = data;
|
||||
it->k = 0;
|
||||
it->length = 0;
|
||||
|
||||
if (!info->kern)
|
||||
return;
|
||||
if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
|
||||
return;
|
||||
if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
|
||||
return;
|
||||
|
||||
it->length = ttUSHORT(data+10);
|
||||
}
|
||||
|
||||
STBTT_DEF int stbtt_KerningTableNext(stbtt_kerning_table_iterator *it, stbtt_kerningentry *entry)
|
||||
{
|
||||
stbtt_uint8 *data = it->data;
|
||||
int k = it->k;
|
||||
int length = it->length;
|
||||
|
||||
if (k < length)
|
||||
{
|
||||
entry->glyph1 = ttUSHORT(data+18+(k*6));
|
||||
entry->glyph2 = ttUSHORT(data+20+(k*6));
|
||||
entry->advance = ttSHORT(data+22+(k*6));
|
||||
|
||||
it->k += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
5079
packages/media/src/stbtt/stb_truetype.h
Normal file
5079
packages/media/src/stbtt/stb_truetype.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -147,15 +147,24 @@ test sin_x8 {
|
||||
|
||||
pub fn cossin(angle_turns: f32) vm.Complex {
|
||||
@setFloatMode(.optimized);
|
||||
// zig fmt: off
|
||||
// Taylor series expansion for f(x)=cos(xπ/2)
|
||||
const term_cos_0: f32 = 1.0;
|
||||
const term_cos_2: f32 = -1.23370055; // -π²/8
|
||||
const term_cos_4: f32 = 0.253669508; // π⁴/384
|
||||
const term_cos_6: f32 = -0.020863481; // -π⁶/46080
|
||||
const term_cos_0: f32 = 1.000000000e-0; // 1
|
||||
const term_cos_2: f32 = -1.233700550e-0; // -π² /8
|
||||
const term_cos_4: f32 = 2.536695079e-1; // π⁴ /384
|
||||
const term_cos_6: f32 = -2.086348076e-2; // -π⁶ /46080
|
||||
const term_cos_8: f32 = 9.192602748e-4; // π⁸ /10321920
|
||||
const term_cos_10: f32 = -2.520204237e-5; // -π¹⁰/3715891200
|
||||
const term_cos_12: f32 = 4.710874778e-7; // π¹²/1961990553600
|
||||
// Taylor series expansion for f(x)=sin(xπ/2)
|
||||
const term_sin_1: f32 = 1.570796327; // π/2
|
||||
const term_sin_3: f32 = -0.645964098; // -π³/48
|
||||
const term_sin_5: f32 = 0.079692626; // π⁵/3840
|
||||
const term_sin_1: f32 = 1.570796326e-0; // π /2
|
||||
const term_sin_3: f32 = -6.459640975e-1; // -π³ /48
|
||||
const term_sin_5: f32 = 7.969262624e-2; // π⁵ /3840
|
||||
const term_sin_7: f32 = -4.681754135e-3; // -π⁷ /645120
|
||||
const term_sin_9: f32 = 1.604411847e-4; // π⁹ /185794560
|
||||
const term_sin_11: f32 = -3.598843235e-6; // -π¹¹/81749606400
|
||||
const term_sin_13: f32 = 5.692172921e-8; // π¹³/51011754393600
|
||||
// zig fmt: on
|
||||
|
||||
const angle_01 = angle_turns - @floor(angle_turns);
|
||||
const angle_04 = 4.0 * angle_01;
|
||||
@@ -168,8 +177,8 @@ pub fn cossin(angle_turns: f32) vm.Complex {
|
||||
const x = angle_04 - @floor(angle_04);
|
||||
const x2 = x * x;
|
||||
|
||||
const c: u32 = @bitCast(((term_cos_6 * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
||||
const s: u32 = @bitCast(((term_sin_5 * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
||||
const c: u32 = @bitCast((((((term_cos_12 * x2 + term_cos_10) * x2 + term_cos_8) * x2 + term_cos_6) * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
||||
const s: u32 = @bitCast(((((((term_sin_13 * x2 + term_sin_11) * x2 + term_sin_9) * x2 + term_sin_7) * x2 + term_sin_5) * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
||||
|
||||
const result_cos: f32 = @bitCast(((s & quadrant_odd) | (c & ~quadrant_odd)) ^ sign_mask_cos);
|
||||
const result_sin: f32 = @bitCast(((c & quadrant_odd) | (s & ~quadrant_odd)) ^ sign_mask_sin);
|
||||
@@ -190,15 +199,24 @@ test cossin {
|
||||
|
||||
pub fn cossin_x8(angle_turns: vm.f32x8) vm.Complex_x8 {
|
||||
@setFloatMode(.optimized);
|
||||
// zig fmt: off
|
||||
// Taylor series expansion for f(x)=cos(xπ/2)
|
||||
const term_cos_0 = vm.ps(1.0);
|
||||
const term_cos_2 = vm.ps(-1.23370055); // -π²/8
|
||||
const term_cos_4 = vm.ps(0.253669508); // π⁴/384
|
||||
const term_cos_6 = vm.ps(-0.020863481); // -π⁶/46080
|
||||
const term_cos_0 = vm.ps( 1.000000000e-0); // 1
|
||||
const term_cos_2 = vm.ps(-1.233700550e-0); // -π² /8
|
||||
const term_cos_4 = vm.ps( 2.536695079e-1); // π⁴ /384
|
||||
const term_cos_6 = vm.ps(-2.086348076e-2); // -π⁶ /46080
|
||||
const term_cos_8 = vm.ps( 9.192602748e-4); // π⁸ /10321920
|
||||
const term_cos_10 = vm.ps(-2.520204237e-5); // -π¹⁰/3715891200
|
||||
const term_cos_12 = vm.ps( 4.710874778e-7); // π¹²/1961990553600
|
||||
// Taylor series expansion for f(x)=sin(xπ/2)
|
||||
const term_sin_1 = vm.ps(1.570796327); // π/2
|
||||
const term_sin_3 = vm.ps(-0.645964098); // -π³/48
|
||||
const term_sin_5 = vm.ps(0.079692626); // π⁵/3840
|
||||
const term_sin_1 = vm.ps( 1.570796326e-0); // π /2
|
||||
const term_sin_3 = vm.ps(-6.459640975e-1); // -π³ /48
|
||||
const term_sin_5 = vm.ps( 7.969262624e-2); // π⁵ /3840
|
||||
const term_sin_7 = vm.ps(-4.681754135e-3); // -π⁷ /645120
|
||||
const term_sin_9 = vm.ps( 1.604411847e-4); // π⁹ /185794560
|
||||
const term_sin_11 = vm.ps(-3.598843235e-6); // -π¹¹/81749606400
|
||||
const term_sin_13 = vm.ps( 5.692172921e-8); // π¹³/51011754393600
|
||||
// zig fmt: on
|
||||
|
||||
const angle_01 = angle_turns - @floor(angle_turns);
|
||||
const angle_04 = vm.ps(4.0) * angle_01;
|
||||
@@ -211,8 +229,8 @@ pub fn cossin_x8(angle_turns: vm.f32x8) vm.Complex_x8 {
|
||||
const x = angle_04 - @floor(angle_04);
|
||||
const x2 = x * x;
|
||||
|
||||
const c: vm.u32x8 = @bitCast(((term_cos_6 * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
||||
const s: vm.u32x8 = @bitCast(((term_sin_5 * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
||||
const c: vm.u32x8 = @bitCast((((((term_cos_12 * x2 + term_cos_10) * x2 + term_cos_8) * x2 + term_cos_6) * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
||||
const s: vm.u32x8 = @bitCast(((((((term_sin_13 * x2 + term_sin_11) * x2 + term_sin_9) * x2 + term_sin_7) * x2 + term_sin_5) * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
||||
|
||||
const result_cos: vm.f32x8 = @bitCast(@select(u32, quadrant_odd, s, c) ^ sign_mask_cos);
|
||||
const result_sin: vm.f32x8 = @bitCast(@select(u32, quadrant_odd, c, s) ^ sign_mask_sin);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.{
|
||||
.name = .web,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
|
||||
81
packages/web/src/Address.zig
Normal file
81
packages/web/src/Address.zig
Normal file
@@ -0,0 +1,81 @@
|
||||
const std = @import("std");
|
||||
|
||||
const linux = std.os.linux;
|
||||
|
||||
pub const Address = extern union {
|
||||
any: linux.sockaddr,
|
||||
in: linux.sockaddr.in,
|
||||
in6: linux.sockaddr.in6,
|
||||
un: linux.sockaddr.un,
|
||||
|
||||
pub fn initIp4(addr: [4]u8, port: u16) Address {
|
||||
return .{
|
||||
.in = .{
|
||||
.addr = @bitCast(addr),
|
||||
.port = std.mem.nativeToBig(u16, port),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
|
||||
return .{
|
||||
.in6 = .{
|
||||
.addr = addr,
|
||||
.port = std.mem.nativeToBig(u16, port),
|
||||
.flowinfo = flowinfo,
|
||||
.scope_id = scope_id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initUnix(path: []const u8) !Address {
|
||||
var un: linux.sockaddr.un = .{
|
||||
.path = @splat(0),
|
||||
};
|
||||
|
||||
// Ensure there is space for null terminator; already present by
|
||||
// initializing the path to zeroes above.
|
||||
if (path.len + 1 > un.path.len) return error.NameTooLong;
|
||||
|
||||
@memcpy(&un.path[0..path.len], path);
|
||||
|
||||
return .{ .un = un };
|
||||
}
|
||||
|
||||
pub fn getOsSockLen(self: Address) linux.socklen_t {
|
||||
return switch (self.any.family) {
|
||||
linux.AF.INET => @sizeOf(linux.sockaddr.in),
|
||||
linux.AF.INET6 => @sizeOf(linux.sockaddr.in6),
|
||||
// NOTE According to zig's 0.15.2 stdlib, this is technically always
|
||||
// correct especially since we ensure there is a space for a null
|
||||
// terminator in init.
|
||||
linux.AF.UNIX => @sizeOf(linux.sockaddr.un),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: *const Address, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
switch (self.any.family) {
|
||||
linux.AF.INET => {
|
||||
const addr: std.Io.net.Ip4Address = .{
|
||||
.bytes = @bitCast(self.in.addr),
|
||||
.port = std.mem.bigToNative(u16, self.in.port),
|
||||
};
|
||||
|
||||
try addr.format(writer);
|
||||
},
|
||||
linux.AF.INET6 => {
|
||||
const addr: std.Io.net.Ip6Address.Unresolved = .{
|
||||
.bytes = self.in6.addr,
|
||||
.interface_name = null,
|
||||
};
|
||||
|
||||
try addr.format(writer);
|
||||
},
|
||||
linux.AF.UNIX => {
|
||||
try writer.writeAll(std.mem.sliceTo(&self.un.path, 0));
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,13 +1,14 @@
|
||||
const std = @import("std");
|
||||
const Connection = @This();
|
||||
|
||||
const Address = @import("Address.zig").Address;
|
||||
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
const openssl = @import("openssl.zig");
|
||||
|
||||
const iovec = std.posix.iovec;
|
||||
const iovec_const = std.posix.iovec_const;
|
||||
|
||||
address: std.net.Address,
|
||||
address: Address,
|
||||
fd: FileDescriptor,
|
||||
ssl: ?*openssl.Ssl,
|
||||
node: std.DoublyLinkedList.Node = .{},
|
||||
@@ -17,7 +18,7 @@ node: std.DoublyLinkedList.Node = .{},
|
||||
|
||||
pub fn reinit(
|
||||
self: *Connection,
|
||||
address: std.net.Address,
|
||||
address: Address,
|
||||
fd: FileDescriptor,
|
||||
ssl: ?*openssl.Ssl,
|
||||
) void {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const linux = std.os.linux;
|
||||
const errno = linux.E.init;
|
||||
const errno = linux.errno;
|
||||
|
||||
const iovec = std.posix.iovec;
|
||||
const iovec_const = std.posix.iovec_const;
|
||||
@@ -28,6 +28,19 @@ pub const FileDescriptor = enum(i32) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn open(path: [*:0]const u8, flags: linux.O, perm: linux.mode_t) !FileDescriptor {
|
||||
while (true) {
|
||||
const rc = linux.open(path, flags, perm);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return @enumFromInt(@as(i32, @intCast(rc))),
|
||||
.INTR => continue,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.ACCESS => return error.AccessDenied,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(self: FileDescriptor) void {
|
||||
_ = linux.close(@intFromEnum(self));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const UUID = @import("UUID.zig");
|
||||
const decoder = &std.base64.url_safe_no_pad.Decoder;
|
||||
const encoder = &std.base64.url_safe_no_pad.Encoder;
|
||||
|
||||
pub fn Id(comptime _tag: @Type(.enum_literal)) type {
|
||||
pub fn Id(comptime _tag: @EnumLiteral()) type {
|
||||
return struct {
|
||||
pub const tag = _tag;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Server = @This();
|
||||
|
||||
const Address = @import("Address.zig").Address;
|
||||
const Connection = @import("Connection.zig");
|
||||
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
const http = @import("http.zig");
|
||||
@@ -11,10 +12,10 @@ const Worker = @import("Worker.zig");
|
||||
|
||||
const log = std.log.scoped(.Server);
|
||||
const linux = std.os.linux;
|
||||
const errno = linux.E.init;
|
||||
const errno = linux.errno;
|
||||
|
||||
fd: FileDescriptor,
|
||||
address: std.net.Address,
|
||||
address: Address,
|
||||
ssl_ctx: ?*openssl.SslContext,
|
||||
workers: []Worker,
|
||||
threads: []std.Thread,
|
||||
@@ -29,9 +30,10 @@ connection_queue: std.DoublyLinkedList,
|
||||
connection_pool: std.DoublyLinkedList,
|
||||
connection_buffer: []Connection,
|
||||
|
||||
mutex: std.Thread.Mutex,
|
||||
cond_connection_queued: std.Thread.Condition,
|
||||
cond_connection_freed: std.Thread.Condition,
|
||||
io: std.Io,
|
||||
mutex: std.Io.Mutex,
|
||||
cond_connection_queued: std.Io.Condition,
|
||||
cond_connection_freed: std.Io.Condition,
|
||||
|
||||
/// 4 kiB
|
||||
const page_size = 4 * 1024;
|
||||
@@ -40,7 +42,7 @@ const huge_page_size = 2 * 1024 * 1024;
|
||||
|
||||
pub const Options = struct {
|
||||
request_handler: RequestHandler,
|
||||
address: std.net.Address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
|
||||
address: Address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
|
||||
/// If not `null`, the server will use TLS with the provided OpenSSL
|
||||
/// context.
|
||||
ssl_ctx: ?*openssl.SslContext = null,
|
||||
@@ -73,7 +75,7 @@ pub const Options = struct {
|
||||
read_timeout_us: u64 = 1 * std.time.us_per_s,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
pub fn init(allocator: std.mem.Allocator, io: std.Io, options: Options) !Server {
|
||||
const worker_count = if (options.worker_count > 0) options.worker_count else try std.Thread.getCpuCount();
|
||||
|
||||
// Create socket fd
|
||||
@@ -123,7 +125,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
const read_buffer_ptr = try errOrPtr(linux.mmap(
|
||||
null,
|
||||
double_all_read_buffers_size,
|
||||
linux.PROT.NONE,
|
||||
.{},
|
||||
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
-1,
|
||||
0,
|
||||
@@ -138,8 +140,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
try err(linux.mmap(
|
||||
read_buffer_ptr + double_offset,
|
||||
single_read_buffer_size,
|
||||
linux.PROT.READ | linux.PROT.WRITE,
|
||||
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.{ .TYPE = .SHARED, .FIXED = true },
|
||||
@intFromEnum(read_buffer_fd),
|
||||
@intCast(offset),
|
||||
));
|
||||
@@ -147,8 +149,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
try err(linux.mmap(
|
||||
read_buffer_ptr + double_offset + single_read_buffer_size,
|
||||
single_read_buffer_size,
|
||||
linux.PROT.READ | linux.PROT.WRITE,
|
||||
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.{ .TYPE = .SHARED, .FIXED = true },
|
||||
@intFromEnum(read_buffer_fd),
|
||||
@intCast(offset),
|
||||
));
|
||||
@@ -162,8 +164,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
const header_write_buffer_ptr = try errOrPtr(linux.mmap(
|
||||
null,
|
||||
all_header_write_buffers_size,
|
||||
linux.PROT.READ | linux.PROT.WRITE,
|
||||
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
-1,
|
||||
0,
|
||||
));
|
||||
@@ -177,8 +179,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
const body_write_buffer_ptr = try errOrPtr(linux.mmap(
|
||||
null,
|
||||
all_body_write_buffers_size,
|
||||
linux.PROT.READ | linux.PROT.WRITE,
|
||||
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
-1,
|
||||
0,
|
||||
));
|
||||
@@ -232,9 +234,10 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
.connection_pool = connection_pool,
|
||||
.connection_buffer = connection_buffer,
|
||||
|
||||
.mutex = .{},
|
||||
.cond_connection_queued = .{},
|
||||
.cond_connection_freed = .{},
|
||||
.io = io,
|
||||
.mutex = .init,
|
||||
.cond_connection_queued = .init,
|
||||
.cond_connection_freed = .init,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,7 +287,7 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||
log.debug("Storing `false` into worker_running.", .{});
|
||||
worker_running.store(false, .release);
|
||||
log.debug("Broadcasting connection queued condition variable.", .{});
|
||||
self.cond_connection_queued.broadcast();
|
||||
self.cond_connection_queued.broadcast(self.io);
|
||||
for (self.threads[0..spawned], 0..) |*thread, i| {
|
||||
log.debug("Joining the thread of worker #{d}.", .{i});
|
||||
thread.join();
|
||||
@@ -298,8 +301,8 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||
}
|
||||
|
||||
while (running.load(.acquire)) {
|
||||
var address: std.net.Address = undefined;
|
||||
var address_size: u32 = @sizeOf(std.net.Address);
|
||||
var address: Address = undefined;
|
||||
var address_size: u32 = @sizeOf(Address);
|
||||
|
||||
log.debug("Accepting connection.", .{});
|
||||
const fd = self.fd.accept(&address.any, &address_size) catch |e| {
|
||||
@@ -322,11 +325,11 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||
|
||||
{
|
||||
log.debug("Acquiring mutex.", .{});
|
||||
self.mutex.lock();
|
||||
self.mutex.lockUncancelable(self.io);
|
||||
log.debug("Acquired mutex.", .{});
|
||||
defer {
|
||||
log.debug("Unlocking mutex.", .{});
|
||||
self.mutex.unlock();
|
||||
self.mutex.unlock(self.io);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
@@ -339,13 +342,13 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||
}
|
||||
|
||||
log.debug("Waiting on connection freed condition variable.", .{});
|
||||
self.cond_connection_freed.wait(&self.mutex);
|
||||
self.cond_connection_freed.waitUncancelable(self.io, &self.mutex);
|
||||
log.debug("Woken up on connection freed condition variable.", .{});
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Signaling connection queued condition variable.", .{});
|
||||
self.cond_connection_queued.signal();
|
||||
self.cond_connection_queued.signal(self.io);
|
||||
} else {
|
||||
log.debug("Loaded `false` from running, the accept loop exited.", .{});
|
||||
}
|
||||
@@ -358,6 +361,10 @@ fn maybeInitSsl(self: *const Server, fd: FileDescriptor) !?*openssl.Ssl {
|
||||
log.debug("Accepting SSL layer.", .{});
|
||||
try ssl.accept();
|
||||
log.debug("Accepted SSL layer.", .{});
|
||||
const opt = std.mem.toBytes(@as(c_int, 1));
|
||||
if (fd.setsockopt(linux.SOL.TLS, 3, &opt)) { // TLS_TX_ZEROCOPY_RO
|
||||
log.debug("Enabled zero-copy on sendfile.", .{});
|
||||
} else |_| {}
|
||||
return ssl;
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -5,9 +5,10 @@ bytes: [16]u8,
|
||||
|
||||
pub const zero: UUID = .{ .bytes = @splat(0) };
|
||||
|
||||
pub fn v4() UUID {
|
||||
pub fn v4(io: std.Io) UUID {
|
||||
var bytes: [16]u8 = undefined;
|
||||
std.crypto.random.bytes(&bytes);
|
||||
|
||||
io.random(&bytes);
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
return .{ .bytes = bytes };
|
||||
@@ -35,7 +36,7 @@ pub const namespaces = struct {
|
||||
pub fn v5(namespace: UUID, name: []const u8) UUID {
|
||||
const hash = blk: {
|
||||
var hasher = std.crypto.hash.Sha1.init(.{});
|
||||
hasher.update(namespace);
|
||||
hasher.update(namespace.bytes);
|
||||
hasher.update(name);
|
||||
break :blk hasher.finalResult();
|
||||
};
|
||||
@@ -47,13 +48,13 @@ pub fn v5(namespace: UUID, name: []const u8) UUID {
|
||||
return .{ .bytes = bytes };
|
||||
}
|
||||
|
||||
var v7_lock: std.Thread.Mutex = .{};
|
||||
var v7_last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
|
||||
var v7_counter: std.atomic.Value(u32) = .{ .raw = 0 };
|
||||
var v7_lock: std.Io.Mutex = .init;
|
||||
var v7_last_timestamp: std.atomic.Value(u64) = .init(0);
|
||||
var v7_counter: std.atomic.Value(u32) = .init(0);
|
||||
|
||||
fn getCount(timestamp: u64) u32 {
|
||||
v7_lock.lock();
|
||||
defer v7_lock.unlock();
|
||||
fn getCount(io: std.Io, timestamp: u64) u32 {
|
||||
v7_lock.lockUncancelable(io);
|
||||
defer v7_lock.unlock(io);
|
||||
|
||||
if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) {
|
||||
v7_counter.store(0, .monotonic);
|
||||
@@ -62,12 +63,12 @@ fn getCount(timestamp: u64) u32 {
|
||||
return v7_counter.fetchAdd(1, .monotonic) % 4096;
|
||||
}
|
||||
|
||||
pub fn v7() UUID {
|
||||
pub fn v7(io: std.Io) UUID {
|
||||
const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
|
||||
const count = getCount(timestamp);
|
||||
const random = blk: {
|
||||
var bytes: [8]u8 = undefined;
|
||||
std.crypto.random.bytes(&bytes);
|
||||
io.random(&bytes);
|
||||
break :blk bytes;
|
||||
};
|
||||
|
||||
|
||||
@@ -77,11 +77,11 @@ pub fn worker(
|
||||
running: *const std.atomic.Value(bool),
|
||||
) void {
|
||||
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||
server.mutex.lock();
|
||||
server.mutex.lockUncancelable(server.io);
|
||||
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
|
||||
defer {
|
||||
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
|
||||
server.mutex.unlock();
|
||||
server.mutex.unlock(server.io);
|
||||
}
|
||||
|
||||
while (running.load(.acquire)) {
|
||||
@@ -90,16 +90,16 @@ pub fn worker(
|
||||
log.debug("[#{d}] Popped connection to {f} from the connection queue.", .{ self.worker_id, connection.address });
|
||||
|
||||
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
|
||||
server.mutex.unlock();
|
||||
server.mutex.unlock(server.io);
|
||||
defer {
|
||||
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||
server.mutex.lock();
|
||||
server.mutex.lockUncancelable(server.io);
|
||||
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
|
||||
|
||||
log.debug("[#{d}] Returning connection to connection pool.", .{self.worker_id});
|
||||
server.connection_pool.append(&connection.node);
|
||||
log.debug("[#{d}] Signaling connection freed condition variable.", .{self.worker_id});
|
||||
server.cond_connection_freed.signal();
|
||||
server.cond_connection_freed.signal(server.io);
|
||||
}
|
||||
|
||||
log.debug("[#{d}] Handling connection to {f}.", .{ self.worker_id, connection.address });
|
||||
@@ -108,7 +108,7 @@ pub fn worker(
|
||||
};
|
||||
} else {
|
||||
log.debug("[#{d}] Waiting on connection queued condition variable.", .{self.worker_id});
|
||||
server.cond_connection_queued.wait(&server.mutex);
|
||||
server.cond_connection_queued.waitUncancelable(server.io, &server.mutex);
|
||||
log.debug("[#{d}] Woken up on connection queued condition variable.", .{self.worker_id});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
//!
|
||||
//! The parser is not involved in any HTTP semantics, only its syntax. It is up
|
||||
//! to the user of this parser to respect all of the HTTP standards (if they
|
||||
//! even choose to). For example, none of the header field valuess are verified.
|
||||
//! The only exception is `Content-Length`. The parser must know the value to
|
||||
//! determine the length of the request body. If the value fails to parse as a
|
||||
//! decimal non-negative integer, a syntax error is returned. Note that
|
||||
//! even choose to). For example, none of the header field values are verified,
|
||||
//! except for `Content-Length`. The parser must know the value of
|
||||
//! `Content-Length` to determine the length of the request body. If the value
|
||||
//! fails to parse as a decimal non-negative integer, a syntax error is
|
||||
//! according to [RFC 9110, Section 8.6: HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110#section-8.6),
|
||||
//! `Content-Length` header field value consisting of the same decimal value
|
||||
//! repeated as a comma-separated list (e.g. `Content-Length: 42, 42`) MAY be
|
||||
|
||||
@@ -4,23 +4,17 @@ const web = @import("web");
|
||||
const linux = std.os.linux;
|
||||
const errno = linux.E.init;
|
||||
const ssl = web.openssl;
|
||||
const UUID = web.UUID;
|
||||
|
||||
var running: std.atomic.Value(bool) = .init(true);
|
||||
|
||||
fn interruptionHandler(sig: i32) callconv(.c) void {
|
||||
const body_write_buffer_huge_pages = 1;
|
||||
|
||||
fn interruptionHandler(sig: linux.SIG) callconv(.c) void {
|
||||
var buf: [32]u8 = undefined;
|
||||
|
||||
const signal_name = blk: inline for (@typeInfo(linux.SIG).@"struct".decls) |decl| {
|
||||
if (comptime std.mem.eql(u8, decl.name, "BLOCK") or
|
||||
std.mem.eql(u8, decl.name, "UNBLOCK") or
|
||||
std.mem.eql(u8, decl.name, "SETMASK")) continue;
|
||||
|
||||
const decl_value = @field(linux.SIG, decl.name);
|
||||
if (@TypeOf(decl_value) != comptime_int) continue;
|
||||
if (decl_value == sig) break :blk "SIG" ++ decl.name;
|
||||
} else {
|
||||
break :blk std.fmt.bufPrint(&buf, "#{d}", .{sig}) catch unreachable;
|
||||
const signal_name = switch (sig) {
|
||||
_ => std.fmt.bufPrint(&buf, "#{d}", .{sig}) catch unreachable,
|
||||
inline else => |x| "SIG" ++ @tagName(x),
|
||||
};
|
||||
|
||||
std.log.debug("Interrupted with signal {s}.", .{signal_name});
|
||||
@@ -34,6 +28,28 @@ fn interruptionHandler(sig: i32) callconv(.c) void {
|
||||
}
|
||||
|
||||
const Handler = struct {
|
||||
fn notFound(response: *web.Response) !void {
|
||||
try response.body_writer.writeAll("Not Found\n");
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.not_found);
|
||||
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
}
|
||||
|
||||
fn methodNotAllowed(response: *web.Response) !void {
|
||||
try response.body_writer.writeAll("Method Not Allowed\n");
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.method_not_allowed);
|
||||
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
}
|
||||
|
||||
fn handle(_: *anyopaque, request: *web.Request, response: *web.Response) !void {
|
||||
std.log.info("{f} | {s} {s}", .{
|
||||
response.connection.address,
|
||||
@@ -41,38 +57,50 @@ const Handler = struct {
|
||||
request.pathname,
|
||||
});
|
||||
|
||||
if (!std.mem.eql(u8, request.pathname, "/")) {
|
||||
try response.body_writer.writeAll("Not Found\n");
|
||||
if (std.mem.eql(u8, request.pathname, "/")) {
|
||||
if (request.method != .GET) {
|
||||
try methodNotAllowed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.not_found);
|
||||
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
|
||||
try response.body_writer.writeAll("{\"ok\":true}\n");
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.ok);
|
||||
try response.header_writer.writeAll("Content-Type: application/json\r\n");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
return;
|
||||
}
|
||||
} else if (std.mem.startsWith(u8, request.pathname, "/zeroes/")) {
|
||||
const bytes = std.fmt.parseUnsigned(usize, request.pathname[8..], 10) catch {
|
||||
try notFound(response);
|
||||
return;
|
||||
};
|
||||
|
||||
if (request.method != .GET) {
|
||||
try response.body_writer.writeAll("Method Not Allowed\n");
|
||||
if (bytes > body_write_buffer_huge_pages * 2 * 1024 * 1024) {
|
||||
try notFound(response);
|
||||
return;
|
||||
}
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.method_not_allowed);
|
||||
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
|
||||
if (request.method != .GET) {
|
||||
try methodNotAllowed(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = [_]u8{0};
|
||||
var data = [_][]const u8{&buffer};
|
||||
|
||||
try response.body_writer.writeSplatAll(&data, bytes);
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.ok);
|
||||
try response.header_writer.writeAll("Content-Type: application/octet-stream\r\n");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
return;
|
||||
} else {
|
||||
try notFound(response);
|
||||
}
|
||||
|
||||
try response.body_writer.writeAll("{\"ok\":true}\n");
|
||||
|
||||
try response.header_writer.writeAll(web.http.status.ok);
|
||||
try response.header_writer.writeAll("Content-Type: application/json\r\n");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
}
|
||||
|
||||
fn interface() web.RequestHandler {
|
||||
@@ -85,12 +113,8 @@ const Handler = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{
|
||||
.thread_safe = true,
|
||||
}) = .init;
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const allocator = init.gpa;
|
||||
|
||||
_ = ssl.c_ssl.SSL_library_init();
|
||||
_ = ssl.c_ssl.OpenSSL_add_all_algorithms();
|
||||
@@ -99,16 +123,21 @@ pub fn main() !void {
|
||||
const ssl_ctx = try ssl.SslContext.new(.tlsServerMethod());
|
||||
defer ssl_ctx.free();
|
||||
|
||||
_ = ssl_ctx.setMinProtoVersion(ssl.c_ssl.TLS1_3_VERSION);
|
||||
_ = ssl_ctx.setOptions(ssl.c_ssl.SSL_OP_NO_SSLv3 | ssl.c_ssl.SSL_OP_NO_TLSv1 | ssl.c_ssl.SSL_OP_NO_TLSv1_1 | ssl.c_ssl.SSL_OP_NO_TLSv1_2);
|
||||
_ = ssl_ctx.setMinProtoVersion(ssl.c_ssl.TLS1_2_VERSION);
|
||||
_ = ssl_ctx.setMaxProtoVersion(ssl.c_ssl.TLS1_2_VERSION);
|
||||
_ = ssl_ctx.setOptions(ssl.c_ssl.SSL_OP_NO_SSLv3 |
|
||||
ssl.c_ssl.SSL_OP_NO_TLSv1 |
|
||||
ssl.c_ssl.SSL_OP_NO_TLSv1_1 |
|
||||
ssl.c_ssl.SSL_OP_NO_TLSv1_3 |
|
||||
ssl.c_ssl.SSL_OP_ENABLE_KTLS);
|
||||
|
||||
try ssl_ctx.useCertificateFile("cert.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
|
||||
try ssl_ctx.usePrivateKeyFile("key.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
|
||||
try ssl_ctx.checkPrivateKey();
|
||||
|
||||
var server = try web.Server.init(allocator, .{
|
||||
var server = try web.Server.init(allocator, init.io, .{
|
||||
.request_handler = Handler.interface(),
|
||||
.address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
|
||||
.body_write_buffer_huge_pages = body_write_buffer_huge_pages,
|
||||
.ssl_ctx = ssl_ctx,
|
||||
});
|
||||
defer server.deinit(allocator);
|
||||
@@ -118,13 +147,13 @@ pub fn main() !void {
|
||||
.mask = linux.sigemptyset(),
|
||||
.flags = linux.SA.RESETHAND,
|
||||
};
|
||||
signal(linux.SIG.INT, &sigaction);
|
||||
signal(linux.SIG.TERM, &sigaction);
|
||||
signal(.INT, &sigaction);
|
||||
signal(.TERM, &sigaction);
|
||||
|
||||
try server.listen(&running);
|
||||
}
|
||||
|
||||
fn signal(sig: u8, action: *const linux.Sigaction) void {
|
||||
fn signal(sig: linux.SIG, action: *const linux.Sigaction) void {
|
||||
var old_action = std.mem.zeroes(linux.Sigaction);
|
||||
_ = linux.sigaction(sig, null, &old_action);
|
||||
if (old_action.handler.handler == linux.SIG.IGN) return;
|
||||
|
||||
171
packages/web/src/mime.zig
Normal file
171
packages/web/src/mime.zig
Normal file
@@ -0,0 +1,171 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const extension_to_media_type: std.StaticStringMap([]const u8) = .initComptime(.{
|
||||
.{ "7z", "application/x-7z-compressed" },
|
||||
.{ "aac", "audio/aac" },
|
||||
.{ "abw", "application/x-abiword" },
|
||||
.{ "apng", "image/apng" },
|
||||
.{ "arc", "application/x-freearc" },
|
||||
.{ "avi", "video/x-msvideo" },
|
||||
.{ "avif", "image/avif" },
|
||||
.{ "azw", "application/vnd.amazon.ebook" },
|
||||
.{ "bin", "application/octet-stream" },
|
||||
.{ "blend", "application/x-blender" },
|
||||
.{ "bmp", "image/bmp" },
|
||||
.{ "bsp", "model/vnd.valve.source.compiled-map" },
|
||||
.{ "bz", "application/x-bzip" },
|
||||
.{ "bz2", "application/x-bzip2" },
|
||||
.{ "cbor", "application/cbor" },
|
||||
.{ "cda", "application/x-cdf" },
|
||||
.{ "cer", "application/pkix-cert" },
|
||||
.{ "chm", "application/vnd.ms-htmlhelp" },
|
||||
.{ "csh", "application/x-csh" },
|
||||
.{ "css", "text/css" },
|
||||
.{ "csv", "text/csv" },
|
||||
.{ "dae", "model/vnd.collada+xml" },
|
||||
.{ "docm", "application/vnd.ms-word.document.macroEnabled.12" },
|
||||
.{ "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
|
||||
.{ "dotm", "application/vnd.ms-word.template.macroEnabled.12" },
|
||||
.{ "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
|
||||
.{ "dwf", "model/vnd.dwf" },
|
||||
.{ "dwg", "image/vnd.dwg" },
|
||||
.{ "dxf", "image/vnd.dxf" },
|
||||
.{ "efi", "application/efi" },
|
||||
.{ "eot", "application/vnd.ms-fontobject" },
|
||||
.{ "epub", "application/epub+zip" },
|
||||
.{ "exr", "image/x-exr" },
|
||||
.{ "flac", "audio/flac" },
|
||||
.{ "flv", "video/x-flv" },
|
||||
.{ "gif", "image/gif" },
|
||||
.{ "glb", "model/gltf-binary" },
|
||||
.{ "glbin", "application/gltf-buffer" },
|
||||
.{ "glbuf", "application/gltf-buffer" },
|
||||
.{ "gltf", "model/gltf+json" },
|
||||
.{ "gz", "application/gzip" },
|
||||
.{ "hdr", "image/vnd.radiance" },
|
||||
.{ "heic", "image/heic" },
|
||||
.{ "heif", "image/heif" },
|
||||
.{ "htm", "text/html" },
|
||||
.{ "html", "text/html" },
|
||||
.{ "ico", "image/vnd.microsoft.icon" },
|
||||
.{ "ics", "text/calendar" },
|
||||
.{ "iges", "model/iges" },
|
||||
.{ "igs", "model/iges" },
|
||||
.{ "jar", "application/java-archive" },
|
||||
.{ "jpeg", "image/jpeg" },
|
||||
.{ "jpg", "image/jpeg" },
|
||||
.{ "js", "text/javascript" },
|
||||
.{ "json", "application/json" },
|
||||
.{ "jsonld", "application/ld+json" },
|
||||
.{ "jxl", "image/jxl" },
|
||||
.{ "ktx", "image/ktx" },
|
||||
.{ "ktx2", "image/ktx2" },
|
||||
.{ "m3u", "application/vnd.apple.mpegurl" },
|
||||
.{ "m3u8", "application/vnd.apple.mpegurl" },
|
||||
.{ "m4u", "video/vnd.mpegurl" },
|
||||
.{ "md", "text/markdown" },
|
||||
.{ "mdb", "application/vnd.ms-access" },
|
||||
.{ "mdi", "image/vnd.ms-modi" },
|
||||
.{ "mid", "audio/midi" },
|
||||
.{ "midi", "audio/midi" },
|
||||
.{ "mjs", "text/javascript" },
|
||||
.{ "mka", "audio/matroska" },
|
||||
.{ "mkv", "video/matroska" },
|
||||
.{ "mml", "application/mathml+xml" },
|
||||
.{ "mov", "video/quicktime" },
|
||||
.{ "mp3", "audio/mpeg" },
|
||||
.{ "mp4", "video/mp4" },
|
||||
.{ "mpeg", "video/mpeg" },
|
||||
.{ "mpkg", "application/vnd.apple.installer+xml" },
|
||||
.{ "mtl", "model/mtl" },
|
||||
.{ "mxl", "application/vnd.recordare.musicxml" },
|
||||
.{ "mxu", "video/vnd.mpegurl" },
|
||||
.{ "obj", "model/obj" }, // .obj could be MSVC object file too, but Wavefront OBJ is more likely
|
||||
.{ "odp", "application/vnd.oasis.opendocument.presentation" },
|
||||
.{ "ods", "application/vnd.oasis.opendocument.spreadsheet" },
|
||||
.{ "odt", "application/vnd.oasis.opendocument.text" },
|
||||
.{ "oga", "audio/ogg" },
|
||||
.{ "ogex", "model/vnd.opengex" },
|
||||
.{ "ogv", "video/ogg" },
|
||||
.{ "ogx", "application/ogg" },
|
||||
.{ "opus", "audio/ogg" },
|
||||
.{ "otf", "font/otf" },
|
||||
.{ "p10", "application/pkcs10" },
|
||||
.{ "p12", "application/pkcs12" },
|
||||
.{ "p8", "application/pkcs8" },
|
||||
.{ "pdf", "application/pdf" },
|
||||
.{ "pfx", "application/pkcs12" },
|
||||
.{ "php", "application/x-httpd-php" },
|
||||
.{ "pki", "application/pkixcmp" },
|
||||
.{ "png", "image/png" },
|
||||
.{ "pot", "application/vnd.ms-powerpoint" },
|
||||
.{ "potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
|
||||
.{ "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
|
||||
.{ "ppa", "application/vnd.ms-powerpoint" },
|
||||
.{ "ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
|
||||
.{ "pps", "application/vnd.ms-powerpoint" },
|
||||
.{ "ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
|
||||
.{ "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
|
||||
.{ "ppt", "application/vnd.ms-powerpoint" },
|
||||
.{ "pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
|
||||
.{ "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
|
||||
.{ "ps", "application/postscript" },
|
||||
.{ "psd", "image/vnd.adobe.photoshop" },
|
||||
.{ "qoa", "audio/qoa" },
|
||||
.{ "qoi", "image/qoi" },
|
||||
.{ "qt", "video/quicktime" },
|
||||
.{ "rar", "application/vnd.rar" },
|
||||
.{ "rmvb", "application/vnd.rn-realmedia-vbr" },
|
||||
.{ "rtf", "application/rtf" },
|
||||
.{ "sf2", "audio/soundfont" },
|
||||
.{ "sh", "application/x-sh" },
|
||||
.{ "sql", "application/sql" },
|
||||
.{ "step", "model/step" },
|
||||
.{ "stl", "model/stl" },
|
||||
.{ "stp", "model/step" },
|
||||
.{ "stpx", "model/step+xml" },
|
||||
.{ "stpxz", "model/step-xml+zip" },
|
||||
.{ "stpz", "model/step+zip" },
|
||||
.{ "svg", "image/svg+xml" },
|
||||
.{ "tar", "application/x-tar" },
|
||||
.{ "tif", "image/tiff" },
|
||||
.{ "tiff", "image/tiff" },
|
||||
.{ "toml", "application/toml" },
|
||||
.{ "ttf", "font/ttf" },
|
||||
.{ "txt", "text/plain" },
|
||||
.{ "usda", "model/vnd.usda" },
|
||||
.{ "usdz", "model/vnd.usdz+zip" },
|
||||
.{ "vcard", "text/vcard" },
|
||||
.{ "vcf", "text/vcard" },
|
||||
.{ "vsd", "application/vnd.visio" },
|
||||
.{ "vtf", "image/vnd.valve.source.texture" },
|
||||
.{ "wav", "audio/wav" },
|
||||
.{ "weba", "audio/webm" },
|
||||
.{ "webm", "video/webm" },
|
||||
.{ "webmanifest", "application/manifest+json" },
|
||||
.{ "webp", "image/webp" },
|
||||
.{ "wgsl", "text/wgsl" },
|
||||
.{ "wma", "audio/x-ms-asf" },
|
||||
.{ "wmv", "video/x-ms-asf" },
|
||||
.{ "woff", "font/woff" },
|
||||
.{ "woff2", "font/woff2" },
|
||||
.{ "xht", "application/xhtml+xml" },
|
||||
.{ "xhtml", "application/xhtml+xml" },
|
||||
.{ "xla", "application/vnd.ms-excel" },
|
||||
.{ "xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
|
||||
.{ "xls", "application/vnd.ms-excel" },
|
||||
.{ "xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
|
||||
.{ "xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
|
||||
.{ "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
|
||||
.{ "xlt", "application/vnd.ms-excel" },
|
||||
.{ "xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
|
||||
.{ "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
|
||||
.{ "xml", "application/xml" },
|
||||
.{ "xsl", "application/xslt+xml" },
|
||||
.{ "xslt", "application/xslt+xml" },
|
||||
.{ "xul", "application/vnd.mozilla.xul+xml" },
|
||||
.{ "yaml", "application/yaml" },
|
||||
.{ "yml", "application/yaml" },
|
||||
.{ "zip", "application/zip" },
|
||||
.{ "zst", "application/zst" },
|
||||
});
|
||||
@@ -5,10 +5,12 @@ comptime {
|
||||
std.debug.assert(@import("builtin").os.tag == .linux);
|
||||
}
|
||||
|
||||
pub const Address = @import("Address.zig").Address;
|
||||
pub const Connection = @import("Connection.zig");
|
||||
pub const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
pub const http = @import("http.zig");
|
||||
pub const Id = @import("Id.zig").Id;
|
||||
pub const mime = @import("mime.zig");
|
||||
pub const openssl = @import("openssl.zig");
|
||||
pub const Request = @import("Request.zig");
|
||||
pub const RequestHandler = @import("RequestHandler.zig");
|
||||
|
||||
59
packages/xcb/build.zig
Normal file
59
packages/xcb/build.zig
Normal file
@@ -0,0 +1,59 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const vulkan_dep = b.dependency("vulkan_zig", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.registry = b.path("vendor/vk.xml"),
|
||||
});
|
||||
|
||||
const vulkan_mod = vulkan_dep.module("vulkan-zig");
|
||||
|
||||
const mod = b.addModule("xcb", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
mod.linkSystemLibrary("xcb", .{});
|
||||
|
||||
const mod_tests = b.addTest(.{
|
||||
.root_module = mod,
|
||||
});
|
||||
|
||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_mod_tests.step);
|
||||
|
||||
const sample_module = b.createModule(.{
|
||||
.root_source_file = b.path("sample/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "vulkan", .module = vulkan_mod },
|
||||
.{ .name = "xcb", .module = mod },
|
||||
},
|
||||
});
|
||||
|
||||
const sample_exe = b.addExecutable(.{
|
||||
.name = "sample",
|
||||
.root_module = sample_module,
|
||||
});
|
||||
|
||||
const sample_install = b.addInstallArtifact(sample_exe, .{});
|
||||
|
||||
const run_sample = b.addRunArtifact(sample_exe);
|
||||
run_sample.step.dependOn(&sample_install.step);
|
||||
|
||||
if (b.args) |args| {
|
||||
run_sample.addArgs(args);
|
||||
}
|
||||
|
||||
const sample_step = b.step("sample", "Run sample");
|
||||
sample_step.dependOn(&run_sample.step);
|
||||
}
|
||||
17
packages/xcb/build.zig.zon
Normal file
17
packages/xcb/build.zig.zon
Normal file
@@ -0,0 +1,17 @@
|
||||
.{
|
||||
.name = .xcb,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{
|
||||
.vulkan_zig = .{
|
||||
.url = "git+https://github.com/Snektron/vulkan-zig#b496a6a561ffbbeb530b0f9ed4e059f88c0723a5",
|
||||
.hash = "vulkan-0.0.0-r7Ytx7N9AwD7IZt5_XNHtkJ4G9qY0pCH-cpcOsbL8wzD",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
.fingerprint = 0x48cfd8ea06caab92,
|
||||
}
|
||||
176
packages/xcb/sample/Vulkan.zig
Normal file
176
packages/xcb/sample/Vulkan.zig
Normal file
@@ -0,0 +1,176 @@
|
||||
const Vulkan = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const xcb = @import("xcb");
|
||||
|
||||
vulkan_loader: *DynamicLibrary,
|
||||
|
||||
base: vk.BaseWrapper,
|
||||
instance: vk.InstanceProxy,
|
||||
surface: vk.SurfaceKHR,
|
||||
device: vk.DeviceProxy,
|
||||
|
||||
queue_family: u32,
|
||||
queue: vk.Queue,
|
||||
command_pool: vk.CommandPool,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, connection: *xcb.Connection, window: xcb.Window) !Vulkan {
|
||||
const vulkan_loader = try DynamicLibrary.open("libvulkan.so.1");
|
||||
errdefer vulkan_loader.close();
|
||||
|
||||
const vkGetInstanceProcAddr = try vulkan_loader.sym(vk.PfnGetInstanceProcAddr, "vkGetInstanceProcAddr");
|
||||
|
||||
const base = vk.BaseWrapper.load(vkGetInstanceProcAddr);
|
||||
|
||||
const instance_handle = blk: {
|
||||
const enabled_layers: []const [*:0]const u8 = &.{};
|
||||
const enabled_extensions: []const [*:0]const u8 = &.{
|
||||
vk.extensions.khr_surface.name,
|
||||
vk.extensions.khr_xcb_surface.name,
|
||||
};
|
||||
|
||||
break :blk try base.createInstance(&.{
|
||||
.p_application_info = &.{
|
||||
.p_application_name = "xcb_sample",
|
||||
.application_version = vk.makeApiVersion(0, 0, 0, 0).toU32(),
|
||||
.p_engine_name = "xcb_sample",
|
||||
.engine_version = vk.makeApiVersion(0, 0, 0, 0).toU32(),
|
||||
.api_version = vk.API_VERSION_1_0.toU32(),
|
||||
},
|
||||
.enabled_layer_count = @intCast(enabled_layers.len),
|
||||
.pp_enabled_layer_names = enabled_layers.ptr,
|
||||
.enabled_extension_count = @intCast(enabled_extensions.len),
|
||||
.pp_enabled_extension_names = enabled_extensions.ptr,
|
||||
}, null);
|
||||
};
|
||||
|
||||
const instance_wrapper_ptr = try allocator.create(vk.InstanceWrapper);
|
||||
errdefer allocator.destroy(instance_wrapper_ptr);
|
||||
|
||||
instance_wrapper_ptr.* = .load(instance_handle, vkGetInstanceProcAddr);
|
||||
const instance = vk.InstanceProxy.init(instance_handle, instance_wrapper_ptr);
|
||||
errdefer instance.destroyInstance(null);
|
||||
|
||||
const surface = try instance.createXcbSurfaceKHR(&.{
|
||||
.connection = connection,
|
||||
.window = window,
|
||||
}, null);
|
||||
errdefer instance.destroySurfaceKHR(surface, null);
|
||||
|
||||
const physical_device = blk: {
|
||||
const physical_devices = try instance.enumeratePhysicalDevicesAlloc(allocator);
|
||||
defer allocator.free(physical_devices);
|
||||
|
||||
if (physical_devices.len > 0) {
|
||||
break :blk physical_devices[0];
|
||||
}
|
||||
|
||||
return error.NoDevice;
|
||||
};
|
||||
|
||||
const queue_family: u32 = blk: {
|
||||
const queue_families_properties = try instance.getPhysicalDeviceQueueFamilyPropertiesAlloc(physical_device, allocator);
|
||||
defer allocator.free(queue_families_properties);
|
||||
|
||||
// Look for the first family that has the `graphics_bit` set and
|
||||
// supports presentation to a given surface.
|
||||
|
||||
for (queue_families_properties, 0..) |queue_family_properties, i| {
|
||||
const graphics_bit = queue_family_properties.queue_flags.graphics_bit;
|
||||
const supports_presentation = try instance.getPhysicalDeviceSurfaceSupportKHR(physical_device, @intCast(i), surface) != .false;
|
||||
|
||||
if (graphics_bit and supports_presentation) {
|
||||
break :blk @intCast(i);
|
||||
}
|
||||
}
|
||||
|
||||
return error.NoQueue;
|
||||
};
|
||||
|
||||
const device_handle = blk: {
|
||||
const queue_create_infos: []const vk.DeviceQueueCreateInfo = &.{
|
||||
.{
|
||||
.queue_family_index = queue_family,
|
||||
.queue_count = 1,
|
||||
.p_queue_priorities = &.{0},
|
||||
},
|
||||
};
|
||||
const enabled_extensions: []const [*:0]const u8 = &.{
|
||||
vk.extensions.khr_swapchain.name,
|
||||
};
|
||||
|
||||
break :blk try instance.createDevice(physical_device, &.{
|
||||
.queue_create_info_count = @intCast(queue_create_infos.len),
|
||||
.p_queue_create_infos = queue_create_infos.ptr,
|
||||
.enabled_extension_count = @intCast(enabled_extensions.len),
|
||||
.pp_enabled_extension_names = enabled_extensions.ptr,
|
||||
}, null);
|
||||
};
|
||||
|
||||
const device_wrapper_ptr = try allocator.create(vk.DeviceWrapper);
|
||||
errdefer allocator.destroy(device_wrapper_ptr);
|
||||
|
||||
device_wrapper_ptr.* = .load(device_handle, instance.wrapper.dispatch.vkGetDeviceProcAddr.?);
|
||||
const device = vk.DeviceProxy.init(device_handle, device_wrapper_ptr);
|
||||
errdefer device.destroyDevice(null);
|
||||
|
||||
const queue = device.getDeviceQueue(queue_family, 0);
|
||||
|
||||
const command_pool = try device.createCommandPool(&.{
|
||||
.flags = .{ .transient_bit = true },
|
||||
.queue_family_index = queue_family,
|
||||
}, null);
|
||||
errdefer device.destroyCommandPool(command_pool, null);
|
||||
|
||||
return .{
|
||||
.vulkan_loader = vulkan_loader,
|
||||
|
||||
.base = base,
|
||||
.instance = instance,
|
||||
.surface = surface,
|
||||
.device = device,
|
||||
|
||||
.queue_family = queue_family,
|
||||
.queue = queue,
|
||||
.command_pool = command_pool,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Vulkan, allocator: std.mem.Allocator) void {
|
||||
self.device.destroyCommandPool(self.command_pool, null);
|
||||
|
||||
self.device.destroyDevice(null);
|
||||
allocator.destroy(self.device.wrapper);
|
||||
self.instance.destroySurfaceKHR(self.surface, null);
|
||||
self.instance.destroyInstance(null);
|
||||
allocator.destroy(self.instance.wrapper);
|
||||
|
||||
self.vulkan_loader.close();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
const DynamicLibrary = opaque {
|
||||
fn open(path: [*:0]const u8) !*DynamicLibrary {
|
||||
const ptr = std.c.dlopen(path, .{ .NOW = true }) orelse {
|
||||
std.debug.print("{s}\n", .{std.c.dlerror().?});
|
||||
return error.DlOpen;
|
||||
};
|
||||
|
||||
return @ptrCast(ptr);
|
||||
}
|
||||
|
||||
fn close(self: *DynamicLibrary) void {
|
||||
_ = std.c.dlclose(self);
|
||||
}
|
||||
|
||||
fn sym(self: *DynamicLibrary, comptime Ptr: type, symbol: [*:0]const u8) !Ptr {
|
||||
const ptr = std.c.dlsym(self, symbol) orelse {
|
||||
std.debug.print("{s}\n", .{std.c.dlerror().?});
|
||||
return error.DlSym;
|
||||
};
|
||||
|
||||
return @ptrCast(@alignCast(ptr));
|
||||
}
|
||||
};
|
||||
95
packages/xcb/sample/root.zig
Normal file
95
packages/xcb/sample/root.zig
Normal file
@@ -0,0 +1,95 @@
|
||||
const std = @import("std");
|
||||
const xcb = @import("xcb");
|
||||
|
||||
const Vulkan = @import("Vulkan.zig");
|
||||
|
||||
// Declarations picked up by "vulkan" module
|
||||
|
||||
pub const xcb_connection_t = xcb.Connection;
|
||||
pub const xcb_visualid_t = xcb.Visualid;
|
||||
pub const xcb_window_t = xcb.Window;
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const connection = xcb.Connection.connect(null, null);
|
||||
defer connection.disconnect();
|
||||
|
||||
{
|
||||
const error_code = connection.hasError();
|
||||
if (error_code != 0) {
|
||||
std.debug.print("Error: xcb_connection_has_error returned {}", .{error_code});
|
||||
return error.XcbConnection;
|
||||
}
|
||||
}
|
||||
|
||||
const setup = connection.getSetup();
|
||||
const screen = setup.rootsIterator().data;
|
||||
|
||||
const window = @as(xcb.Window, @enumFromInt(connection.generateId()));
|
||||
_ = connection.createWindow(
|
||||
xcb.COPY_FROM_PARENT,
|
||||
window,
|
||||
screen.root,
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
360,
|
||||
1,
|
||||
.INPUT_OUTPUT,
|
||||
screen.root_visual,
|
||||
.{ .BACK_PIXEL = true },
|
||||
&[_]u32{screen.black_pixel},
|
||||
);
|
||||
|
||||
const wm_protocols_atom_cookie = connection.internAtom(true, "WM_PROTOCOLS");
|
||||
const wm_delete_window_atom_cookie = connection.internAtom(false, "WM_DELETE_WINDOW");
|
||||
|
||||
const wm_protocols_atom = blk: {
|
||||
const reply = connection.internAtomReply(wm_protocols_atom_cookie, null) orelse return error.XcbInternAtom;
|
||||
const ret = reply.atom;
|
||||
std.c.free(reply);
|
||||
break :blk ret;
|
||||
};
|
||||
|
||||
const wm_delete_window_atom = blk: {
|
||||
const reply = connection.internAtomReply(wm_delete_window_atom_cookie, null) orelse return error.XcbInternAtom;
|
||||
const ret = reply.atom;
|
||||
std.c.free(reply);
|
||||
break :blk ret;
|
||||
};
|
||||
|
||||
_ = connection.changeProperty(
|
||||
.REPLACE,
|
||||
window,
|
||||
wm_protocols_atom,
|
||||
.ATOM,
|
||||
32,
|
||||
1,
|
||||
&wm_delete_window_atom,
|
||||
);
|
||||
|
||||
_ = connection.mapWindow(window);
|
||||
_ = connection.flush();
|
||||
|
||||
var vulkan = try Vulkan.init(init.gpa, connection, window);
|
||||
defer vulkan.deinit(init.gpa);
|
||||
|
||||
var running = true;
|
||||
while (running) {
|
||||
if (connection.waitForEvent()) |event| {
|
||||
defer std.c.free(event);
|
||||
|
||||
switch (event.response_type & ~@as(u8, 0x80)) {
|
||||
xcb.CLIENT_MESSAGE => {
|
||||
const client_message_event = @as(*xcb.ClientMessageEvent, @ptrCast(event));
|
||||
if (client_message_event.data.data32[0] == @intFromEnum(wm_delete_window_atom)) {
|
||||
running = false;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
} else {
|
||||
std.debug.print("Error: xcb_wait_for_event returned null", .{});
|
||||
return error.XcbWaitForEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
4383
packages/xcb/src/root.zig
Normal file
4383
packages/xcb/src/root.zig
Normal file
File diff suppressed because it is too large
Load Diff
35719
packages/xcb/vendor/vk.xml
vendored
Normal file
35719
packages/xcb/vendor/vk.xml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user