Compare commits

..

21 Commits

Author SHA1 Message Date
af8d725e0c media: update stbtt bindings 2026-09-19 22:50:28 +02:00
ff87d58319 media: add stbtt binding 2026-09-13 02:02:59 +02:00
70076f2268 Minor fixes 2026-09-12 23:52:43 +02:00
8dbac71007 cjit: fix stdint.h header 2026-09-04 16:39:29 +02:00
c14fc89c74 xcb: add Vulkan init to sample 2026-09-04 16:38:58 +02:00
94c08ff3b6 xcb: minor touch-ups 2026-09-01 01:08:15 +02:00
084512258c datastore: typed value retrieval 2026-09-01 00:42:48 +02:00
1093209328 xcb: trimming fat 2026-08-31 03:39:07 +02:00
08217a0256 xcb: sample-driven ziggification 2026-08-31 03:10:09 +02:00
0f6d1a0842 xcb: add sample 2026-08-31 01:09:53 +02:00
fe7f845359 xcb: ziggify 2026-08-31 00:10:52 +02:00
043d3e1c22 xcb: c-translate 2026-08-30 12:44:11 +02:00
753e2a850d datastore: refactors, clustered (simd/aosoa) value support 2026-08-20 01:35:42 +02:00
Szymon Nowakowski
e262e00829 datatore: new project 2026-08-17 23:46:54 +02:00
cbbe7f2ea1 web: fix typo in mime extension 2026-07-29 12:05:53 +02:00
3ba53ac724 web: add extension to media type map 2026-07-09 02:10:48 +02:00
dc203cd700 web: expand example executable 2026-07-09 02:05:42 +02:00
91020a2741 web: Update UUID code to zig 0.16.0 2026-06-24 13:15:57 +02:00
d2719a77f0 web: Try enabling kTLS 2026-06-24 13:15:23 +02:00
9f9ae95f54 web: Export Address struct 2026-06-24 01:06:19 +02:00
e7c8d89aa9 web: Update to zig 0.16.0 2026-06-24 00:10:39 +02:00
31 changed files with 47199 additions and 108 deletions

View File

@@ -114,5 +114,3 @@ typedef uint64_t uintptr_t;
#define WCHAR_MIN (0) #define WCHAR_MIN (0)
#define WCHAR_MAX (0x10FFFF) #define WCHAR_MAX (0x10FFFF)
#endif

View File

@@ -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 => zig_int.signedness == .unsigned and zig_int.bits == 64,
.unsigned_long_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, else => false,
}, },

View 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);
}

View 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",
},
},
}

View 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));
}
};

View 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,
};

View 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);
}

View File

@@ -17,8 +17,11 @@ pub fn build(b: *std.Build) void {
}, },
}); });
mod.addCSourceFile(.{ mod.addCSourceFiles(.{
.file = b.path("src/stbi/stb_image.c"), .files = &.{
"src/stbi/stb_image.c",
"src/stbtt/stb_truetype.c",
},
.flags = &.{ .flags = &.{
"-std=c17", "-std=c17",
"-Wall", "-Wall",

View File

@@ -451,7 +451,7 @@ pub const ChunkType = enum(u32) {
_, _,
fn fromName(name: *const [4]u8) 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 { pub fn fromBytes(bytes: *const [4]u8) ChunkType {
@@ -459,18 +459,18 @@ pub const ChunkType = enum(u32) {
} }
pub fn toBytes(self: ChunkType) [4]u8 { 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; 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; 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; return self.toBytes()[3] & 0b0010_0000 != 0;
} }
@@ -493,7 +493,7 @@ pub const StandardKeyword = enum {
Description, Description,
/// Copyright notice /// Copyright notice
Copyright, Copyright,
/// Time Time of original image creation /// Time of original image creation
Creation, Creation,
/// Software used to create the image /// Software used to create the image
Software, Software,

View File

@@ -9,6 +9,7 @@ pub const png = @import("png.zig");
pub const qoa = @import("qoa.zig"); pub const qoa = @import("qoa.zig");
pub const qoi = @import("qoi.zig"); pub const qoi = @import("qoi.zig");
pub const stbi = @import("stbi.zig"); pub const stbi = @import("stbi.zig");
pub const stbtt = @import("stbtt.zig");
test "refAllDecls" { test "refAllDecls" {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());

View File

@@ -0,0 +1,263 @@
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,
};
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;
};
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;
}
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,
};
// --- 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());
}

View File

@@ -0,0 +1,24 @@
#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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
.{ .{
.name = .web, .name = .web,
.version = "0.0.0", .version = "0.0.0",
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.16.0",
.paths = .{ .paths = .{
"src", "src",
"build.zig", "build.zig",

View 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,
}
}
};

View File

@@ -1,13 +1,14 @@
const std = @import("std"); const std = @import("std");
const Connection = @This(); const Connection = @This();
const Address = @import("Address.zig").Address;
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor; const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const openssl = @import("openssl.zig"); const openssl = @import("openssl.zig");
const iovec = std.posix.iovec; const iovec = std.posix.iovec;
const iovec_const = std.posix.iovec_const; const iovec_const = std.posix.iovec_const;
address: std.net.Address, address: Address,
fd: FileDescriptor, fd: FileDescriptor,
ssl: ?*openssl.Ssl, ssl: ?*openssl.Ssl,
node: std.DoublyLinkedList.Node = .{}, node: std.DoublyLinkedList.Node = .{},
@@ -17,7 +18,7 @@ node: std.DoublyLinkedList.Node = .{},
pub fn reinit( pub fn reinit(
self: *Connection, self: *Connection,
address: std.net.Address, address: Address,
fd: FileDescriptor, fd: FileDescriptor,
ssl: ?*openssl.Ssl, ssl: ?*openssl.Ssl,
) void { ) void {

View File

@@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const linux = std.os.linux; const linux = std.os.linux;
const errno = linux.E.init; const errno = linux.errno;
const iovec = std.posix.iovec; const iovec = std.posix.iovec;
const iovec_const = std.posix.iovec_const; 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 { pub fn close(self: FileDescriptor) void {
_ = linux.close(@intFromEnum(self)); _ = linux.close(@intFromEnum(self));
} }

View File

@@ -5,7 +5,7 @@ const UUID = @import("UUID.zig");
const decoder = &std.base64.url_safe_no_pad.Decoder; const decoder = &std.base64.url_safe_no_pad.Decoder;
const encoder = &std.base64.url_safe_no_pad.Encoder; 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 { return struct {
pub const tag = _tag; pub const tag = _tag;

View File

@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const Server = @This(); const Server = @This();
const Address = @import("Address.zig").Address;
const Connection = @import("Connection.zig"); const Connection = @import("Connection.zig");
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor; const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const http = @import("http.zig"); const http = @import("http.zig");
@@ -11,10 +12,10 @@ const Worker = @import("Worker.zig");
const log = std.log.scoped(.Server); const log = std.log.scoped(.Server);
const linux = std.os.linux; const linux = std.os.linux;
const errno = linux.E.init; const errno = linux.errno;
fd: FileDescriptor, fd: FileDescriptor,
address: std.net.Address, address: Address,
ssl_ctx: ?*openssl.SslContext, ssl_ctx: ?*openssl.SslContext,
workers: []Worker, workers: []Worker,
threads: []std.Thread, threads: []std.Thread,
@@ -29,9 +30,10 @@ connection_queue: std.DoublyLinkedList,
connection_pool: std.DoublyLinkedList, connection_pool: std.DoublyLinkedList,
connection_buffer: []Connection, connection_buffer: []Connection,
mutex: std.Thread.Mutex, io: std.Io,
cond_connection_queued: std.Thread.Condition, mutex: std.Io.Mutex,
cond_connection_freed: std.Thread.Condition, cond_connection_queued: std.Io.Condition,
cond_connection_freed: std.Io.Condition,
/// 4 kiB /// 4 kiB
const page_size = 4 * 1024; const page_size = 4 * 1024;
@@ -40,7 +42,7 @@ const huge_page_size = 2 * 1024 * 1024;
pub const Options = struct { pub const Options = struct {
request_handler: RequestHandler, 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 /// If not `null`, the server will use TLS with the provided OpenSSL
/// context. /// context.
ssl_ctx: ?*openssl.SslContext = null, ssl_ctx: ?*openssl.SslContext = null,
@@ -73,7 +75,7 @@ pub const Options = struct {
read_timeout_us: u64 = 1 * std.time.us_per_s, 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(); const worker_count = if (options.worker_count > 0) options.worker_count else try std.Thread.getCpuCount();
// Create socket fd // 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( const read_buffer_ptr = try errOrPtr(linux.mmap(
null, null,
double_all_read_buffers_size, double_all_read_buffers_size,
linux.PROT.NONE, .{},
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true }, linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1, -1,
0, 0,
@@ -138,8 +140,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
try err(linux.mmap( try err(linux.mmap(
read_buffer_ptr + double_offset, read_buffer_ptr + double_offset,
single_read_buffer_size, single_read_buffer_size,
linux.PROT.READ | linux.PROT.WRITE, .{ .READ = true, .WRITE = true },
linux.MAP{ .TYPE = .SHARED, .FIXED = true }, .{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd), @intFromEnum(read_buffer_fd),
@intCast(offset), @intCast(offset),
)); ));
@@ -147,8 +149,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
try err(linux.mmap( try err(linux.mmap(
read_buffer_ptr + double_offset + single_read_buffer_size, read_buffer_ptr + double_offset + single_read_buffer_size,
single_read_buffer_size, single_read_buffer_size,
linux.PROT.READ | linux.PROT.WRITE, .{ .READ = true, .WRITE = true },
linux.MAP{ .TYPE = .SHARED, .FIXED = true }, .{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd), @intFromEnum(read_buffer_fd),
@intCast(offset), @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( const header_write_buffer_ptr = try errOrPtr(linux.mmap(
null, null,
all_header_write_buffers_size, all_header_write_buffers_size,
linux.PROT.READ | linux.PROT.WRITE, .{ .READ = true, .WRITE = true },
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true }, .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1, -1,
0, 0,
)); ));
@@ -177,8 +179,8 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
const body_write_buffer_ptr = try errOrPtr(linux.mmap( const body_write_buffer_ptr = try errOrPtr(linux.mmap(
null, null,
all_body_write_buffers_size, all_body_write_buffers_size,
linux.PROT.READ | linux.PROT.WRITE, .{ .READ = true, .WRITE = true },
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true }, .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1, -1,
0, 0,
)); ));
@@ -232,9 +234,10 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
.connection_pool = connection_pool, .connection_pool = connection_pool,
.connection_buffer = connection_buffer, .connection_buffer = connection_buffer,
.mutex = .{}, .io = io,
.cond_connection_queued = .{}, .mutex = .init,
.cond_connection_freed = .{}, .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.", .{}); log.debug("Storing `false` into worker_running.", .{});
worker_running.store(false, .release); worker_running.store(false, .release);
log.debug("Broadcasting connection queued condition variable.", .{}); 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| { for (self.threads[0..spawned], 0..) |*thread, i| {
log.debug("Joining the thread of worker #{d}.", .{i}); log.debug("Joining the thread of worker #{d}.", .{i});
thread.join(); thread.join();
@@ -298,8 +301,8 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
} }
while (running.load(.acquire)) { while (running.load(.acquire)) {
var address: std.net.Address = undefined; var address: Address = undefined;
var address_size: u32 = @sizeOf(std.net.Address); var address_size: u32 = @sizeOf(Address);
log.debug("Accepting connection.", .{}); log.debug("Accepting connection.", .{});
const fd = self.fd.accept(&address.any, &address_size) catch |e| { 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.", .{}); log.debug("Acquiring mutex.", .{});
self.mutex.lock(); self.mutex.lockUncancelable(self.io);
log.debug("Acquired mutex.", .{}); log.debug("Acquired mutex.", .{});
defer { defer {
log.debug("Unlocking mutex.", .{}); log.debug("Unlocking mutex.", .{});
self.mutex.unlock(); self.mutex.unlock(self.io);
} }
while (true) { 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.", .{}); 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("Woken up on connection freed condition variable.", .{});
} }
} }
log.debug("Signaling connection queued condition variable.", .{}); log.debug("Signaling connection queued condition variable.", .{});
self.cond_connection_queued.signal(); self.cond_connection_queued.signal(self.io);
} else { } else {
log.debug("Loaded `false` from running, the accept loop exited.", .{}); 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.", .{}); log.debug("Accepting SSL layer.", .{});
try ssl.accept(); try ssl.accept();
log.debug("Accepted SSL layer.", .{}); 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; return ssl;
} else { } else {
return null; return null;

View File

@@ -5,9 +5,10 @@ bytes: [16]u8,
pub const zero: UUID = .{ .bytes = @splat(0) }; pub const zero: UUID = .{ .bytes = @splat(0) };
pub fn v4() UUID { pub fn v4(io: std.Io) UUID {
var bytes: [16]u8 = undefined; var bytes: [16]u8 = undefined;
std.crypto.random.bytes(&bytes);
io.random(&bytes);
bytes[6] = (bytes[6] & 0x0F) | 0x40; bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80; bytes[8] = (bytes[8] & 0x3F) | 0x80;
return .{ .bytes = bytes }; return .{ .bytes = bytes };
@@ -35,7 +36,7 @@ pub const namespaces = struct {
pub fn v5(namespace: UUID, name: []const u8) UUID { pub fn v5(namespace: UUID, name: []const u8) UUID {
const hash = blk: { const hash = blk: {
var hasher = std.crypto.hash.Sha1.init(.{}); var hasher = std.crypto.hash.Sha1.init(.{});
hasher.update(namespace); hasher.update(namespace.bytes);
hasher.update(name); hasher.update(name);
break :blk hasher.finalResult(); break :blk hasher.finalResult();
}; };
@@ -47,13 +48,13 @@ pub fn v5(namespace: UUID, name: []const u8) UUID {
return .{ .bytes = bytes }; return .{ .bytes = bytes };
} }
var v7_lock: std.Thread.Mutex = .{}; var v7_lock: std.Io.Mutex = .init;
var v7_last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 }; var v7_last_timestamp: std.atomic.Value(u64) = .init(0);
var v7_counter: std.atomic.Value(u32) = .{ .raw = 0 }; var v7_counter: std.atomic.Value(u32) = .init(0);
fn getCount(timestamp: u64) u32 { fn getCount(io: std.Io, timestamp: u64) u32 {
v7_lock.lock(); v7_lock.lockUncancelable(io);
defer v7_lock.unlock(); defer v7_lock.unlock(io);
if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) { if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) {
v7_counter.store(0, .monotonic); v7_counter.store(0, .monotonic);
@@ -62,12 +63,12 @@ fn getCount(timestamp: u64) u32 {
return v7_counter.fetchAdd(1, .monotonic) % 4096; 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 timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
const count = getCount(timestamp); const count = getCount(timestamp);
const random = blk: { const random = blk: {
var bytes: [8]u8 = undefined; var bytes: [8]u8 = undefined;
std.crypto.random.bytes(&bytes); io.random(&bytes);
break :blk bytes; break :blk bytes;
}; };

View File

@@ -77,11 +77,11 @@ pub fn worker(
running: *const std.atomic.Value(bool), running: *const std.atomic.Value(bool),
) void { ) void {
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id}); 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}] Acquired mutex.", .{self.worker_id});
defer { defer {
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id}); log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
server.mutex.unlock(); server.mutex.unlock(server.io);
} }
while (running.load(.acquire)) { 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}] Popped connection to {f} from the connection queue.", .{ self.worker_id, connection.address });
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id}); log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
server.mutex.unlock(); server.mutex.unlock(server.io);
defer { defer {
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id}); 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}] Acquired mutex.", .{self.worker_id});
log.debug("[#{d}] Returning connection to connection pool.", .{self.worker_id}); log.debug("[#{d}] Returning connection to connection pool.", .{self.worker_id});
server.connection_pool.append(&connection.node); server.connection_pool.append(&connection.node);
log.debug("[#{d}] Signaling connection freed condition variable.", .{self.worker_id}); 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 }); log.debug("[#{d}] Handling connection to {f}.", .{ self.worker_id, connection.address });
@@ -108,7 +108,7 @@ pub fn worker(
}; };
} else { } else {
log.debug("[#{d}] Waiting on connection queued condition variable.", .{self.worker_id}); 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}); log.debug("[#{d}] Woken up on connection queued condition variable.", .{self.worker_id});
} }
} else { } else {

View File

@@ -34,10 +34,10 @@
//! //!
//! The parser is not involved in any HTTP semantics, only its syntax. It is up //! 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 //! 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. //! even choose to). For example, none of the header field values are verified,
//! The only exception is `Content-Length`. The parser must know the value to //! except for `Content-Length`. The parser must know the value of
//! determine the length of the request body. If the value fails to parse as a //! `Content-Length` to determine the length of the request body. If the value
//! decimal non-negative integer, a syntax error is returned. Note that //! 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), //! 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 //! `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 //! repeated as a comma-separated list (e.g. `Content-Length: 42, 42`) MAY be

View File

@@ -4,23 +4,17 @@ const web = @import("web");
const linux = std.os.linux; const linux = std.os.linux;
const errno = linux.E.init; const errno = linux.E.init;
const ssl = web.openssl; const ssl = web.openssl;
const UUID = web.UUID;
var running: std.atomic.Value(bool) = .init(true); 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; var buf: [32]u8 = undefined;
const signal_name = blk: inline for (@typeInfo(linux.SIG).@"struct".decls) |decl| { const signal_name = switch (sig) {
if (comptime std.mem.eql(u8, decl.name, "BLOCK") or _ => std.fmt.bufPrint(&buf, "#{d}", .{sig}) catch unreachable,
std.mem.eql(u8, decl.name, "UNBLOCK") or inline else => |x| "SIG" ++ @tagName(x),
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;
}; };
std.log.debug("Interrupted with signal {s}.", .{signal_name}); std.log.debug("Interrupted with signal {s}.", .{signal_name});
@@ -34,6 +28,28 @@ fn interruptionHandler(sig: i32) callconv(.c) void {
} }
const Handler = struct { 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 { fn handle(_: *anyopaque, request: *web.Request, response: *web.Response) !void {
std.log.info("{f} | {s} {s}", .{ std.log.info("{f} | {s} {s}", .{
response.connection.address, response.connection.address,
@@ -41,38 +57,50 @@ const Handler = struct {
request.pathname, request.pathname,
}); });
if (!std.mem.eql(u8, request.pathname, "/")) { if (std.mem.eql(u8, request.pathname, "/")) {
try response.body_writer.writeAll("Not Found\n"); if (request.method != .GET) {
try methodNotAllowed(response);
return;
}
try response.header_writer.writeAll(web.http.status.not_found); try response.body_writer.writeAll("{\"ok\":true}\n");
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\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.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n"); try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody(); 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) { if (bytes > body_write_buffer_huge_pages * 2 * 1024 * 1024) {
try response.body_writer.writeAll("Method Not Allowed\n"); try notFound(response);
return;
}
try response.header_writer.writeAll(web.http.status.method_not_allowed); if (request.method != .GET) {
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n"); 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.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n"); try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody(); 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 { fn interface() web.RequestHandler {
@@ -85,12 +113,8 @@ const Handler = struct {
} }
}; };
pub fn main() !void { pub fn main(init: std.process.Init) !void {
var gpa: std.heap.GeneralPurposeAllocator(.{ const allocator = init.gpa;
.thread_safe = true,
}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
_ = ssl.c_ssl.SSL_library_init(); _ = ssl.c_ssl.SSL_library_init();
_ = ssl.c_ssl.OpenSSL_add_all_algorithms(); _ = ssl.c_ssl.OpenSSL_add_all_algorithms();
@@ -99,16 +123,21 @@ pub fn main() !void {
const ssl_ctx = try ssl.SslContext.new(.tlsServerMethod()); const ssl_ctx = try ssl.SslContext.new(.tlsServerMethod());
defer ssl_ctx.free(); defer ssl_ctx.free();
_ = ssl_ctx.setMinProtoVersion(ssl.c_ssl.TLS1_3_VERSION); _ = ssl_ctx.setMinProtoVersion(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_2); _ = 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.useCertificateFile("cert.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
try ssl_ctx.usePrivateKeyFile("key.pem", ssl.c_ssl.SSL_FILETYPE_PEM); try ssl_ctx.usePrivateKeyFile("key.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
try ssl_ctx.checkPrivateKey(); try ssl_ctx.checkPrivateKey();
var server = try web.Server.init(allocator, .{ var server = try web.Server.init(allocator, init.io, .{
.request_handler = Handler.interface(), .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, .ssl_ctx = ssl_ctx,
}); });
defer server.deinit(allocator); defer server.deinit(allocator);
@@ -118,13 +147,13 @@ pub fn main() !void {
.mask = linux.sigemptyset(), .mask = linux.sigemptyset(),
.flags = linux.SA.RESETHAND, .flags = linux.SA.RESETHAND,
}; };
signal(linux.SIG.INT, &sigaction); signal(.INT, &sigaction);
signal(linux.SIG.TERM, &sigaction); signal(.TERM, &sigaction);
try server.listen(&running); 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); var old_action = std.mem.zeroes(linux.Sigaction);
_ = linux.sigaction(sig, null, &old_action); _ = linux.sigaction(sig, null, &old_action);
if (old_action.handler.handler == linux.SIG.IGN) return; if (old_action.handler.handler == linux.SIG.IGN) return;

171
packages/web/src/mime.zig Normal file
View 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" },
});

View File

@@ -5,10 +5,12 @@ comptime {
std.debug.assert(@import("builtin").os.tag == .linux); std.debug.assert(@import("builtin").os.tag == .linux);
} }
pub const Address = @import("Address.zig").Address;
pub const Connection = @import("Connection.zig"); pub const Connection = @import("Connection.zig");
pub const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor; pub const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
pub const http = @import("http.zig"); pub const http = @import("http.zig");
pub const Id = @import("Id.zig").Id; pub const Id = @import("Id.zig").Id;
pub const mime = @import("mime.zig");
pub const openssl = @import("openssl.zig"); pub const openssl = @import("openssl.zig");
pub const Request = @import("Request.zig"); pub const Request = @import("Request.zig");
pub const RequestHandler = @import("RequestHandler.zig"); pub const RequestHandler = @import("RequestHandler.zig");

59
packages/xcb/build.zig Normal file
View 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);
}

View 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,
}

View 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));
}
};

View 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

File diff suppressed because it is too large Load Diff

35719
packages/xcb/vendor/vk.xml vendored Normal file

File diff suppressed because it is too large Load Diff